/* Copyright (C) 2009, 2010 Simon Josefsson
 * Copyright (C) 2006, 2007 The Written Word, Inc.  All rights reserved.
 * Copyright (c) 2004-2006, Sara Golemon <sarag@libssh2.org>
 *
 * Author: Simon Josefsson
 *
 * Redistribution and use in source and binary forms,
 * with or without modification, are permitted provided
 * that the following conditions are met:
 *
 *   Redistributions of source code must retain the above
 *   copyright notice, this list of conditions and the
 *   following disclaimer.
 *
 *   Redistributions in binary form must reproduce the above
 *   copyright notice, this list of conditions and the following
 *   disclaimer in the documentation and/or other materials
 *   provided with the distribution.
 *
 *   Neither the name of the copyright holder nor the names
 *   of any other contributors may be used to endorse or
 *   promote products derived from this software without
 *   specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 */

#ifdef LIBSSH2_CRYPTO_C /* Compile this via crypto.c */

#include <stdlib.h>
#include <assert.h>

#ifndef EVP_MAX_BLOCK_LENGTH
#define EVP_MAX_BLOCK_LENGTH 32
#endif

int
read_openssh_private_key_from_memory(void **key_ctx, LIBSSH2_SESSION *session,
                                     const char *key_type,
                                     const char *filedata,
                                     size_t filedata_len,
                                     unsigned const char *passphrase);

static int
_libssh2_sk_pub_openssh_keyfilememory(LIBSSH2_SESSION *session,
                                      void **key_ctx,
                                      const char *key_type,
                                      unsigned char **method,
                                      size_t *method_len,
                                      unsigned char **pubkeydata,
                                      size_t *pubkeydata_len,
                                      int *algorithm,
                                      unsigned char *flags,
                                      const char **application,
                                      const unsigned char **key_handle,
                                      size_t *handle_len,
                                      const char *privatekeydata,
                                      size_t privatekeydata_len,
                                      unsigned const char *passphrase);

#if LIBSSH2_RSA || LIBSSH2_DSA || LIBSSH2_ECDSA
static unsigned char *
write_bn(unsigned char *buf, const BIGNUM *bn, int bn_bytes)
{
    unsigned char *p = buf;

    /* Left space for bn size which will be written below. */
    p += 4;

    *p = 0;
    BN_bn2bin(bn, p + 1);
    if(!(*(p + 1) & 0x80)) {
        memmove(p, p + 1, --bn_bytes);
    }
    _libssh2_htonu32(p - 4, bn_bytes);  /* Post write bn size. */

    return p + bn_bytes;
}
#endif

int
_libssh2_openssl_random(void *buf, size_t len)
{
    if(len > INT_MAX) {
        return -1;
    }

    return RAND_bytes(buf, (int)len) == 1 ? 0 : -1;
}

#if LIBSSH2_RSA
int
_libssh2_rsa_new(libssh2_rsa_ctx ** rsa,
                 const unsigned char *edata,
                 unsigned long elen,
                 const unsigned char *ndata,
                 unsigned long nlen,
                 const unsigned char *ddata,
                 unsigned long dlen,
                 const unsigned char *pdata,
                 unsigned long plen,
                 const unsigned char *qdata,
                 unsigned long qlen,
                 const unsigned char *e1data,
                 unsigned long e1len,
                 const unsigned char *e2data,
                 unsigned long e2len,
                 const unsigned char *coeffdata, unsigned long coefflen)
{
    BIGNUM * e;
    BIGNUM * n;
    BIGNUM * d = 0;
    BIGNUM * p = 0;
    BIGNUM * q = 0;
    BIGNUM * dmp1 = 0;
    BIGNUM * dmq1 = 0;
    BIGNUM * iqmp = 0;

    e = BN_new();
    BN_bin2bn(edata, (int) elen, e);

    n = BN_new();
    BN_bin2bn(ndata, (int) nlen, n);

    if(ddata) {
        d = BN_new();
        BN_bin2bn(ddata, (int) dlen, d);

        p = BN_new();
        BN_bin2bn(pdata, (int) plen, p);

        q = BN_new();
        BN_bin2bn(qdata, (int) qlen, q);

        dmp1 = BN_new();
        BN_bin2bn(e1data, (int) e1len, dmp1);

        dmq1 = BN_new();
        BN_bin2bn(e2data, (int) e2len, dmq1);

        iqmp = BN_new();
        BN_bin2bn(coeffdata, (int) coefflen, iqmp);
    }

    *rsa = RSA_new();
#ifdef HAVE_OPAQUE_STRUCTS
    RSA_set0_key(*rsa, n, e, d);
#else
    (*rsa)->e = e;
    (*rsa)->n = n;
    (*rsa)->d = d;
#endif

#ifdef HAVE_OPAQUE_STRUCTS
    RSA_set0_factors(*rsa, p, q);
#else
    (*rsa)->p = p;
    (*rsa)->q = q;
#endif

#ifdef HAVE_OPAQUE_STRUCTS
    RSA_set0_crt_params(*rsa, dmp1, dmq1, iqmp);
#else
    (*rsa)->dmp1 = dmp1;
    (*rsa)->dmq1 = dmq1;
    (*rsa)->iqmp = iqmp;
#endif
    return 0;
}

int
_libssh2_rsa_sha2_verify(libssh2_rsa_ctx * rsactx,
                         size_t hash_len,
                         const unsigned char *sig,
                         size_t sig_len,
                         const unsigned char *m, size_t m_len)
{
    int ret;
    int nid_type;
    unsigned char *hash = malloc(hash_len);
    if(!hash)
        return -1;

    if(hash_len == SHA_DIGEST_LENGTH) {
        nid_type = NID_sha1;
        ret = _libssh2_sha1(m, m_len, hash);
    }
    else if(hash_len == SHA256_DIGEST_LENGTH) {
        nid_type = NID_sha256;
        ret = _libssh2_sha256(m, m_len, hash);

    }
    else if(hash_len == SHA512_DIGEST_LENGTH) {
        nid_type = NID_sha512;
        ret = _libssh2_sha512(m, m_len, hash);
    }
    else {
/* silence:
   warning C4701: potentially uninitialized local variable 'nid_type' used */
#if defined(_MSC_VER)
        nid_type = 0;
#endif
        ret = -1; /* unsupported digest */
    }

    if(ret) {
        free(hash);
        return -1; /* failure */
    }

    ret = RSA_verify(nid_type, hash, (unsigned int) hash_len,
                     (unsigned char *) sig,
                     (unsigned int) sig_len, rsactx);

    free(hash);

    return (ret == 1) ? 0 : -1;
}

#if LIBSSH2_RSA_SHA1
int
_libssh2_rsa_sha1_verify(libssh2_rsa_ctx * rsactx,
                         const unsigned char *sig,
                         size_t sig_len,
                         const unsigned char *m, size_t m_len)
{
    return _libssh2_rsa_sha2_verify(rsactx, SHA_DIGEST_LENGTH, sig, sig_len, m,
                                    m_len);
}
#endif
#endif

#if LIBSSH2_DSA
int
_libssh2_dsa_new(libssh2_dsa_ctx ** dsactx,
                 const unsigned char *p,
                 unsigned long p_len,
                 const unsigned char *q,
                 unsigned long q_len,
                 const unsigned char *g,
                 unsigned long g_len,
                 const unsigned char *y,
                 unsigned long y_len,
                 const unsigned char *x, unsigned long x_len)
{
    BIGNUM * p_bn;
    BIGNUM * q_bn;
    BIGNUM * g_bn;
    BIGNUM * pub_key;
    BIGNUM * priv_key = NULL;

    p_bn = BN_new();
    BN_bin2bn(p, (int) p_len, p_bn);

    q_bn = BN_new();
    BN_bin2bn(q, (int) q_len, q_bn);

    g_bn = BN_new();
    BN_bin2bn(g, (int) g_len, g_bn);

    pub_key = BN_new();
    BN_bin2bn(y, (int) y_len, pub_key);

    if(x_len) {
        priv_key = BN_new();
        BN_bin2bn(x, (int) x_len, priv_key);
    }

    *dsactx = DSA_new();

#ifdef HAVE_OPAQUE_STRUCTS
    DSA_set0_pqg(*dsactx, p_bn, q_bn, g_bn);
#else
    (*dsactx)->p = p_bn;
    (*dsactx)->g = g_bn;
    (*dsactx)->q = q_bn;
#endif

#ifdef HAVE_OPAQUE_STRUCTS
    DSA_set0_key(*dsactx, pub_key, priv_key);
#else
    (*dsactx)->pub_key = pub_key;
    (*dsactx)->priv_key = priv_key;
#endif
    return 0;
}

int
_libssh2_dsa_sha1_verify(libssh2_dsa_ctx * dsactx,
                         const unsigned char *sig,
                         const unsigned char *m, size_t m_len)
{
    unsigned char hash[SHA_DIGEST_LENGTH];
    DSA_SIG * dsasig;
    BIGNUM * r;
    BIGNUM * s;
    int ret = -1;

    r = BN_new();
    BN_bin2bn(sig, 20, r);
    s = BN_new();
    BN_bin2bn(sig + 20, 20, s);

    dsasig = DSA_SIG_new();
#ifdef HAVE_OPAQUE_STRUCTS
    DSA_SIG_set0(dsasig, r, s);
#else
    dsasig->r = r;
    dsasig->s = s;
#endif
    if(!_libssh2_sha1(m, m_len, hash))
        /* _libssh2_sha1() succeeded */
        ret = DSA_do_verify(hash, SHA_DIGEST_LENGTH, dsasig, dsactx);

    DSA_SIG_free(dsasig);

    return (ret == 1) ? 0 : -1;
}
#endif /* LIBSSH_DSA */

#if LIBSSH2_ECDSA

/* _libssh2_ecdsa_get_curve_type
 *
 * returns key curve type that maps to libssh2_curve_type
 *
 */

libssh2_curve_type
_libssh2_ecdsa_get_curve_type(libssh2_ecdsa_ctx *ec_ctx)
{
    const EC_GROUP *group = EC_KEY_get0_group(ec_ctx);
    return EC_GROUP_get_curve_name(group);
}

/* _libssh2_ecdsa_curve_type_from_name
 *
 * returns 0 for success, key curve type that maps to libssh2_curve_type
 *
 */

int
_libssh2_ecdsa_curve_type_from_name(const char *name,
                                    libssh2_curve_type *out_type)
{
    int ret = 0;
    libssh2_curve_type type;

    if(!name || strlen(name) != 19)
        return -1;

    if(strcmp(name, "ecdsa-sha2-nistp256") == 0)
        type = LIBSSH2_EC_CURVE_NISTP256;
    else if(strcmp(name, "ecdsa-sha2-nistp384") == 0)
        type = LIBSSH2_EC_CURVE_NISTP384;
    else if(strcmp(name, "ecdsa-sha2-nistp521") == 0)
        type = LIBSSH2_EC_CURVE_NISTP521;
    else {
/* silence:
   warning C4701: potentially uninitialized local variable 'type' used */
#if defined(_MSC_VER)
        type = (libssh2_curve_type)0;
#endif
        ret = -1;
    }

    if(ret == 0 && out_type) {
        *out_type = type;
    }

    return ret;
}

/* _libssh2_ecdsa_curve_name_with_octal_new
 *
 * Creates a new public key given an octal string, length and type
 *
 */

int
_libssh2_ecdsa_curve_name_with_octal_new(libssh2_ecdsa_ctx ** ec_ctx,
     const unsigned char *k,
     size_t k_len, libssh2_curve_type curve)
{

    int ret = 0;
    const EC_GROUP *ec_group = NULL;
    EC_KEY *ec_key = EC_KEY_new_by_curve_name(curve);
    EC_POINT *point = NULL;

    if(ec_key) {
        ec_group = EC_KEY_get0_group(ec_key);
        point = EC_POINT_new(ec_group);
        ret = EC_POINT_oct2point(ec_group, point, k, k_len, NULL);
        ret = EC_KEY_set_public_key(ec_key, point);

        if(point)
            EC_POINT_free(point);

        if(ec_ctx)
            *ec_ctx = ec_key;
    }

    return (ret == 1) ? 0 : -1;
}

#define LIBSSH2_ECDSA_VERIFY(digest_type)                               \
    do {                                                                \
        unsigned char hash[SHA##digest_type##_DIGEST_LENGTH];           \
        libssh2_sha##digest_type(m, m_len, hash);                       \
        ret = ECDSA_do_verify(hash, SHA##digest_type##_DIGEST_LENGTH,   \
                              ecdsa_sig, ec_key);                       \
    } while(0)

int
_libssh2_ecdsa_verify(libssh2_ecdsa_ctx * ctx,
                      const unsigned char *r, size_t r_len,
                      const unsigned char *s, size_t s_len,
                      const unsigned char *m, size_t m_len)
{
    int ret = 0;
    EC_KEY *ec_key = (EC_KEY*)ctx;
    libssh2_curve_type type = _libssh2_ecdsa_get_curve_type(ec_key);

#ifdef HAVE_OPAQUE_STRUCTS
    ECDSA_SIG *ecdsa_sig = ECDSA_SIG_new();
    BIGNUM *pr = BN_new();
    BIGNUM *ps = BN_new();

    BN_bin2bn(r, (int) r_len, pr);
    BN_bin2bn(s, (int) s_len, ps);
    ECDSA_SIG_set0(ecdsa_sig, pr, ps);

#else
    ECDSA_SIG ecdsa_sig_;
    ECDSA_SIG *ecdsa_sig = &ecdsa_sig_;
    ecdsa_sig_.r = BN_new();
    BN_bin2bn(r, (int) r_len, ecdsa_sig_.r);
    ecdsa_sig_.s = BN_new();
    BN_bin2bn(s, (int) s_len, ecdsa_sig_.s);
#endif

    if(type == LIBSSH2_EC_CURVE_NISTP256) {
        LIBSSH2_ECDSA_VERIFY(256);
    }
    else if(type == LIBSSH2_EC_CURVE_NISTP384) {
        LIBSSH2_ECDSA_VERIFY(384);
    }
    else if(type == LIBSSH2_EC_CURVE_NISTP521) {
        LIBSSH2_ECDSA_VERIFY(512);
    }

#ifdef HAVE_OPAQUE_STRUCTS
    if(ecdsa_sig)
        ECDSA_SIG_free(ecdsa_sig);
#else
    BN_clear_free(ecdsa_sig_.s);
    BN_clear_free(ecdsa_sig_.r);
#endif

    return (ret == 1) ? 0 : -1;
}

#endif /* LIBSSH2_ECDSA */

int
_libssh2_cipher_init(_libssh2_cipher_ctx * h,
                     _libssh2_cipher_type(algo),
                     unsigned char *iv, unsigned char *secret, int encrypt)
{
#ifdef HAVE_OPAQUE_STRUCTS
#if LIBSSH2_AES_GCM
    const int is_aesgcm = (algo == EVP_aes_128_gcm) ||
                          (algo == EVP_aes_256_gcm);
#endif /* LIBSSH2_AES_GCM */
    int rc;

    *h = EVP_CIPHER_CTX_new();
    rc = !EVP_CipherInit(*h, algo(), secret, iv, encrypt);
#if LIBSSH2_AES_GCM
    if(is_aesgcm) {
        /* Sets both fixed and invocation_counter parts of IV */
        rc |= !EVP_CIPHER_CTX_ctrl(*h, EVP_CTRL_AEAD_SET_IV_FIXED, -1, iv);
    }
#endif /* LIBSSH2_AES_GCM */

    return rc;
#else
# if LIBSSH2_AES_GCM
#  error AES-GCM is only supported with opaque structs in use
# endif /* LIBSSH2_AES_GCM */
    EVP_CIPHER_CTX_init(h);
    return !EVP_CipherInit(h, algo(), secret, iv, encrypt);
#endif
}

int
_libssh2_cipher_crypt(_libssh2_cipher_ctx * ctx,
                      _libssh2_cipher_type(algo),
                      int encrypt, unsigned char *block, size_t blocksize,
                      int firstlast)
{
    unsigned char buf[EVP_MAX_BLOCK_LENGTH];
    int ret = 1;
    int rc = 1;

#if LIBSSH2_AES_GCM
    const int is_aesgcm = (algo == EVP_aes_128_gcm) ||
                          (algo == EVP_aes_256_gcm);
    char lastiv[1];
#else
    const int is_aesgcm = 0;
#endif /* LIBSSH2_AES_GCM */
    /* length of AES-GCM Authentication Tag */
    const int authlen = is_aesgcm ? 16 : 0;
    /* length of AAD, only on the first block */
    const int aadlen = (is_aesgcm && IS_FIRST(firstlast)) ? 4 : 0;
    /* size of AT, if present */
    const int authenticationtag = IS_LAST(firstlast) ? authlen : 0;
    /* length to encrypt */
    const int cryptlen = (unsigned int)blocksize - aadlen - authenticationtag;

    (void)algo;

    assert(blocksize <= sizeof(buf));
    assert(cryptlen >= 0);

#if LIBSSH2_AES_GCM
    /* First block */
    if(IS_FIRST(firstlast)) {
        /* Increments invocation_counter portion of IV */
        if(is_aesgcm) {
            ret = EVP_CIPHER_CTX_ctrl(*ctx, EVP_CTRL_GCM_IV_GEN, 1, lastiv);
        }

        if(aadlen) {
            /* Include the 4 byte packet length as AAD */
            ret = EVP_Cipher(*ctx, NULL, block, aadlen);
        }
    }

    /* Last portion of block to encrypt/decrypt */
    if(IS_LAST(firstlast)) {
        if(is_aesgcm && !encrypt) {
            /* set tag on decryption */
            ret = EVP_CIPHER_CTX_ctrl(*ctx, EVP_CTRL_GCM_SET_TAG, authlen,
                                      block + blocksize - authlen);
        }
    }
#else
    (void)encrypt;
    (void)firstlast;
#endif /* LIBSSH2_AES_GCM */

    if(cryptlen > 0) {
#ifdef HAVE_OPAQUE_STRUCTS
        ret = EVP_Cipher(*ctx, buf + aadlen, block + aadlen, cryptlen);
#else
        ret = EVP_Cipher(ctx, buf + aadlen, block + aadlen, cryptlen);
#endif
    }

#if (defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3) || \
     defined(LIBSSH2_WOLFSSL)
    if(ret != -1)
#else
    if(ret >= 1)
#endif
    {
        rc = 0;
        if(IS_LAST(firstlast)) {
            /* This is the last block.
               encrypt: compute tag, if applicable
               decrypt: verify tag, if applicable
               in!=NULL is equivalent to EVP_CipherUpdate
               in==NULL is equivalent to EVP_CipherFinal */
#ifdef HAVE_OPAQUE_STRUCTS
            ret = EVP_Cipher(*ctx, NULL, NULL, 0); /* final */
#else
            ret = EVP_Cipher(ctx, NULL, NULL, 0); /* final */
#endif
            if(ret < 0) {
                ret = 0;
            }
            else {
                ret = 1;
#if LIBSSH2_AES_GCM
                if(is_aesgcm && encrypt) {
                    /* write the Authentication Tag a.k.a. MAC at the end
                       of the block */
                    assert(authenticationtag == authlen);
                    ret = EVP_CIPHER_CTX_ctrl(*ctx, EVP_CTRL_GCM_GET_TAG,
                            authlen, block + blocksize - authenticationtag);
                }
#endif /* LIBSSH2_AES_GCM */
            }
        }
        /* Copy en/decrypted data back to the caller.
           The first aadlen should not be touched because they weren't
           encrypted and are unmodified. */
        memcpy(block + aadlen, buf + aadlen, cryptlen);
        rc = !ret;
    }

    /* TODO: the return code should distinguish between decryption errors and
       invalid MACs */
    return rc;
}

void _libssh2_openssl_crypto_init(void)
{
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
    !defined(LIBRESSL_VERSION_NUMBER)
#ifndef OPENSSL_NO_ENGINE
    ENGINE_load_builtin_engines();
    ENGINE_register_all_complete();
#endif
#else
    OpenSSL_add_all_algorithms();
    OpenSSL_add_all_ciphers();
    OpenSSL_add_all_digests();
#ifndef OPENSSL_NO_ENGINE
    ENGINE_load_builtin_engines();
    ENGINE_register_all_complete();
#endif
#endif
}

void _libssh2_openssl_crypto_exit(void)
{
}

#if LIBSSH2_RSA || LIBSSH2_DSA || LIBSSH2_ECDSA || LIBSSH2_ED25519
/* TODO: Optionally call a passphrase callback specified by the
 * calling program
 */
static int
passphrase_cb(char *buf, int size, int rwflag, char *passphrase)
{
    int passphrase_len = (int) strlen(passphrase);

    (void)rwflag;

    if(passphrase_len > (size - 1)) {
        passphrase_len = size - 1;
    }
    memcpy(buf, passphrase, passphrase_len);
    buf[passphrase_len] = '\0';

    return passphrase_len;
}

typedef void * (*pem_read_bio_func)(BIO *, void **, pem_password_cb *,
                                    void *u);

static int
read_private_key_from_memory(void **key_ctx,
                             pem_read_bio_func read_private_key,
                             const char *filedata,
                             size_t filedata_len,
                             unsigned const char *passphrase)
{
    BIO * bp;

    *key_ctx = NULL;

#if OPENSSL_VERSION_NUMBER >= 0x1000200fL
    bp = BIO_new_mem_buf(filedata, (int)filedata_len);
#else
    bp = BIO_new_mem_buf((char *)filedata, (int)filedata_len);
#endif
    if(!bp) {
        return -1;
    }

    *key_ctx = read_private_key(bp, NULL, (pem_password_cb *) passphrase_cb,
                                (void *) passphrase);

    BIO_free(bp);
    return (*key_ctx) ? 0 : -1;
}
#endif

#if LIBSSH2_RSA || LIBSSH2_DSA || LIBSSH2_ECDSA
static int
read_private_key_from_file(void **key_ctx,
                           pem_read_bio_func read_private_key,
                           const char *filename,
                           unsigned const char *passphrase)
{
    BIO * bp;

    *key_ctx = NULL;

    bp = BIO_new_file(filename, "r");
    if(!bp) {
        return -1;
    }

    *key_ctx = read_private_key(bp, NULL, (pem_password_cb *) passphrase_cb,
                                (void *) passphrase);

    BIO_free(bp);
    return (*key_ctx) ? 0 : -1;
}
#endif

#if LIBSSH2_RSA
int
_libssh2_rsa_new_private_frommemory(libssh2_rsa_ctx ** rsa,
                                    LIBSSH2_SESSION * session,
                                    const char *filedata, size_t filedata_len,
                                    unsigned const char *passphrase)
{
    int rc;

    pem_read_bio_func read_rsa =
        (pem_read_bio_func) &PEM_read_bio_RSAPrivateKey;

    _libssh2_init_if_needed();

    rc = read_private_key_from_memory((void **) rsa, read_rsa,
                                      filedata, filedata_len, passphrase);

    if(rc) {
        rc = read_openssh_private_key_from_memory((void **)rsa, session,
                        "ssh-rsa", filedata, filedata_len, passphrase);
    }

    return rc;
}

static unsigned char *
gen_publickey_from_rsa(LIBSSH2_SESSION *session, RSA *rsa,
                       size_t *key_len)
{
    int            e_bytes, n_bytes;
    unsigned long  len;
    unsigned char *key;
    unsigned char *p;
    const BIGNUM * e;
    const BIGNUM * n;
#ifdef HAVE_OPAQUE_STRUCTS
    RSA_get0_key(rsa, &n, &e, NULL);
#else
    e = rsa->e;
    n = rsa->n;
#endif
    e_bytes = BN_num_bytes(e) + 1;
    n_bytes = BN_num_bytes(n) + 1;

    /* Key form is "ssh-rsa" + e + n. */
    len = 4 + 7 + 4 + e_bytes + 4 + n_bytes;

    key = LIBSSH2_ALLOC(session, len);
    if(!key) {
        return NULL;
    }

    /* Process key encoding. */
    p = key;

    _libssh2_htonu32(p, 7);  /* Key type. */
    p += 4;
    memcpy(p, "ssh-rsa", 7);
    p += 7;

    p = write_bn(p, e, e_bytes);
    p = write_bn(p, n, n_bytes);

    *key_len = (size_t)(p - key);
    return key;
}

static int
gen_publickey_from_rsa_evp(LIBSSH2_SESSION *session,
                           unsigned char **method,
                           size_t *method_len,
                           unsigned char **pubkeydata,
                           size_t *pubkeydata_len,
                           EVP_PKEY *pk)
{
    RSA*           rsa = NULL;
    unsigned char *key;
    unsigned char *method_buf = NULL;
    size_t  key_len;

    _libssh2_debug((session,
                   LIBSSH2_TRACE_AUTH,
                   "Computing public key from RSA private key envelope"));

    rsa = EVP_PKEY_get1_RSA(pk);
    if(!rsa) {
        /* Assume memory allocation error... what else could it be ? */
        goto __alloc_error;
    }

    method_buf = LIBSSH2_ALLOC(session, 7);  /* ssh-rsa. */
    if(!method_buf) {
        goto __alloc_error;
    }

    key = gen_publickey_from_rsa(session, rsa, &key_len);
    if(!key) {
        goto __alloc_error;
    }
    RSA_free(rsa);

    memcpy(method_buf, "ssh-rsa", 7);
    *method         = method_buf;
    *method_len     = 7;
    *pubkeydata     = key;
    *pubkeydata_len = key_len;
    return 0;

__alloc_error:
    if(rsa) {
        RSA_free(rsa);
    }
    if(method_buf) {
        LIBSSH2_FREE(session, method_buf);
    }

    return _libssh2_error(session,
                          LIBSSH2_ERROR_ALLOC,
                          "Unable to allocate memory for private key data");
}

static int _libssh2_rsa_new_additional_parameters(RSA *rsa)
{
    BN_CTX *ctx = NULL;
    BIGNUM *aux = NULL;
    BIGNUM *dmp1 = NULL;
    BIGNUM *dmq1 = NULL;
    const BIGNUM *p = NULL;
    const BIGNUM *q = NULL;
    const BIGNUM *d = NULL;
    int rc = 0;

#ifdef HAVE_OPAQUE_STRUCTS
    RSA_get0_key(rsa, NULL, NULL, &d);
    RSA_get0_factors(rsa, &p, &q);
#else
    d = (*rsa).d;
    p = (*rsa).p;
    q = (*rsa).q;
#endif

    ctx = BN_CTX_new();
    if(!ctx)
        return -1;

    aux = BN_new();
    if(!aux) {
        rc = -1;
        goto out;
    }

    dmp1 = BN_new();
    if(!dmp1) {
        rc = -1;
        goto out;
    }

    dmq1 = BN_new();
    if(!dmq1) {
        rc = -1;
        goto out;
    }

    if((BN_sub(aux, q, BN_value_one()) == 0) ||
        (BN_mod(dmq1, d, aux, ctx) == 0) ||
        (BN_sub(aux, p, BN_value_one()) == 0) ||
        (BN_mod(dmp1, d, aux, ctx) == 0)) {
        rc = -1;
        goto out;
    }

#ifdef HAVE_OPAQUE_STRUCTS
    RSA_set0_crt_params(rsa, dmp1, dmq1, NULL);
#else
    (*rsa).dmp1 = dmp1;
    (*rsa).dmq1 = dmq1;
#endif

out:
    if(aux)
        BN_clear_free(aux);
    BN_CTX_free(ctx);

    if(rc) {
        if(dmp1)
            BN_clear_free(dmp1);
        if(dmq1)
            BN_clear_free(dmq1);
    }

    return rc;
}

static int
gen_publickey_from_rsa_openssh_priv_data(LIBSSH2_SESSION *session,
                                         struct string_buf *decrypted,
                                         unsigned char **method,
                                         size_t *method_len,
                                         unsigned char **pubkeydata,
                                         size_t *pubkeydata_len,
                                         libssh2_rsa_ctx **rsa_ctx)
{
    int rc = 0;
    size_t nlen, elen, dlen, plen, qlen, coefflen, commentlen;
    unsigned char *n, *e, *d, *p, *q, *coeff, *comment;
    RSA *rsa = NULL;

    _libssh2_debug((session,
                   LIBSSH2_TRACE_AUTH,
                   "Computing RSA keys from private key data"));

    /* public key data */
    if(_libssh2_get_bignum_bytes(decrypted, &n, &nlen)) {
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "RSA no n");
        return -1;
    }

    if(_libssh2_get_bignum_bytes(decrypted, &e, &elen)) {
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "RSA no e");
        return -1;
    }

    /* private key data */
    if(_libssh2_get_bignum_bytes(decrypted, &d, &dlen)) {
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "RSA no d");
        return -1;
    }

    if(_libssh2_get_bignum_bytes(decrypted, &coeff, &coefflen)) {
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "RSA no coeff");
        return -1;
    }

    if(_libssh2_get_bignum_bytes(decrypted, &p, &plen)) {
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "RSA no p");
        return -1;
    }

    if(_libssh2_get_bignum_bytes(decrypted, &q, &qlen)) {
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "RSA no q");
        return -1;
    }

    if(_libssh2_get_string(decrypted, &comment, &commentlen)) {
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "RSA no comment");
        return -1;
    }

    rc = _libssh2_rsa_new(&rsa,
                          e, (unsigned long)elen,
                          n, (unsigned long)nlen,
                          d, (unsigned long)dlen,
                          p, (unsigned long)plen,
                          q, (unsigned long)qlen,
                          NULL, 0, NULL, 0,
                          coeff, (unsigned long)coefflen);
    if(rc) {
        _libssh2_debug((session,
                       LIBSSH2_TRACE_AUTH,
                       "Could not create RSA private key"));
        goto fail;
    }

    if(rsa)
        rc = _libssh2_rsa_new_additional_parameters(rsa);

    if(rsa && pubkeydata && method) {
        EVP_PKEY *pk = EVP_PKEY_new();
        EVP_PKEY_set1_RSA(pk, rsa);

        rc = gen_publickey_from_rsa_evp(session, method, method_len,
                                        pubkeydata, pubkeydata_len,
                                        pk);

        if(pk)
            EVP_PKEY_free(pk);
    }

    if(rsa_ctx)
        *rsa_ctx = rsa;
    else
        RSA_free(rsa);

    return rc;

fail:

    if(rsa)
        RSA_free(rsa);

    return _libssh2_error(session,
                          LIBSSH2_ERROR_ALLOC,
                          "Unable to allocate memory for private key data");
}

static int
_libssh2_rsa_new_openssh_private(libssh2_rsa_ctx ** rsa,
                                 LIBSSH2_SESSION * session,
                                 const char *filename,
                                 unsigned const char *passphrase)
{
    FILE *fp;
    int rc;
    unsigned char *buf = NULL;
    struct string_buf *decrypted = NULL;

    if(!session) {
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "Session is required");
        return -1;
    }

    _libssh2_init_if_needed();

    fp = fopen(filename, "r");
    if(!fp) {
        _libssh2_error(session, LIBSSH2_ERROR_FILE,
                       "Unable to open OpenSSH RSA private key file");
        return -1;
    }

    rc = _libssh2_openssh_pem_parse(session, passphrase, fp, &decrypted);
    fclose(fp);
    if(rc) {
        return rc;
    }

    /* We have a new key file, now try and parse it using supported types  */
    rc = _libssh2_get_string(decrypted, &buf, NULL);

    if(rc || !buf) {
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "Public key type in decrypted key data not found");
        return -1;
    }

    if(strcmp("ssh-rsa", (const char *)buf) == 0) {
        rc = gen_publickey_from_rsa_openssh_priv_data(session, decrypted,
                                                      NULL, 0,
                                                      NULL, 0, rsa);
    }
    else {
        rc = -1;
    }

    if(decrypted)
        _libssh2_string_buf_free(session, decrypted);

    return rc;
}

int
_libssh2_rsa_new_private(libssh2_rsa_ctx ** rsa,
                         LIBSSH2_SESSION * session,
                         const char *filename, unsigned const char *passphrase)
{
    int rc;

    pem_read_bio_func read_rsa =
        (pem_read_bio_func) &PEM_read_bio_RSAPrivateKey;

    _libssh2_init_if_needed();

    rc = read_private_key_from_file((void **) rsa, read_rsa,
                                    filename, passphrase);

    if(rc) {
        rc = _libssh2_rsa_new_openssh_private(rsa, session,
                                              filename, passphrase);
    }

    return rc;
}
#endif

#if LIBSSH2_DSA
int
_libssh2_dsa_new_private_frommemory(libssh2_dsa_ctx ** dsa,
                                    LIBSSH2_SESSION * session,
                                    const char *filedata, size_t filedata_len,
                                    unsigned const char *passphrase)
{
    int rc;

    pem_read_bio_func read_dsa =
        (pem_read_bio_func) &PEM_read_bio_DSAPrivateKey;

    _libssh2_init_if_needed();

    rc = read_private_key_from_memory((void **)dsa, read_dsa,
                                      filedata, filedata_len,
                                      passphrase);

    if(rc) {
        rc = read_openssh_private_key_from_memory((void **)dsa, session,
                                                  "ssh-dsa",
                                                  filedata, filedata_len,
                                                  passphrase);
    }

    return rc;
}

static unsigned char *
gen_publickey_from_dsa(LIBSSH2_SESSION* session, DSA *dsa,
                       size_t *key_len)
{
    int            p_bytes, q_bytes, g_bytes, k_bytes;
    unsigned long  len;
    unsigned char *key;
    unsigned char *p;

    const BIGNUM * p_bn;
    const BIGNUM * q;
    const BIGNUM * g;
    const BIGNUM * pub_key;
#ifdef HAVE_OPAQUE_STRUCTS
    DSA_get0_pqg(dsa, &p_bn, &q, &g);
#else
    p_bn = dsa->p;
    q = dsa->q;
    g = dsa->g;
#endif

#ifdef HAVE_OPAQUE_STRUCTS
    DSA_get0_key(dsa, &pub_key, NULL);
#else
    pub_key = dsa->pub_key;
#endif
    p_bytes = BN_num_bytes(p_bn) + 1;
    q_bytes = BN_num_bytes(q) + 1;
    g_bytes = BN_num_bytes(g) + 1;
    k_bytes = BN_num_bytes(pub_key) + 1;

    /* Key form is "ssh-dss" + p + q + g + pub_key. */
    len = 4 + 7 + 4 + p_bytes + 4 + q_bytes + 4 + g_bytes + 4 + k_bytes;

    key = LIBSSH2_ALLOC(session, len);
    if(!key) {
        return NULL;
    }

    /* Process key encoding. */
    p = key;

    _libssh2_htonu32(p, 7);  /* Key type. */
    p += 4;
    memcpy(p, "ssh-dss", 7);
    p += 7;

    p = write_bn(p, p_bn, p_bytes);
    p = write_bn(p, q, q_bytes);
    p = write_bn(p, g, g_bytes);
    p = write_bn(p, pub_key, k_bytes);

    *key_len = (size_t)(p - key);
    return key;
}

static int
gen_publickey_from_dsa_evp(LIBSSH2_SESSION *session,
                           unsigned char **method,
                           size_t *method_len,
                           unsigned char **pubkeydata,
                           size_t *pubkeydata_len,
                           EVP_PKEY *pk)
{
    DSA*           dsa = NULL;
    unsigned char *key;
    unsigned char *method_buf = NULL;
    size_t  key_len;

    _libssh2_debug((session,
                   LIBSSH2_TRACE_AUTH,
                   "Computing public key from DSA private key envelope"));

    dsa = EVP_PKEY_get1_DSA(pk);
    if(!dsa) {
        /* Assume memory allocation error... what else could it be ? */
        goto __alloc_error;
    }

    method_buf = LIBSSH2_ALLOC(session, 7);  /* ssh-dss. */
    if(!method_buf) {
        goto __alloc_error;
    }

    key = gen_publickey_from_dsa(session, dsa, &key_len);
    if(!key) {
        goto __alloc_error;
    }
    DSA_free(dsa);

    memcpy(method_buf, "ssh-dss", 7);
    *method         = method_buf;
    *method_len     = 7;
    *pubkeydata     = key;
    *pubkeydata_len = key_len;
    return 0;

__alloc_error:
    if(dsa) {
        DSA_free(dsa);
    }
    if(method_buf) {
        LIBSSH2_FREE(session, method_buf);
    }

    return _libssh2_error(session,
                          LIBSSH2_ERROR_ALLOC,
                          "Unable to allocate memory for private key data");
}

static int
gen_publickey_from_dsa_openssh_priv_data(LIBSSH2_SESSION *session,
                                         struct string_buf *decrypted,
                                         unsigned char **method,
                                         size_t *method_len,
                                         unsigned char **pubkeydata,
                                         size_t *pubkeydata_len,
                                         libssh2_dsa_ctx **dsa_ctx)
{
    int rc = 0;
    size_t plen, qlen, glen, pub_len, priv_len;
    unsigned char *p, *q, *g, *pub_key, *priv_key;
    DSA *dsa = NULL;

    _libssh2_debug((session,
                   LIBSSH2_TRACE_AUTH,
                   "Computing DSA keys from private key data"));

    if(_libssh2_get_bignum_bytes(decrypted, &p, &plen)) {
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "DSA no p");
        return -1;
    }

    if(_libssh2_get_bignum_bytes(decrypted, &q, &qlen)) {
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "DSA no q");
        return -1;
    }

    if(_libssh2_get_bignum_bytes(decrypted, &g, &glen)) {
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "DSA no g");
        return -1;
    }

    if(_libssh2_get_bignum_bytes(decrypted, &pub_key, &pub_len)) {
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "DSA no public key");
        return -1;
    }

    if(_libssh2_get_bignum_bytes(decrypted, &priv_key, &priv_len)) {
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "DSA no private key");
        return -1;
    }

    rc = _libssh2_dsa_new(&dsa,
                          p, (unsigned long)plen,
                          q, (unsigned long)qlen,
                          g, (unsigned long)glen,
                          pub_key, (unsigned long)pub_len,
                          priv_key, (unsigned long)priv_len);
    if(rc) {
        _libssh2_debug((session,
                       LIBSSH2_ERROR_PROTO,
                       "Could not create DSA private key"));
        goto fail;
    }

    if(dsa && pubkeydata && method) {
        EVP_PKEY *pk = EVP_PKEY_new();
        EVP_PKEY_set1_DSA(pk, dsa);

        rc = gen_publickey_from_dsa_evp(session, method, method_len,
                                        pubkeydata, pubkeydata_len,
                                        pk);

        if(pk)
            EVP_PKEY_free(pk);
    }

    if(dsa_ctx)
        *dsa_ctx = dsa;
    else
        DSA_free(dsa);

    return rc;

fail:

    if(dsa)
        DSA_free(dsa);

    return _libssh2_error(session,
                          LIBSSH2_ERROR_ALLOC,
                          "Unable to allocate memory for private key data");
}

static int
_libssh2_dsa_new_openssh_private(libssh2_dsa_ctx ** dsa,
                                 LIBSSH2_SESSION * session,
                                 const char *filename,
                                 unsigned const char *passphrase)
{
    FILE *fp;
    int rc;
    unsigned char *buf = NULL;
    struct string_buf *decrypted = NULL;

    if(!session) {
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "Session is required");
        return -1;
    }

    _libssh2_init_if_needed();

    fp = fopen(filename, "r");
    if(!fp) {
        _libssh2_error(session, LIBSSH2_ERROR_FILE,
                       "Unable to open OpenSSH DSA private key file");
        return -1;
    }

    rc = _libssh2_openssh_pem_parse(session, passphrase, fp, &decrypted);
    fclose(fp);
    if(rc) {
        return rc;
    }

    /* We have a new key file, now try and parse it using supported types  */
    rc = _libssh2_get_string(decrypted, &buf, NULL);

    if(rc || !buf) {
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "Public key type in decrypted key data not found");
        return -1;
    }

    if(strcmp("ssh-dss", (const char *)buf) == 0) {
        rc = gen_publickey_from_dsa_openssh_priv_data(session, decrypted,
                                                      NULL, 0,
                                                      NULL, 0, dsa);
    }
    else {
        rc = -1;
    }

    if(decrypted)
        _libssh2_string_buf_free(session, decrypted);

    return rc;
}

int
_libssh2_dsa_new_private(libssh2_dsa_ctx ** dsa,
                         LIBSSH2_SESSION * session,
                         const char *filename, unsigned const char *passphrase)
{
    int rc;

    pem_read_bio_func read_dsa =
        (pem_read_bio_func) &PEM_read_bio_DSAPrivateKey;

    _libssh2_init_if_needed();

    rc = read_private_key_from_file((void **) dsa, read_dsa,
                                    filename, passphrase);

    if(rc) {
        rc = _libssh2_dsa_new_openssh_private(dsa, session,
                                              filename, passphrase);
    }

    return rc;
}
#endif /* LIBSSH_DSA */

#if LIBSSH2_ECDSA

int
_libssh2_ecdsa_new_private_frommemory(libssh2_ecdsa_ctx ** ec_ctx,
                                      LIBSSH2_SESSION * session,
                                      const char *filedata,
                                      size_t filedata_len,
                                      unsigned const char *passphrase)
{
    int rc;

    pem_read_bio_func read_ec =
        (pem_read_bio_func) &PEM_read_bio_ECPrivateKey;

    _libssh2_init_if_needed();

    rc = read_private_key_from_memory((void **) ec_ctx, read_ec,
                                      filedata, filedata_len,
                                      passphrase);

    if(rc) {
        rc = read_openssh_private_key_from_memory((void **)ec_ctx, session,
                                                  "ssh-ecdsa",
                                                  filedata, filedata_len,
                                                  passphrase);
    }

    return rc;
}

int _libssh2_ecdsa_new_private_frommemory_sk(libssh2_ecdsa_ctx ** ec_ctx,
                                             unsigned char *flags,
                                             const char **application,
                                             const unsigned char **key_handle,
                                             size_t *handle_len,
                                             LIBSSH2_SESSION * session,
                                             const char *filedata,
                                             size_t filedata_len,
                                             unsigned const char *passphrase)
{
    int algorithm;
    return _libssh2_sk_pub_openssh_keyfilememory(session,
                                                 (void **)ec_ctx,
                                          "sk-ecdsa-sha2-nistp256@openssh.com",
                                                 NULL,
                                                 NULL,
                                                 NULL,
                                                 NULL,
                                                 &algorithm,
                                                 flags,
                                                 application,
                                                 key_handle,
                                                 handle_len,
                                                 filedata,
                                                 filedata_len,
                                                 passphrase);
}

#endif /* LIBSSH2_ECDSA */


#if LIBSSH2_ED25519

int
_libssh2_curve25519_new(LIBSSH2_SESSION *session,
                        unsigned char **out_public_key,
                        unsigned char **out_private_key)
{
    EVP_PKEY *key = NULL;
    EVP_PKEY_CTX *pctx = NULL;
    unsigned char *priv = NULL, *pub = NULL;
    size_t privLen, pubLen;
    int rc = -1;

    pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_X25519, NULL);
    if(!pctx)
        return -1;

    if(EVP_PKEY_keygen_init(pctx) != 1 ||
       EVP_PKEY_keygen(pctx, &key) != 1) {
        goto clean_exit;
    }

    if(out_private_key) {
        privLen = LIBSSH2_ED25519_KEY_LEN;
        priv = LIBSSH2_ALLOC(session, privLen);
        if(!priv)
            goto clean_exit;

        if(EVP_PKEY_get_raw_private_key(key, priv, &privLen) != 1 ||
           privLen != LIBSSH2_ED25519_KEY_LEN) {
            goto clean_exit;
        }

        *out_private_key = priv;
        priv = NULL;
    }

    if(out_public_key) {
        pubLen = LIBSSH2_ED25519_KEY_LEN;
        pub = LIBSSH2_ALLOC(session, pubLen);
        if(!pub)
            goto clean_exit;

        if(EVP_PKEY_get_raw_public_key(key, pub, &pubLen) != 1 ||
           pubLen != LIBSSH2_ED25519_KEY_LEN) {
            goto clean_exit;
        }

        *out_public_key = pub;
        pub = NULL;
    }

    /* success */
    rc = 0;

clean_exit:

    if(pctx)
        EVP_PKEY_CTX_free(pctx);
    if(key)
        EVP_PKEY_free(key);
    if(priv)
        LIBSSH2_FREE(session, priv);
    if(pub)
        LIBSSH2_FREE(session, pub);

    return rc;
}


static int
gen_publickey_from_ed_evp(LIBSSH2_SESSION *session,
                          unsigned char **method,
                          size_t *method_len,
                          unsigned char **pubkeydata,
                          size_t *pubkeydata_len,
                          EVP_PKEY *pk)
{
    const char methodName[] = "ssh-ed25519";
    unsigned char *methodBuf = NULL;
    size_t rawKeyLen = 0;
    unsigned char *keyBuf = NULL;
    size_t bufLen = 0;
    unsigned char *bufPos = NULL;

    _libssh2_debug((session, LIBSSH2_TRACE_AUTH,
                   "Computing public key from ED private key envelope"));

    methodBuf = LIBSSH2_ALLOC(session, sizeof(methodName) - 1);
    if(!methodBuf) {
        _libssh2_error(session, LIBSSH2_ERROR_ALLOC,
                       "Unable to allocate memory for private key data");
        goto fail;
    }
    memcpy(methodBuf, methodName, sizeof(methodName) - 1);

    if(EVP_PKEY_get_raw_public_key(pk, NULL, &rawKeyLen) != 1) {
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "EVP_PKEY_get_raw_public_key failed");
        goto fail;
    }

    /* Key form is: type_len(4) + type(11) + pub_key_len(4) + pub_key(32). */
    bufLen = 4 + sizeof(methodName) - 1  + 4 + rawKeyLen;
    bufPos = keyBuf = LIBSSH2_ALLOC(session, bufLen);
    if(!keyBuf) {
        _libssh2_error(session, LIBSSH2_ERROR_ALLOC,
                       "Unable to allocate memory for private key data");
        goto fail;
    }

    _libssh2_store_str(&bufPos, methodName, sizeof(methodName) - 1);
    _libssh2_store_u32(&bufPos, (uint32_t) rawKeyLen);

    if(EVP_PKEY_get_raw_public_key(pk, bufPos, &rawKeyLen) != 1) {
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "EVP_PKEY_get_raw_public_key failed");
        goto fail;
    }

    *method         = methodBuf;
    *method_len     = sizeof(methodName) - 1;
    *pubkeydata     = keyBuf;
    *pubkeydata_len = bufLen;
    return 0;

fail:
    if(methodBuf)
        LIBSSH2_FREE(session, methodBuf);
    if(keyBuf)
        LIBSSH2_FREE(session, keyBuf);
    return -1;
}


static int
gen_publickey_from_ed25519_openssh_priv_data(LIBSSH2_SESSION *session,
                                             struct string_buf *decrypted,
                                             unsigned char **method,
                                             size_t *method_len,
                                             unsigned char **pubkeydata,
                                             size_t *pubkeydata_len,
                                             libssh2_ed25519_ctx **out_ctx)
{
    libssh2_ed25519_ctx *ctx = NULL;
    unsigned char *method_buf = NULL;
    unsigned char *key = NULL;
    int i, ret = 0;
    unsigned char *pub_key, *priv_key, *buf;
    size_t key_len = 0, tmp_len = 0;
    unsigned char *p;

    _libssh2_debug((session,
                   LIBSSH2_TRACE_AUTH,
                   "Computing ED25519 keys from private key data"));

    if(_libssh2_get_string(decrypted, &pub_key, &tmp_len) ||
       tmp_len != LIBSSH2_ED25519_KEY_LEN) {
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "Wrong public key length");
        return -1;
    }

    if(_libssh2_get_string(decrypted, &priv_key, &tmp_len) ||
       tmp_len != LIBSSH2_ED25519_PRIVATE_KEY_LEN) {
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "Wrong private key length");
        ret = -1;
        goto clean_exit;
    }

    /* first 32 bytes of priv_key is the private key, the last 32 bytes are
       the public key */
    ctx = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL,
                                       (const unsigned char *)priv_key,
                                       LIBSSH2_ED25519_KEY_LEN);

    /* comment */
    if(_libssh2_get_string(decrypted, &buf, &tmp_len)) {
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "Unable to read comment");
        ret = -1;
        goto clean_exit;
    }

    if(tmp_len > 0) {
        unsigned char *comment = LIBSSH2_CALLOC(session, tmp_len + 1);
        if(comment) {
            memcpy(comment, buf, tmp_len);
            memcpy(comment + tmp_len, "\0", 1);

            _libssh2_debug((session, LIBSSH2_TRACE_AUTH, "Key comment: %s",
                           comment));

            LIBSSH2_FREE(session, comment);
        }
    }

    /* Padding */
    i = 1;
    while(decrypted->dataptr < decrypted->data + decrypted->len) {
        if(*decrypted->dataptr != i) {
            _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                           "Wrong padding");
            ret = -1;
            goto clean_exit;
        }
        i++;
        decrypted->dataptr++;
    }

    if(ret == 0) {
        _libssh2_debug((session,
                       LIBSSH2_TRACE_AUTH,
                       "Computing public key from ED25519 "
                       "private key envelope"));

        method_buf = LIBSSH2_ALLOC(session, 11);  /* ssh-ed25519. */
        if(!method_buf) {
            _libssh2_error(session, LIBSSH2_ERROR_ALLOC,
                           "Unable to allocate memory for ED25519 key");
            goto clean_exit;
        }

        /* Key form is: type_len(4) + type(11) + pub_key_len(4) +
           pub_key(32). */
        key_len = LIBSSH2_ED25519_KEY_LEN + 19;
        key = LIBSSH2_CALLOC(session, key_len);
        if(!key) {
            _libssh2_error(session, LIBSSH2_ERROR_ALLOC,
                           "Unable to allocate memory for ED25519 key");
            goto clean_exit;
        }

        p = key;

        _libssh2_store_str(&p, "ssh-ed25519", 11);
        _libssh2_store_str(&p, (const char *)pub_key, LIBSSH2_ED25519_KEY_LEN);

        memcpy(method_buf, "ssh-ed25519", 11);

        if(method)
            *method = method_buf;
        else
            LIBSSH2_FREE(session, method_buf);

        if(method_len)
            *method_len = 11;

        if(pubkeydata)
            *pubkeydata = key;
        else
            LIBSSH2_FREE(session, key);

        if(pubkeydata_len)
            *pubkeydata_len = key_len;

        if(out_ctx)
            *out_ctx = ctx;
        else if(ctx)
            _libssh2_ed25519_free(ctx);

        return 0;
    }

clean_exit:

    if(ctx)
        _libssh2_ed25519_free(ctx);

    if(method_buf)
        LIBSSH2_FREE(session, method_buf);

    if(key)
        LIBSSH2_FREE(session, key);

    return -1;
}

static int
gen_publickey_from_sk_ed25519_openssh_priv_data(LIBSSH2_SESSION *session,
                                                struct string_buf *decrypted,
                                                unsigned char **method,
                                                size_t *method_len,
                                                unsigned char **pubkeydata,
                                                size_t *pubkeydata_len,
                                                unsigned char *flags,
                                                const char **application,
                                              const unsigned char **key_handle,
                                                size_t *handle_len,
                                                libssh2_ed25519_ctx **out_ctx)
{
    const char *key_type = "sk-ssh-ed25519@openssh.com";

    libssh2_ed25519_ctx *ctx = NULL;
    unsigned char *method_buf = NULL;
    unsigned char *key = NULL;
    int ret = 0;
    unsigned char *pub_key, *app;
    size_t key_len = 0, app_len = 0, tmp_len = 0;
    unsigned char *p;

    _libssh2_debug((session,
                   LIBSSH2_TRACE_AUTH,
                   "Computing sk-ED25519 keys from private key data"));

    if(_libssh2_get_string(decrypted, &pub_key, &tmp_len) ||
       tmp_len != LIBSSH2_ED25519_KEY_LEN) {
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "Wrong public key length");
        return -1;
    }

    if(_libssh2_get_string(decrypted, &app, &app_len)) {
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "No SK application.");
        return -1;
    }

    if(flags && _libssh2_get_byte(decrypted, flags)) {
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "No SK flags.");
        return -1;
    }

    if(key_handle && handle_len) {
        unsigned char *handle = NULL;
        if(_libssh2_get_string(decrypted, &handle, handle_len)) {
            _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                           "No SK key_handle.");
            return -1;
        }

        if(*handle_len > 0) {
            *key_handle = LIBSSH2_ALLOC(session, *handle_len);

            if(key_handle) {
                memcpy((void *)*key_handle, handle, *handle_len);
            }
        }
    }

    ctx = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL,
                                      (const unsigned char *)pub_key,
                                      LIBSSH2_ED25519_KEY_LEN);

    if(ret == 0) {
        _libssh2_debug((session,
                       LIBSSH2_TRACE_AUTH,
                       "Computing public key from ED25519 "
                       "private key envelope"));

        /* sk-ssh-ed25519@openssh.com. */
        method_buf = LIBSSH2_ALLOC(session, strlen(key_type));
        if(!method_buf) {
            _libssh2_error(session, LIBSSH2_ERROR_ALLOC,
                           "Unable to allocate memory for ED25519 key");
            goto clean_exit;
        }

        /* Key form is: type_len(4) + type(26) + pub_key_len(4) +
           pub_key(32) + application_len(4) + application(X). */
        key_len = LIBSSH2_ED25519_KEY_LEN + 38 + app_len;
        key = LIBSSH2_CALLOC(session, key_len);
        if(!key) {
            _libssh2_error(session, LIBSSH2_ERROR_ALLOC,
                           "Unable to allocate memory for ED25519 key");
            goto clean_exit;
        }

        p = key;

        _libssh2_store_str(&p, key_type, strlen(key_type));
        _libssh2_store_str(&p, (const char *)pub_key, LIBSSH2_ED25519_KEY_LEN);
        _libssh2_store_str(&p, (const char *)app, app_len);

        if(application && app_len > 0) {
            *application = (const char *)LIBSSH2_ALLOC(session, app_len + 1);
            _libssh2_explicit_zero((void *)*application, app_len + 1);
            memcpy((void *)*application, app, app_len);
        }

        memcpy(method_buf, key_type, strlen(key_type));

        if(method)
            *method = method_buf;
        else
            LIBSSH2_FREE(session, method_buf);

        if(method_len)
            *method_len = strlen(key_type);

        if(pubkeydata)
            *pubkeydata = key;
        else if(key)
            LIBSSH2_FREE(session, key);

        if(pubkeydata_len)
            *pubkeydata_len = key_len;

        if(out_ctx)
            *out_ctx = ctx;
        else if(ctx)
            _libssh2_ed25519_free(ctx);

        return 0;
    }

clean_exit:

    if(ctx)
        _libssh2_ed25519_free(ctx);

    if(method_buf)
        LIBSSH2_FREE(session, method_buf);

    if(key)
        LIBSSH2_FREE(session, key);

    if(application && *application) {
        LIBSSH2_FREE(session, (void *)application);
        *application = NULL;
    }

    if(key_handle && *key_handle) {
        LIBSSH2_FREE(session, (void *)key_handle);
        *key_handle = NULL;
    }

    return -1;
}


int
_libssh2_ed25519_new_private(libssh2_ed25519_ctx ** ed_ctx,
                             LIBSSH2_SESSION * session,
                             const char *filename, const uint8_t *passphrase)
{
    int rc;
    FILE *fp;
    unsigned char *buf;
    struct string_buf *decrypted = NULL;
    libssh2_ed25519_ctx *ctx = NULL;

    if(!session) {
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "Session is required");
        return -1;
    }

    _libssh2_init_if_needed();

    fp = fopen(filename, "r");
    if(!fp) {
        _libssh2_error(session, LIBSSH2_ERROR_FILE,
                       "Unable to open ED25519 private key file");
        return -1;
    }

    rc = _libssh2_openssh_pem_parse(session, passphrase, fp, &decrypted);
    fclose(fp);
    if(rc) {
        return rc;
    }

    /* We have a new key file, now try and parse it using supported types  */
    rc = _libssh2_get_string(decrypted, &buf, NULL);

    if(rc || !buf) {
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "Public key type in decrypted key data not found");
        return -1;
    }

    if(strcmp("ssh-ed25519", (const char *)buf) == 0) {
        rc = gen_publickey_from_ed25519_openssh_priv_data(session,
                                                          decrypted,
                                                          NULL,
                                                          NULL,
                                                          NULL,
                                                          NULL,
                                                          &ctx);
    }
    else {
        rc = -1;
    }

    if(decrypted)
        _libssh2_string_buf_free(session, decrypted);

    if(rc == 0) {
        if(ed_ctx)
            *ed_ctx = ctx;
        else if(ctx)
            _libssh2_ed25519_free(ctx);
    }

    return rc;
}

int
_libssh2_ed25519_new_private_sk(libssh2_ed25519_ctx **ed_ctx,
                                unsigned char *flags,
                                const char **application,
                                const unsigned char **key_handle,
                                size_t *handle_len,
                                LIBSSH2_SESSION *session,
                                const char *filename,
                                const uint8_t *passphrase)
{
    int rc;
    FILE *fp;
    unsigned char *buf;
    struct string_buf *decrypted = NULL;
    libssh2_ed25519_ctx *ctx = NULL;

    if(!session) {
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "Session is required");
        return -1;
    }

    _libssh2_init_if_needed();

    fp = fopen(filename, "r");
    if(!fp) {
        _libssh2_error(session, LIBSSH2_ERROR_FILE,
                       "Unable to open ED25519 SK private key file");
        return -1;
    }

    rc = _libssh2_openssh_pem_parse(session, passphrase, fp, &decrypted);
    fclose(fp);
    if(rc) {
        return rc;
    }

    /* We have a new key file, now try and parse it using supported types  */
    rc = _libssh2_get_string(decrypted, &buf, NULL);

    if(rc || !buf) {
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "Public key type in decrypted key data not found");
        return -1;
    }

    if(strcmp("sk-ssh-ed25519@openssh.com", (const char *)buf) == 0) {
        rc = gen_publickey_from_sk_ed25519_openssh_priv_data(session,
                                                             decrypted,
                                                             NULL,
                                                             NULL,
                                                             NULL,
                                                             NULL,
                                                             flags,
                                                             application,
                                                             key_handle,
                                                             handle_len,
                                                             &ctx);
    }
    else {
        rc = -1;
    }

    if(decrypted)
        _libssh2_string_buf_free(session, decrypted);

    if(rc == 0) {
        if(ed_ctx)
            *ed_ctx = ctx;
        else if(ctx)
            _libssh2_ed25519_free(ctx);
    }

    return rc;
}

int
_libssh2_ed25519_new_private_frommemory(libssh2_ed25519_ctx ** ed_ctx,
                                        LIBSSH2_SESSION * session,
                                        const char *filedata,
                                        size_t filedata_len,
                                        unsigned const char *passphrase)
{
    libssh2_ed25519_ctx *ctx = NULL;

    _libssh2_init_if_needed();

    if(read_private_key_from_memory((void **)&ctx,
                                    (pem_read_bio_func)
                                    &PEM_read_bio_PrivateKey,
                                    filedata, filedata_len,
                                    passphrase) == 0) {
        if(EVP_PKEY_id(ctx) != EVP_PKEY_ED25519) {
            _libssh2_ed25519_free(ctx);
            return _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                                  "Private key is not an ED25519 key");
        }

        *ed_ctx = ctx;
        return 0;
    }

    return read_openssh_private_key_from_memory((void **)ed_ctx, session,
                                                "ssh-ed25519",
                                                filedata, filedata_len,
                                                passphrase);
}

int
_libssh2_ed25519_new_private_frommemory_sk(libssh2_ed25519_ctx **ed_ctx,
                                           unsigned char *flags,
                                           const char **application,
                                           const unsigned char **key_handle,
                                           size_t *handle_len,
                                           LIBSSH2_SESSION *session,
                                           const char *filedata,
                                           size_t filedata_len,
                                           unsigned const char *passphrase)
{
    int algorithm;
    return _libssh2_sk_pub_openssh_keyfilememory(session,
                                                 (void **)ed_ctx,
                                                 "sk-ssh-ed25519@openssh.com",
                                                 NULL,
                                                 NULL,
                                                 NULL,
                                                 NULL,
                                                 &algorithm,
                                                 flags,
                                                 application,
                                                 key_handle,
                                                 handle_len,
                                                 filedata,
                                                 filedata_len,
                                                 passphrase);
}

int
_libssh2_ed25519_new_public(libssh2_ed25519_ctx ** ed_ctx,
                            LIBSSH2_SESSION * session,
                            const unsigned char *raw_pub_key,
                            const size_t key_len)
{
    libssh2_ed25519_ctx *ctx = NULL;

    if(!ed_ctx)
        return -1;

    ctx = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL,
                                      raw_pub_key, key_len);
    if(!ctx)
        return _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                              "could not create ED25519 public key");

    if(ed_ctx)
        *ed_ctx = ctx;
    else if(ctx)
        _libssh2_ed25519_free(ctx);

    return 0;
}
#endif /* LIBSSH2_ED25519 */


#if LIBSSH2_RSA
int
_libssh2_rsa_sha2_sign(LIBSSH2_SESSION * session,
                       libssh2_rsa_ctx * rsactx,
                       const unsigned char *hash,
                       size_t hash_len,
                       unsigned char **signature, size_t *signature_len)
{
    int ret;
    unsigned char *sig;
    unsigned int sig_len;

    sig_len = RSA_size(rsactx);
    sig = LIBSSH2_ALLOC(session, sig_len);

    if(!sig) {
        return -1;
    }

    if(hash_len == SHA_DIGEST_LENGTH)
        ret = RSA_sign(NID_sha1,
                       hash, (unsigned int) hash_len, sig, &sig_len, rsactx);
    else if(hash_len == SHA256_DIGEST_LENGTH)
        ret = RSA_sign(NID_sha256,
                       hash, (unsigned int) hash_len, sig, &sig_len, rsactx);
    else if(hash_len == SHA512_DIGEST_LENGTH)
        ret = RSA_sign(NID_sha512,
                       hash, (unsigned int) hash_len, sig, &sig_len, rsactx);
    else {
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "Unsupported hash digest length");
        ret = -1;
    }

    if(!ret) {
        LIBSSH2_FREE(session, sig);
        return -1;
    }

    *signature = sig;
    *signature_len = sig_len;

    return 0;
}

#if LIBSSH2_RSA_SHA1
int
_libssh2_rsa_sha1_sign(LIBSSH2_SESSION * session,
                       libssh2_rsa_ctx * rsactx,
                       const unsigned char *hash,
                       size_t hash_len,
                       unsigned char **signature, size_t *signature_len)
{
    return _libssh2_rsa_sha2_sign(session, rsactx, hash, hash_len,
                                  signature, signature_len);
}
#endif
#endif

#if LIBSSH2_DSA
int
_libssh2_dsa_sha1_sign(libssh2_dsa_ctx * dsactx,
                       const unsigned char *hash,
                       unsigned long hash_len, unsigned char *signature)
{
    DSA_SIG *sig;
    const BIGNUM * r;
    const BIGNUM * s;
    int r_len, s_len;

    (void)hash_len;

    sig = DSA_do_sign(hash, SHA_DIGEST_LENGTH, dsactx);
    if(!sig) {
        return -1;
    }

#ifdef HAVE_OPAQUE_STRUCTS
    DSA_SIG_get0(sig, &r, &s);
#else
    r = sig->r;
    s = sig->s;
#endif
    r_len = BN_num_bytes(r);
    if(r_len < 1 || r_len > 20) {
        DSA_SIG_free(sig);
        return -1;
    }
    s_len = BN_num_bytes(s);
    if(s_len < 1 || s_len > 20) {
        DSA_SIG_free(sig);
        return -1;
    }

    memset(signature, 0, 40);

    BN_bn2bin(r, signature + (20 - r_len));
    BN_bn2bin(s, signature + 20 + (20 - s_len));

    DSA_SIG_free(sig);

    return 0;
}
#endif /* LIBSSH_DSA */

#if LIBSSH2_ECDSA

int
_libssh2_ecdsa_sign(LIBSSH2_SESSION * session, libssh2_ecdsa_ctx * ec_ctx,
                    const unsigned char *hash, unsigned long hash_len,
                    unsigned char **signature, size_t *signature_len)
{
    int r_len, s_len;
    int rc = 0;
    size_t out_buffer_len = 0;
    unsigned char *sp;
    const BIGNUM *pr = NULL, *ps = NULL;
    unsigned char *temp_buffer = NULL;
    unsigned char *out_buffer = NULL;

    ECDSA_SIG *sig = ECDSA_do_sign(hash, (int) hash_len, ec_ctx);
    if(!sig)
        return -1;
#ifdef HAVE_OPAQUE_STRUCTS
    ECDSA_SIG_get0(sig, &pr, &ps);
#else
    pr = sig->r;
    ps = sig->s;
#endif

    r_len = BN_num_bytes(pr) + 1;
    s_len = BN_num_bytes(ps) + 1;

    temp_buffer = malloc(r_len + s_len + 8);
    if(!temp_buffer) {
        rc = -1;
        goto clean_exit;
    }

    sp = temp_buffer;
    sp = write_bn(sp, pr, r_len);
    sp = write_bn(sp, ps, s_len);

    out_buffer_len = (size_t)(sp - temp_buffer);

    out_buffer = LIBSSH2_CALLOC(session, out_buffer_len);
    if(!out_buffer) {
        rc = -1;
        goto clean_exit;
    }

    memcpy(out_buffer, temp_buffer, out_buffer_len);

    *signature = out_buffer;
    *signature_len = out_buffer_len;

clean_exit:

    if(temp_buffer)
        free(temp_buffer);

    if(sig)
        ECDSA_SIG_free(sig);

    return rc;
}
#endif /* LIBSSH2_ECDSA */

int
_libssh2_sha1_init(libssh2_sha1_ctx *ctx)
{
#ifdef HAVE_OPAQUE_STRUCTS
    *ctx = EVP_MD_CTX_new();

    if(!*ctx)
        return 0;

    if(EVP_DigestInit(*ctx, EVP_get_digestbyname("sha1")))
        return 1;

    EVP_MD_CTX_free(*ctx);
    *ctx = NULL;

    return 0;
#else
    EVP_MD_CTX_init(ctx);
    return EVP_DigestInit(ctx, EVP_get_digestbyname("sha1"));
#endif
}

int
_libssh2_sha1(const unsigned char *message, size_t len,
              unsigned char *out)
{
#ifdef HAVE_OPAQUE_STRUCTS
    EVP_MD_CTX * ctx = EVP_MD_CTX_new();

    if(!ctx)
        return 1; /* error */

    if(EVP_DigestInit(ctx, EVP_get_digestbyname("sha1"))) {
        EVP_DigestUpdate(ctx, message, len);
        EVP_DigestFinal(ctx, out, NULL);
        EVP_MD_CTX_free(ctx);
        return 0; /* success */
    }
    EVP_MD_CTX_free(ctx);
#else
    EVP_MD_CTX ctx;

    EVP_MD_CTX_init(&ctx);
    if(EVP_DigestInit(&ctx, EVP_get_digestbyname("sha1"))) {
        EVP_DigestUpdate(&ctx, message, len);
        EVP_DigestFinal(&ctx, out, NULL);
        return 0; /* success */
    }
#endif
    return 1; /* error */
}

int
_libssh2_sha256_init(libssh2_sha256_ctx *ctx)
{
#ifdef HAVE_OPAQUE_STRUCTS
    *ctx = EVP_MD_CTX_new();

    if(!*ctx)
        return 0;

    if(EVP_DigestInit(*ctx, EVP_get_digestbyname("sha256")))
        return 1;

    EVP_MD_CTX_free(*ctx);
    *ctx = NULL;

    return 0;
#else
    EVP_MD_CTX_init(ctx);
    return EVP_DigestInit(ctx, EVP_get_digestbyname("sha256"));
#endif
}

int
_libssh2_sha256(const unsigned char *message, size_t len,
                unsigned char *out)
{
#ifdef HAVE_OPAQUE_STRUCTS
    EVP_MD_CTX * ctx = EVP_MD_CTX_new();

    if(!ctx)
        return 1; /* error */

    if(EVP_DigestInit(ctx, EVP_get_digestbyname("sha256"))) {
        EVP_DigestUpdate(ctx, message, len);
        EVP_DigestFinal(ctx, out, NULL);
        EVP_MD_CTX_free(ctx);
        return 0; /* success */
    }
    EVP_MD_CTX_free(ctx);
#else
    EVP_MD_CTX ctx;

    EVP_MD_CTX_init(&ctx);
    if(EVP_DigestInit(&ctx, EVP_get_digestbyname("sha256"))) {
        EVP_DigestUpdate(&ctx, message, len);
        EVP_DigestFinal(&ctx, out, NULL);
        return 0; /* success */
    }
#endif
    return 1; /* error */
}

int
_libssh2_sha384_init(libssh2_sha384_ctx *ctx)
{
#ifdef HAVE_OPAQUE_STRUCTS
    *ctx = EVP_MD_CTX_new();

    if(!*ctx)
        return 0;

    if(EVP_DigestInit(*ctx, EVP_get_digestbyname("sha384")))
        return 1;

    EVP_MD_CTX_free(*ctx);
    *ctx = NULL;

    return 0;
#else
    EVP_MD_CTX_init(ctx);
    return EVP_DigestInit(ctx, EVP_get_digestbyname("sha384"));
#endif
}

int
_libssh2_sha384(const unsigned char *message, size_t len,
                unsigned char *out)
{
#ifdef HAVE_OPAQUE_STRUCTS
    EVP_MD_CTX * ctx = EVP_MD_CTX_new();

    if(!ctx)
        return 1; /* error */

    if(EVP_DigestInit(ctx, EVP_get_digestbyname("sha384"))) {
        EVP_DigestUpdate(ctx, message, len);
        EVP_DigestFinal(ctx, out, NULL);
        EVP_MD_CTX_free(ctx);
        return 0; /* success */
    }
    EVP_MD_CTX_free(ctx);
#else
    EVP_MD_CTX ctx;

    EVP_MD_CTX_init(&ctx);
    if(EVP_DigestInit(&ctx, EVP_get_digestbyname("sha384"))) {
        EVP_DigestUpdate(&ctx, message, len);
        EVP_DigestFinal(&ctx, out, NULL);
        return 0; /* success */
    }
#endif
    return 1; /* error */
}

int
_libssh2_sha512_init(libssh2_sha512_ctx *ctx)
{
#ifdef HAVE_OPAQUE_STRUCTS
    *ctx = EVP_MD_CTX_new();

    if(!*ctx)
        return 0;

    if(EVP_DigestInit(*ctx, EVP_get_digestbyname("sha512")))
        return 1;

    EVP_MD_CTX_free(*ctx);
    *ctx = NULL;

    return 0;
#else
    EVP_MD_CTX_init(ctx);
    return EVP_DigestInit(ctx, EVP_get_digestbyname("sha512"));
#endif
}

int
_libssh2_sha512(const unsigned char *message, size_t len,
                unsigned char *out)
{
#ifdef HAVE_OPAQUE_STRUCTS
    EVP_MD_CTX * ctx = EVP_MD_CTX_new();

    if(!ctx)
        return 1; /* error */

    if(EVP_DigestInit(ctx, EVP_get_digestbyname("sha512"))) {
        EVP_DigestUpdate(ctx, message, len);
        EVP_DigestFinal(ctx, out, NULL);
        EVP_MD_CTX_free(ctx);
        return 0; /* success */
    }
    EVP_MD_CTX_free(ctx);
#else
    EVP_MD_CTX ctx;

    EVP_MD_CTX_init(&ctx);
    if(EVP_DigestInit(&ctx, EVP_get_digestbyname("sha512"))) {
        EVP_DigestUpdate(&ctx, message, len);
        EVP_DigestFinal(&ctx, out, NULL);
        return 0; /* success */
    }
#endif
    return 1; /* error */
}

int
_libssh2_md5_init(libssh2_md5_ctx *ctx)
{
    /* MD5 digest is not supported in OpenSSL FIPS mode
     * Trying to init it will result in a latent OpenSSL error:
     * "digital envelope routines:FIPS_DIGESTINIT:disabled for fips"
     * So, just return 0 in FIPS mode
     */
#if OPENSSL_VERSION_NUMBER >= 0x000907000L && \
    defined(OPENSSL_VERSION_MAJOR) && \
    OPENSSL_VERSION_MAJOR < 3 && \
    !defined(LIBRESSL_VERSION_NUMBER)

    if(FIPS_mode())
        return 0;
#endif

#ifdef HAVE_OPAQUE_STRUCTS
    *ctx = EVP_MD_CTX_new();

    if(!*ctx)
        return 0;

    if(EVP_DigestInit(*ctx, EVP_get_digestbyname("md5")))
        return 1;

    EVP_MD_CTX_free(*ctx);
    *ctx = NULL;

    return 0;
#else
    EVP_MD_CTX_init(ctx);
    return EVP_DigestInit(ctx, EVP_get_digestbyname("md5"));
#endif
}

#if LIBSSH2_ECDSA

static int
gen_publickey_from_ec_evp(LIBSSH2_SESSION *session,
                          unsigned char **method,
                          size_t *method_len,
                          unsigned char **pubkeydata,
                          size_t *pubkeydata_len,
                          int is_sk,
                          EVP_PKEY *pk)
{
    int rc = 0;
    EC_KEY *ec = NULL;
    unsigned char *p;
    unsigned char *method_buf = NULL;
    unsigned char *key;
    size_t  key_len = 0;
    unsigned char *octal_value = NULL;
    size_t octal_len;
    const EC_POINT *public_key;
    const EC_GROUP *group;
    BN_CTX *bn_ctx;
    libssh2_curve_type type;

    _libssh2_debug((session,
                   LIBSSH2_TRACE_AUTH,
                   "Computing public key from EC private key envelope"));

    bn_ctx = BN_CTX_new();
    if(!bn_ctx)
        return -1;

    ec = EVP_PKEY_get1_EC_KEY(pk);
    if(!ec) {
        rc = -1;
        goto clean_exit;
    }

    public_key = EC_KEY_get0_public_key(ec);
    group = EC_KEY_get0_group(ec);
    type = _libssh2_ecdsa_get_curve_type(ec);

    if(is_sk)
        *method_len = 34;
    else
        *method_len = 19;

    method_buf = LIBSSH2_ALLOC(session, *method_len);
    if(!method_buf) {
        return _libssh2_error(session, LIBSSH2_ERROR_ALLOC,
                              "out of memory");
    }

    if(is_sk)
        memcpy(method_buf, "sk-ecdsa-sha2-nistp256@openssh.com", *method_len);
    else if(type == LIBSSH2_EC_CURVE_NISTP256)
        memcpy(method_buf, "ecdsa-sha2-nistp256", *method_len);
    else if(type == LIBSSH2_EC_CURVE_NISTP384)
        memcpy(method_buf, "ecdsa-sha2-nistp384", *method_len);
    else if(type == LIBSSH2_EC_CURVE_NISTP521)
        memcpy(method_buf, "ecdsa-sha2-nistp521", *method_len);
    else {
        _libssh2_debug((session,
                       LIBSSH2_TRACE_ERROR,
                       "Unsupported EC private key type"));
        rc = -1;
        goto clean_exit;
    }

    /* get length */
    octal_len = EC_POINT_point2oct(group, public_key,
                                   POINT_CONVERSION_UNCOMPRESSED,
                                   NULL, 0, bn_ctx);
    if(octal_len > EC_MAX_POINT_LEN) {
        rc = -1;
        goto clean_exit;
    }

    octal_value = malloc(octal_len);
    if(!octal_value) {
        rc = -1;
        goto clean_exit;
    }

    /* convert to octal */
    if(EC_POINT_point2oct(group, public_key, POINT_CONVERSION_UNCOMPRESSED,
       octal_value, octal_len, bn_ctx) != octal_len) {
        rc = -1;
        goto clean_exit;
    }

    /* Key form is: type_len(4) + type(method_len) + domain_len(4) + domain(8)
       + pub_key_len(4) + pub_key(~65). */
    key_len = 4 + *method_len + 4 + 8 + 4 + octal_len;
    key = LIBSSH2_ALLOC(session, key_len);
    if(!key) {
        rc = -1;
        goto clean_exit;
    }

    /* Process key encoding. */
    p = key;

    /* Key type */
    _libssh2_store_str(&p, (const char *)method_buf, *method_len);

    /* Name domain */
    if(is_sk) {
        _libssh2_store_str(&p, "nistp256", 8);
    }
    else {
        _libssh2_store_str(&p, (const char *)method_buf + 11, 8);
    }

    /* Public key */
    _libssh2_store_str(&p, (const char *)octal_value, octal_len);

    *method         = method_buf;
    *pubkeydata     = key;
    *pubkeydata_len = key_len;

clean_exit:

    if(ec)
        EC_KEY_free(ec);

    if(bn_ctx) {
        BN_CTX_free(bn_ctx);
    }

    if(octal_value)
        free(octal_value);

    if(rc == 0)
        return 0;

    if(method_buf)
        LIBSSH2_FREE(session, method_buf);

    return -1;
}

static int
gen_publickey_from_ecdsa_openssh_priv_data(LIBSSH2_SESSION *session,
                                           libssh2_curve_type curve_type,
                                           struct string_buf *decrypted,
                                           unsigned char **method,
                                           size_t *method_len,
                                           unsigned char **pubkeydata,
                                           size_t *pubkeydata_len,
                                           libssh2_ecdsa_ctx **ec_ctx)
{
    int rc = 0;
    size_t curvelen, exponentlen, pointlen;
    unsigned char *curve, *exponent, *point_buf;
    EC_KEY *ec_key = NULL;
    BIGNUM *bn_exponent;

    _libssh2_debug((session,
                   LIBSSH2_TRACE_AUTH,
                   "Computing ECDSA keys from private key data"));

    if(_libssh2_get_string(decrypted, &curve, &curvelen) ||
        curvelen == 0) {
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "ECDSA no curve");
        return -1;
    }

    if(_libssh2_get_string(decrypted, &point_buf, &pointlen)) {
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "ECDSA no point");
        return -1;
    }

    if(_libssh2_get_bignum_bytes(decrypted, &exponent, &exponentlen)) {
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "ECDSA no exponent");
        return -1;
    }

    rc = _libssh2_ecdsa_curve_name_with_octal_new(&ec_key,
                                                  point_buf, pointlen,
                                                  curve_type);
    if(rc) {
        rc = -1;
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "ECDSA could not create key");
        goto fail;
    }

    bn_exponent = BN_new();
    if(!bn_exponent) {
        rc = -1;
        _libssh2_error(session, LIBSSH2_ERROR_ALLOC,
                       "Unable to allocate memory for private key data");
        goto fail;
    }

    BN_bin2bn(exponent, (int) exponentlen, bn_exponent);
    rc = (EC_KEY_set_private_key(ec_key, bn_exponent) != 1);

    if(rc == 0 && ec_key && pubkeydata && method) {
        EVP_PKEY *pk = EVP_PKEY_new();
        EVP_PKEY_set1_EC_KEY(pk, ec_key);

        rc = gen_publickey_from_ec_evp(session, method, method_len,
                                       pubkeydata, pubkeydata_len,
                                       0, pk);

        if(pk)
            EVP_PKEY_free(pk);
    }

    if(ec_ctx)
        *ec_ctx = ec_key;
    else
        EC_KEY_free(ec_key);

    return rc;

fail:
    if(ec_key)
        EC_KEY_free(ec_key);

    return rc;
}

static int
gen_publickey_from_sk_ecdsa_openssh_priv_data(LIBSSH2_SESSION *session,
                                              struct string_buf *decrypted,
                                              unsigned char **method,
                                              size_t *method_len,
                                              unsigned char **pubkeydata,
                                              size_t *pubkeydata_len,
                                              uint8_t *flags,
                                              const char **application,
                                              const unsigned char **key_handle,
                                              size_t *handle_len,
                                              libssh2_ecdsa_ctx **ec_ctx)
{
    int rc = 0;
    size_t curvelen, pointlen, key_len, app_len;
    unsigned char *curve, *point_buf, *p, *key, *app;
    EC_KEY *ec_key = NULL;

    _libssh2_debug((session,
                   LIBSSH2_TRACE_AUTH,
                   "Extracting ECDSA-SK public key"));

    if(_libssh2_get_string(decrypted, &curve, &curvelen) ||
        curvelen == 0) {
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "ECDSA no curve");
        return -1;
    }

    if(_libssh2_get_string(decrypted, &point_buf, &pointlen)) {
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "ECDSA no point");
        return -1;
    }

    rc = _libssh2_ecdsa_curve_name_with_octal_new(&ec_key,
                                                  point_buf, pointlen,
                                                  LIBSSH2_EC_CURVE_NISTP256);
    if(rc) {
        rc = -1;
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "ECDSA could not create key");
        goto fail;
    }

    if(_libssh2_get_string(decrypted, &app, &app_len)) {
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "No SK application.");
        goto fail;
    }

    if(flags && _libssh2_get_byte(decrypted, flags)) {
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "No SK flags.");
        goto fail;
    }

    if(key_handle && handle_len) {
        unsigned char *handle = NULL;
        if(_libssh2_get_string(decrypted, &handle, handle_len)) {
            _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                           "No SK key_handle.");
            goto fail;
        }

        if(*handle_len > 0) {
            *key_handle = LIBSSH2_ALLOC(session, *handle_len);

            if(*key_handle) {
                memcpy((void *)*key_handle, handle, *handle_len);
            }
        }
    }

    if(rc == 0 && ec_key && pubkeydata && method) {
        EVP_PKEY *pk = EVP_PKEY_new();
        EVP_PKEY_set1_EC_KEY(pk, ec_key);

        rc = gen_publickey_from_ec_evp(session, method, method_len,
                                       pubkeydata, pubkeydata_len,
                                       1, pk);

        if(pk)
            EVP_PKEY_free(pk);
    }

    if(rc == 0 && pubkeydata) {
        key_len = *pubkeydata_len + app_len + 4;
        key = LIBSSH2_ALLOC(session, key_len);

        if(!key) {
            rc = -1;
            goto fail;
        }

        p = key + *pubkeydata_len;

        memcpy(key, *pubkeydata, *pubkeydata_len);
        _libssh2_store_str(&p, (const char *)app, app_len);

        if(application && app_len > 0) {
            *application = (const char *)LIBSSH2_ALLOC(session, app_len + 1);
            _libssh2_explicit_zero((void *)*application, app_len + 1);
            memcpy((void *)*application, app, app_len);
        }

        LIBSSH2_FREE(session, *pubkeydata);
        *pubkeydata_len = key_len;

        if(pubkeydata)
            *pubkeydata = key;
        else if(key)
            LIBSSH2_FREE(session, key);
    }

    if(ec_ctx)
        *ec_ctx = ec_key;
    else
        EC_KEY_free(ec_key);

    return rc;

fail:
    if(ec_key)
        EC_KEY_free(ec_key);

    if(application && *application) {
        LIBSSH2_FREE(session, (void *)application);
        *application = NULL;
    }

    if(key_handle && *key_handle) {
        LIBSSH2_FREE(session, (void *)key_handle);
        *key_handle = NULL;
    }

    return rc;
}


static int
_libssh2_ecdsa_new_openssh_private(libssh2_ecdsa_ctx ** ec_ctx,
                                   LIBSSH2_SESSION * session,
                                   const char *filename,
                                   unsigned const char *passphrase)
{
    FILE *fp;
    int rc;
    unsigned char *buf = NULL;
    libssh2_curve_type type;
    struct string_buf *decrypted = NULL;

    if(!session) {
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "Session is required");
        return -1;
    }

    _libssh2_init_if_needed();

    fp = fopen(filename, "r");
    if(!fp) {
        _libssh2_error(session, LIBSSH2_ERROR_FILE,
                       "Unable to open OpenSSH ECDSA private key file");
        return -1;
    }

    rc = _libssh2_openssh_pem_parse(session, passphrase, fp, &decrypted);
    fclose(fp);
    if(rc) {
        return rc;
    }

    /* We have a new key file, now try and parse it using supported types  */
    rc = _libssh2_get_string(decrypted, &buf, NULL);

    if(rc || !buf) {
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "Public key type in decrypted key data not found");
        return -1;
    }

    rc = _libssh2_ecdsa_curve_type_from_name((const char *)buf, &type);

    if(rc == 0) {
        rc = gen_publickey_from_ecdsa_openssh_priv_data(session, type,
                                                        decrypted, NULL, 0,
                                                        NULL, 0, ec_ctx);
    }
    else {
        rc = -1;
    }

    if(decrypted)
        _libssh2_string_buf_free(session, decrypted);

    return rc;
}

static int
_libssh2_ecdsa_new_openssh_private_sk(libssh2_ecdsa_ctx ** ec_ctx,
                                      uint8_t *flags,
                                      const char **application,
                                      const unsigned char **key_handle,
                                      size_t *handle_len,
                                      LIBSSH2_SESSION * session,
                                      const char *filename,
                                      unsigned const char *passphrase)
{
    FILE *fp;
    int rc;
    unsigned char *buf = NULL;
    struct string_buf *decrypted = NULL;

    if(!session) {
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "Session is required");
        return -1;
    }

    _libssh2_init_if_needed();

    fp = fopen(filename, "r");
    if(!fp) {
        _libssh2_error(session, LIBSSH2_ERROR_FILE,
                       "Unable to open OpenSSH ECDSA private key file");
        return -1;
    }

    rc = _libssh2_openssh_pem_parse(session, passphrase, fp, &decrypted);
    fclose(fp);
    if(rc) {
        return rc;
    }

    /* We have a new key file, now try and parse it using supported types  */
    rc = _libssh2_get_string(decrypted, &buf, NULL);

    if(rc || !buf) {
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "Public key type in decrypted key data not found");
        return -1;
    }

    if(strcmp("sk-ecdsa-sha2-nistp256@openssh.com", (const char *)buf) == 0) {
        rc = gen_publickey_from_sk_ecdsa_openssh_priv_data(session,
                                                           decrypted,
                                                           NULL, 0,
                                                           NULL, 0,
                                                           flags,
                                                           application,
                                                           key_handle,
                                                           handle_len,
                                                           ec_ctx);
    }
    else {
        rc = -1;
    }

    if(decrypted)
        _libssh2_string_buf_free(session, decrypted);

    return rc;
}

int
_libssh2_ecdsa_new_private(libssh2_ecdsa_ctx ** ec_ctx,
       LIBSSH2_SESSION * session,
       const char *filename, unsigned const char *passphrase)
{
    int rc;

    pem_read_bio_func read_ec = (pem_read_bio_func) &PEM_read_bio_ECPrivateKey;

    _libssh2_init_if_needed();

    rc = read_private_key_from_file((void **) ec_ctx, read_ec,
      filename, passphrase);

    if(rc) {
        return _libssh2_ecdsa_new_openssh_private(ec_ctx, session,
                                                  filename, passphrase);
    }

    return rc;
}

int
_libssh2_ecdsa_new_private_sk(libssh2_ecdsa_ctx ** ec_ctx,
                              unsigned char *flags,
                              const char **application,
                              const unsigned char **key_handle,
                              size_t *handle_len,
                              LIBSSH2_SESSION * session,
                              const char *filename,
                              unsigned const char *passphrase)
{
    int rc;

    pem_read_bio_func read_ec = (pem_read_bio_func) &PEM_read_bio_ECPrivateKey;

    _libssh2_init_if_needed();

    rc = read_private_key_from_file((void **) ec_ctx, read_ec,
      filename, passphrase);

    if(rc) {
        return _libssh2_ecdsa_new_openssh_private_sk(ec_ctx,
                                                     flags,
                                                     application,
                                                     key_handle,
                                                     handle_len,
                                                     session,
                                                     filename,
                                                     passphrase);
    }

    return rc;
}


/*
 * _libssh2_ecdsa_create_key
 *
 * Creates a local private key based on input curve
 * and returns octal value and octal length
 *
 */

int
_libssh2_ecdsa_create_key(LIBSSH2_SESSION *session,
                          _libssh2_ec_key **out_private_key,
                          unsigned char **out_public_key_octal,
                          size_t *out_public_key_octal_len,
                          libssh2_curve_type curve_type)
{
    int ret = 1;
    size_t octal_len = 0;
    unsigned char octal_value[EC_MAX_POINT_LEN];
    const EC_POINT *public_key = NULL;
    EC_KEY *private_key = NULL;
    const EC_GROUP *group = NULL;

    /* create key */
    BN_CTX *bn_ctx = BN_CTX_new();
    if(!bn_ctx)
        return -1;

    private_key = EC_KEY_new_by_curve_name(curve_type);
    group = EC_KEY_get0_group(private_key);

    EC_KEY_generate_key(private_key);
    public_key = EC_KEY_get0_public_key(private_key);

    /* get length */
    octal_len = EC_POINT_point2oct(group, public_key,
                                   POINT_CONVERSION_UNCOMPRESSED,
                                   NULL, 0, bn_ctx);
    if(octal_len > EC_MAX_POINT_LEN) {
        ret = -1;
        goto clean_exit;
    }

    /* convert to octal */
    if(EC_POINT_point2oct(group, public_key, POINT_CONVERSION_UNCOMPRESSED,
       octal_value, octal_len, bn_ctx) != octal_len) {
        ret = -1;
        goto clean_exit;
    }

    if(out_private_key)
        *out_private_key = private_key;

    if(out_public_key_octal) {
        *out_public_key_octal = LIBSSH2_ALLOC(session, octal_len);
        if(!*out_public_key_octal) {
            ret = -1;
            goto clean_exit;
        }

        memcpy(*out_public_key_octal, octal_value, octal_len);
    }

    if(out_public_key_octal_len)
        *out_public_key_octal_len = octal_len;

clean_exit:

    if(bn_ctx)
        BN_CTX_free(bn_ctx);

    return (ret == 1) ? 0 : -1;
}

/* _libssh2_ecdh_gen_k
 *
 * Computes the shared secret K given a local private key,
 * remote public key and length
 */

int
_libssh2_ecdh_gen_k(_libssh2_bn **k, _libssh2_ec_key *private_key,
    const unsigned char *server_public_key, size_t server_public_key_len)
{
    int ret = 0;
    int rc;
    size_t secret_len;
    unsigned char *secret = NULL;
    const EC_GROUP *private_key_group;
    EC_POINT *server_public_key_point;

    BN_CTX *bn_ctx = BN_CTX_new();

    if(!bn_ctx)
        return -1;

    if(!k)
        return -1;

    private_key_group = EC_KEY_get0_group(private_key);

    server_public_key_point = EC_POINT_new(private_key_group);
    if(!server_public_key_point)
        return -1;

    rc = EC_POINT_oct2point(private_key_group, server_public_key_point,
                            server_public_key, server_public_key_len, bn_ctx);
    if(rc != 1) {
        ret = -1;
        goto clean_exit;
    }

    secret_len = (EC_GROUP_get_degree(private_key_group) + 7) / 8;
    secret = malloc(secret_len);
    if(!secret) {
        ret = -1;
        goto clean_exit;
    }

    secret_len = ECDH_compute_key(secret, secret_len, server_public_key_point,
                                  private_key, NULL);

    if(secret_len <= 0 || secret_len > EC_MAX_POINT_LEN) {
        ret = -1;
        goto clean_exit;
    }

    BN_bin2bn(secret, (int) secret_len, *k);

clean_exit:

    if(server_public_key_point)
        EC_POINT_free(server_public_key_point);

    if(bn_ctx)
        BN_CTX_free(bn_ctx);

    if(secret)
        free(secret);

    return ret;
}


#endif /* LIBSSH2_ECDSA */

#if LIBSSH2_ED25519

int
_libssh2_ed25519_sign(libssh2_ed25519_ctx *ctx, LIBSSH2_SESSION *session,
                      uint8_t **out_sig, size_t *out_sig_len,
                      const uint8_t *message, size_t message_len)
{
    int rc = -1;
    EVP_MD_CTX *md_ctx = EVP_MD_CTX_new();
    size_t sig_len = 0;
    unsigned char *sig = NULL;

    if(md_ctx) {
        if(EVP_DigestSignInit(md_ctx, NULL, NULL, NULL, ctx) != 1)
            goto clean_exit;
        if(EVP_DigestSign(md_ctx, NULL, &sig_len, message, message_len) != 1)
            goto clean_exit;

        if(sig_len != LIBSSH2_ED25519_SIG_LEN)
            goto clean_exit;

        sig = LIBSSH2_CALLOC(session, sig_len);
        if(!sig)
            goto clean_exit;

        rc = EVP_DigestSign(md_ctx, sig, &sig_len, message, message_len);
    }

    if(rc == 1) {
        *out_sig = sig;
        *out_sig_len = sig_len;
    }
    else {
        *out_sig_len = 0;
        *out_sig = NULL;
        LIBSSH2_FREE(session, sig);
    }

clean_exit:

    if(md_ctx)
        EVP_MD_CTX_free(md_ctx);

    return (rc == 1) ? 0 : -1;
}

int
_libssh2_curve25519_gen_k(_libssh2_bn **k,
                          uint8_t private_key[LIBSSH2_ED25519_KEY_LEN],
                          uint8_t server_public_key[LIBSSH2_ED25519_KEY_LEN])
{
    int rc = -1;
    unsigned char out_shared_key[LIBSSH2_ED25519_KEY_LEN];
    EVP_PKEY *peer_key = NULL, *server_key = NULL;
    EVP_PKEY_CTX *server_key_ctx = NULL;
    BN_CTX *bn_ctx = NULL;
    size_t out_len = 0;

    if(!k || !*k)
        return -1;

    bn_ctx = BN_CTX_new();
    if(!bn_ctx)
        return -1;

    peer_key = EVP_PKEY_new_raw_public_key(EVP_PKEY_X25519, NULL,
                                           server_public_key,
                                           LIBSSH2_ED25519_KEY_LEN);

    server_key = EVP_PKEY_new_raw_private_key(EVP_PKEY_X25519, NULL,
                                              private_key,
                                              LIBSSH2_ED25519_KEY_LEN);

    if(!peer_key || !server_key) {
        goto clean_exit;
    }

    server_key_ctx = EVP_PKEY_CTX_new(server_key, NULL);
    if(!server_key_ctx) {
        goto clean_exit;
    }

    rc = EVP_PKEY_derive_init(server_key_ctx);
    if(rc <= 0) {
        goto clean_exit;
    }

    rc = EVP_PKEY_derive_set_peer(server_key_ctx, peer_key);
    if(rc <= 0) {
        goto clean_exit;
    }

    rc = EVP_PKEY_derive(server_key_ctx, NULL, &out_len);
    if(rc <= 0) {
        goto clean_exit;
    }

    if(out_len != LIBSSH2_ED25519_KEY_LEN) {
        rc = -1;
        goto clean_exit;
    }

    rc = EVP_PKEY_derive(server_key_ctx, out_shared_key, &out_len);

    if(rc == 1 && out_len == LIBSSH2_ED25519_KEY_LEN) {
        BN_bin2bn(out_shared_key, LIBSSH2_ED25519_KEY_LEN, *k);
    }
    else {
        rc = -1;
    }

clean_exit:

    if(server_key_ctx)
        EVP_PKEY_CTX_free(server_key_ctx);
    if(peer_key)
        EVP_PKEY_free(peer_key);
    if(server_key)
        EVP_PKEY_free(server_key);
    if(bn_ctx)
        BN_CTX_free(bn_ctx);

    return (rc == 1) ? 0 : -1;
}


int
_libssh2_ed25519_verify(libssh2_ed25519_ctx *ctx, const uint8_t *s,
                        size_t s_len, const uint8_t *m, size_t m_len)
{
    int ret = -1;

    EVP_MD_CTX *md_ctx = EVP_MD_CTX_new();
    if(!md_ctx)
        return -1;

    ret = EVP_DigestVerifyInit(md_ctx, NULL, NULL, NULL, ctx);
    if(ret != 1)
        goto clean_exit;

    ret = EVP_DigestVerify(md_ctx, s, s_len, m, m_len);

clean_exit:

    EVP_MD_CTX_free(md_ctx);

    return (ret == 1) ? 0 : -1;
}

#endif /* LIBSSH2_ED25519 */

static int
_libssh2_pub_priv_openssh_keyfile(LIBSSH2_SESSION *session,
                                  unsigned char **method,
                                  size_t *method_len,
                                  unsigned char **pubkeydata,
                                  size_t *pubkeydata_len,
                                  const char *privatekey,
                                  const char *passphrase)
{
    FILE *fp;
    unsigned char *buf = NULL;
    struct string_buf *decrypted = NULL;
    int rc = 0;

    if(!session) {
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "Session is required");
        return -1;
    }

    _libssh2_init_if_needed();

    fp = fopen(privatekey, "r");
    if(!fp) {
        _libssh2_error(session, LIBSSH2_ERROR_FILE,
                       "Unable to open private key file");
        return -1;
    }

    rc = _libssh2_openssh_pem_parse(session, (const unsigned char *)passphrase,
                                    fp, &decrypted);
    fclose(fp);
    if(rc) {
        _libssh2_error(session, LIBSSH2_ERROR_FILE,
                       "Not an OpenSSH key file");
        return rc;
    }

    /* We have a new key file, now try and parse it using supported types  */
    rc = _libssh2_get_string(decrypted, &buf, NULL);

    if(rc || !buf) {
        _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                       "Public key type in decrypted key data not found");
        return -1;
    }

    rc = -1;

    /* Avoid unused variable warnings when all branches below are disabled */
    (void)method;
    (void)method_len;
    (void)pubkeydata;
    (void)pubkeydata_len;

#if LIBSSH2_ED25519
    if(strcmp("ssh-ed25519", (const char *)buf) == 0) {
        rc = gen_publickey_from_ed25519_openssh_priv_data(session, decrypted,
                                                          method, method_len,
                                                          pubkeydata,
                                                          pubkeydata_len,
                                                          NULL);
    }
#endif
#if LIBSSH2_RSA
    if(strcmp("ssh-rsa", (const char *)buf) == 0) {
        rc = gen_publickey_from_rsa_openssh_priv_data(session, decrypted,
                                                      method, method_len,
                                                      pubkeydata,
                                                      pubkeydata_len,
                                                      NULL);
    }
#endif
#if LIBSSH2_DSA
    if(strcmp("ssh-dss", (const char *)buf) == 0) {
        rc = gen_publickey_from_dsa_openssh_priv_data(session, decrypted,
                                                      method, method_len,
                                                      pubkeydata,
                                                      pubkeydata_len,
                                                      NULL);
    }
#endif
#if LIBSSH2_ECDSA
    {
        libssh2_curve_type type;

        if(_libssh2_ecdsa_curve_type_from_name((const char *)buf,
                                               &type) == 0) {
            rc = gen_publickey_from_ecdsa_openssh_priv_data(session, type,
                                                            decrypted,
                                                            method, method_len,
                                                            pubkeydata,
                                                            pubkeydata_len,
                                                            NULL);
        }
    }
#endif

    if(decrypted)
        _libssh2_string_buf_free(session, decrypted);

    if(rc) {
        _libssh2_error(session, LIBSSH2_ERROR_FILE,
                       "Unsupported OpenSSH key type");
    }

    return rc;
}

int
_libssh2_pub_priv_keyfile(LIBSSH2_SESSION *session,
                          unsigned char **method,
                          size_t *method_len,
                          unsigned char **pubkeydata,
                          size_t *pubkeydata_len,
                          const char *privatekey,
                          const char *passphrase)
{
    int       st;
    BIO*      bp;
    EVP_PKEY* pk;
    int       pktype;
    int       rc;

    _libssh2_debug((session,
                   LIBSSH2_TRACE_AUTH,
                   "Computing public key from private key file: %s",
                   privatekey));

    bp = BIO_new_file(privatekey, "r");
    if(!bp) {
        return _libssh2_error(session,
                              LIBSSH2_ERROR_FILE,
                              "Unable to extract public key from private key "
                              "file: Unable to open private key file");
    }

    BIO_reset(bp);
    pk = PEM_read_bio_PrivateKey(bp, NULL, NULL, (void *)passphrase);
    BIO_free(bp);

    if(!pk) {

        /* Try OpenSSH format */
        rc = _libssh2_pub_priv_openssh_keyfile(session,
                                               method,
                                               method_len,
                                               pubkeydata, pubkeydata_len,
                                               privatekey, passphrase);
        if(rc) {
            return _libssh2_error(session,
                                  LIBSSH2_ERROR_FILE,
                                  "Unable to extract public key "
                                  "from private key file: "
                                  "Wrong passphrase or invalid/unrecognized "
                                  "private key file format");
        }

        return 0;
    }

#ifdef HAVE_OPAQUE_STRUCTS
    pktype = EVP_PKEY_id(pk);
#else
    pktype = pk->type;
#endif

    switch(pktype) {
#if LIBSSH2_ED25519
    case EVP_PKEY_ED25519:
        st = gen_publickey_from_ed_evp(
            session, method, method_len, pubkeydata, pubkeydata_len, pk);
        break;
#endif /* LIBSSH2_ED25519 */
#if LIBSSH2_RSA
    case EVP_PKEY_RSA:
        st = gen_publickey_from_rsa_evp(
            session, method, method_len, pubkeydata, pubkeydata_len, pk);
        break;
#endif /* LIBSSH2_RSA */
#if LIBSSH2_DSA
    case EVP_PKEY_DSA:
        st = gen_publickey_from_dsa_evp(
            session, method, method_len, pubkeydata, pubkeydata_len, pk);
        break;
#endif /* LIBSSH2_DSA */
#if LIBSSH2_ECDSA
    case EVP_PKEY_EC:
        st = gen_publickey_from_ec_evp(
            session, method, method_len, pubkeydata, pubkeydata_len, 0, pk);
    break;
#endif /* LIBSSH2_ECDSA */
    default:
        st = _libssh2_error(session,
                            LIBSSH2_ERROR_FILE,
                            "Unable to extract public key "
                            "from private key file: "
                            "Unsupported private key file format");
        break;
    }

    EVP_PKEY_free(pk);
    return st;
}

static int
_libssh2_pub_priv_openssh_keyfilememory(LIBSSH2_SESSION *session,
                                        void **key_ctx,
                                        const char *key_type,
                                        unsigned char **method,
                                        size_t *method_len,
                                        unsigned char **pubkeydata,
                                        size_t *pubkeydata_len,
                                        const char *privatekeydata,
                                        size_t privatekeydata_len,
                                        unsigned const char *passphrase)
{
    int rc;
    unsigned char *buf = NULL;
    struct string_buf *decrypted = NULL;

    if(key_ctx)
        *key_ctx = NULL;

    if(!session)
        return _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                              "Session is required");

    if(key_type && (strlen(key_type) > 11 || strlen(key_type) < 7))
        return _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                              "type is invalid");

    _libssh2_init_if_needed();

    rc = _libssh2_openssh_pem_parse_memory(session, passphrase,
                                           privatekeydata,
                                           privatekeydata_len, &decrypted);

    if(rc)
        return rc;

    /* We have a new key file, now try and parse it using supported types  */
    rc = _libssh2_get_string(decrypted, &buf, NULL);

    if(rc || !buf)
        return _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                              "Public key type in decrypted "
                              "key data not found");

    rc = LIBSSH2_ERROR_FILE;

    /* Avoid unused variable warnings when all branches below are disabled */
    (void)method;
    (void)method_len;
    (void)pubkeydata;
    (void)pubkeydata_len;

#if LIBSSH2_ED25519
    if(strcmp("ssh-ed25519", (const char *)buf) == 0) {
        if(!key_type || strcmp("ssh-ed25519", key_type) == 0) {
            rc = gen_publickey_from_ed25519_openssh_priv_data(session,
                                                              decrypted,
                                                              method,
                                                              method_len,
                                                              pubkeydata,
                                                              pubkeydata_len,
                                              (libssh2_ed25519_ctx**)key_ctx);
        }
    }

    if(strcmp("sk-ssh-ed25519@openssh.com", (const char *)buf) == 0) {
        if(!key_type ||
           strcmp("sk-ssh-ed25519@openssh.com", key_type) == 0) {
            rc = gen_publickey_from_sk_ed25519_openssh_priv_data(session,
                                                                 decrypted,
                                                                 method,
                                                                 method_len,
                                                                 pubkeydata,
                                                                pubkeydata_len,
                                                                 NULL,
                                                                 NULL,
                                                                 NULL,
                                                                 NULL,
                                               (libssh2_ed25519_ctx**)key_ctx);
        }
    }
#endif
#if LIBSSH2_RSA
    if(strcmp("ssh-rsa", (const char *)buf) == 0) {
        if(!key_type || strcmp("ssh-rsa", key_type) == 0) {
            rc = gen_publickey_from_rsa_openssh_priv_data(session, decrypted,
                                                          method, method_len,
                                                          pubkeydata,
                                                          pubkeydata_len,
                                                   (libssh2_rsa_ctx**)key_ctx);
        }
    }
#endif
#if LIBSSH2_DSA
    if(strcmp("ssh-dss", (const char *)buf) == 0) {
        if(!key_type || strcmp("ssh-dss", key_type) == 0) {
            rc = gen_publickey_from_dsa_openssh_priv_data(session, decrypted,
                                                          method, method_len,
                                                          pubkeydata,
                                                          pubkeydata_len,
                                                   (libssh2_dsa_ctx**)key_ctx);
        }
    }
#endif
#if LIBSSH2_ECDSA
{
   libssh2_curve_type type;

    if(strcmp("sk-ecdsa-sha2-nistp256@openssh.com", (const char *)buf) == 0) {
        rc = gen_publickey_from_sk_ecdsa_openssh_priv_data(session, decrypted,
                                                           method, method_len,
                                                           pubkeydata,
                                                           pubkeydata_len,
                                                           NULL,
                                                           NULL, NULL,
                                                           NULL,
                                                 (libssh2_ecdsa_ctx**)key_ctx);
    }
    else if(_libssh2_ecdsa_curve_type_from_name((const char *)buf, &type)
        == 0) {
        if(!key_type || strcmp("ssh-ecdsa", key_type) == 0) {
            rc = gen_publickey_from_ecdsa_openssh_priv_data(session, type,
                                                            decrypted,
                                                            method, method_len,
                                                            pubkeydata,
                                                            pubkeydata_len,
                                                 (libssh2_ecdsa_ctx**)key_ctx);
        }
    }
}
#endif

    if(rc == LIBSSH2_ERROR_FILE)
        rc = _libssh2_error(session, LIBSSH2_ERROR_FILE,
                         "Unable to extract public key from private key file: "
                         "invalid/unrecognized private key file format");

    if(decrypted)
        _libssh2_string_buf_free(session, decrypted);

    return rc;
}

static int
_libssh2_sk_pub_openssh_keyfilememory(LIBSSH2_SESSION *session,
                                      void **key_ctx,
                                      const char *key_type,
                                      unsigned char **method,
                                      size_t *method_len,
                                      unsigned char **pubkeydata,
                                      size_t *pubkeydata_len,
                                      int *algorithm,
                                      unsigned char *flags,
                                      const char **application,
                                      const unsigned char **key_handle,
                                      size_t *handle_len,
                                      const char *privatekeydata,
                                      size_t privatekeydata_len,
                                      unsigned const char *passphrase)
{
    int rc;
    unsigned char *buf = NULL;
    struct string_buf *decrypted = NULL;

    if(key_ctx)
        *key_ctx = NULL;

    if(!session)
        return _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                              "Session is required");

    if(key_type && strlen(key_type) < 7)
        return _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                              "type is invalid");

    _libssh2_init_if_needed();

    rc = _libssh2_openssh_pem_parse_memory(session, passphrase,
                                           privatekeydata,
                                           privatekeydata_len, &decrypted);

    if(rc)
        return rc;

    /* We have a new key file, now try and parse it using supported types  */
    rc = _libssh2_get_string(decrypted, &buf, NULL);

    if(rc || !buf)
        return _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                              "Public key type in decrypted "
                              "key data not found");

    rc = LIBSSH2_ERROR_FILE;

    /* Avoid unused variable warnings when all branches below are disabled */
    (void)method;
    (void)method_len;
    (void)pubkeydata;
    (void)pubkeydata_len;
    (void)algorithm;
    (void)flags;
    (void)application;
    (void)key_handle;
    (void)handle_len;

#if LIBSSH2_ED25519
    if(strcmp("sk-ssh-ed25519@openssh.com", (const char *)buf) == 0) {
        *algorithm = LIBSSH2_HOSTKEY_TYPE_ED25519;
        if(!key_type ||
           strcmp("sk-ssh-ed25519@openssh.com", key_type) == 0) {
            rc = gen_publickey_from_sk_ed25519_openssh_priv_data(session,
                                                                 decrypted,
                                                                 method,
                                                                 method_len,
                                                                 pubkeydata,
                                                                pubkeydata_len,
                                                                 flags,
                                                                 application,
                                                                 key_handle,
                                                                 handle_len,
                                               (libssh2_ed25519_ctx**)key_ctx);
        }
    }
#endif
#if LIBSSH2_ECDSA
    if(strcmp("sk-ecdsa-sha2-nistp256@openssh.com", (const char *)buf) == 0) {
        *algorithm = LIBSSH2_HOSTKEY_TYPE_ECDSA_256;
        rc = gen_publickey_from_sk_ecdsa_openssh_priv_data(session, decrypted,
                                                           method, method_len,
                                                           pubkeydata,
                                                           pubkeydata_len,
                                                           flags,
                                                           application,
                                                           key_handle,
                                                           handle_len,
                                                 (libssh2_ecdsa_ctx**)key_ctx);
    }
#endif

    if(rc == LIBSSH2_ERROR_FILE)
        rc = _libssh2_error(session, LIBSSH2_ERROR_FILE,
                         "Unable to extract public key from private key file: "
                         "invalid/unrecognized private key file format");

    if(decrypted)
        _libssh2_string_buf_free(session, decrypted);

    return rc;
}

int
read_openssh_private_key_from_memory(void **key_ctx, LIBSSH2_SESSION *session,
                                     const char *key_type,
                                     const char *filedata,
                                     size_t filedata_len,
                                     unsigned const char *passphrase)
{
    return _libssh2_pub_priv_openssh_keyfilememory(session, key_ctx, key_type,
                                                   NULL, NULL, NULL, NULL,
                                                   filedata, filedata_len,
                                                   passphrase);
}

#if OPENSSL_VERSION_NUMBER >= 0x30000000L
#define HAVE_SSLERROR_BAD_DECRYPT
#endif

int
_libssh2_pub_priv_keyfilememory(LIBSSH2_SESSION *session,
                                unsigned char **method,
                                size_t *method_len,
                                unsigned char **pubkeydata,
                                size_t *pubkeydata_len,
                                const char *privatekeydata,
                                size_t privatekeydata_len,
                                const char *passphrase)
{
    int       st;
    BIO*      bp;
    EVP_PKEY* pk;
    int       pktype;
#ifdef HAVE_SSLERROR_BAD_DECRYPT
    unsigned long sslError;
#endif

    _libssh2_debug((session,
                   LIBSSH2_TRACE_AUTH,
                   "Computing public key from private key."));

#if OPENSSL_VERSION_NUMBER >= 0x1000200fL
    bp = BIO_new_mem_buf(privatekeydata, (int)privatekeydata_len);
#else
    bp = BIO_new_mem_buf((char *)privatekeydata, (int)privatekeydata_len);
#endif
    if(!bp)
        return _libssh2_error(session, LIBSSH2_ERROR_ALLOC,
                              "Unable to allocate memory when"
                              "computing public key");
    BIO_reset(bp);
    pk = PEM_read_bio_PrivateKey(bp, NULL, NULL, (void *)passphrase);
#ifdef HAVE_SSLERROR_BAD_DECRYPT
    sslError = ERR_get_error();
#endif
    BIO_free(bp);

    if(!pk) {
        /* Try OpenSSH format */
        st = _libssh2_pub_priv_openssh_keyfilememory(session, NULL, NULL,
                                                     method,
                                                     method_len,
                                                     pubkeydata,
                                                     pubkeydata_len,
                                                     privatekeydata,
                                                     privatekeydata_len,
                                            (unsigned const char *)passphrase);
        if(st == 0)
            return 0;

#ifdef HAVE_SSLERROR_BAD_DECRYPT
        if((ERR_GET_LIB(sslError) == ERR_LIB_PEM &&
            ERR_GET_REASON(sslError) == PEM_R_BAD_DECRYPT) ||
           (ERR_GET_LIB(sslError) == ERR_LIB_PROV &&
            ERR_GET_REASON(sslError) == EVP_R_BAD_DECRYPT))
            return _libssh2_error(session, LIBSSH2_ERROR_KEYFILE_AUTH_FAILED,
                                  "Wrong passphrase for private key");
#endif
        return _libssh2_error(session,
                              LIBSSH2_ERROR_FILE,
                              "Unable to extract public key "
                              "from private key file: "
                              "Unsupported private key file format");
    }

#ifdef HAVE_OPAQUE_STRUCTS
    pktype = EVP_PKEY_id(pk);
#else
    pktype = pk->type;
#endif

    switch(pktype) {
#if LIBSSH2_ED25519
    case EVP_PKEY_ED25519:
        st = gen_publickey_from_ed_evp(
            session, method, method_len, pubkeydata, pubkeydata_len, pk);
        break;
#endif /* LIBSSH2_ED25519 */
#if LIBSSH2_RSA
    case EVP_PKEY_RSA:
        st = gen_publickey_from_rsa_evp(session, method, method_len,
                                        pubkeydata, pubkeydata_len, pk);
        break;
#endif /* LIBSSH2_RSA */
#if LIBSSH2_DSA
    case EVP_PKEY_DSA:
        st = gen_publickey_from_dsa_evp(session, method, method_len,
                                        pubkeydata, pubkeydata_len, pk);
        break;
#endif /* LIBSSH2_DSA */
#if LIBSSH2_ECDSA
    case EVP_PKEY_EC:
        st = gen_publickey_from_ec_evp(session, method, method_len,
                                       pubkeydata, pubkeydata_len,
                                       0, pk);
        break;
#endif /* LIBSSH2_ECDSA */
    default:
        st = _libssh2_error(session,
                            LIBSSH2_ERROR_FILE,
                            "Unable to extract public key "
                            "from private key file: "
                            "Unsupported private key file format");
        break;
    }

    EVP_PKEY_free(pk);
    return st;
}

int
_libssh2_sk_pub_keyfilememory(LIBSSH2_SESSION *session,
                              unsigned char **method,
                              size_t *method_len,
                              unsigned char **pubkeydata,
                              size_t *pubkeydata_len,
                              int *algorithm,
                              unsigned char *flags,
                              const char **application,
                              const unsigned char **key_handle,
                              size_t *handle_len,
                              const char *privatekeydata,
                              size_t privatekeydata_len,
                              const char *passphrase)
{
    int       st = -1;
    BIO*      bp;
    EVP_PKEY* pk;

    _libssh2_debug((session,
                   LIBSSH2_TRACE_AUTH,
                   "Computing public key from private key."));

#if OPENSSL_VERSION_NUMBER >= 0x1000200fL
    bp = BIO_new_mem_buf(privatekeydata, (int)privatekeydata_len);
#else
    bp = BIO_new_mem_buf((char *)privatekeydata, (int)privatekeydata_len);
#endif
    if(!bp)
        return _libssh2_error(session, LIBSSH2_ERROR_ALLOC,
                              "Unable to allocate memory when"
                              "computing public key");
    BIO_reset(bp);
    pk = PEM_read_bio_PrivateKey(bp, NULL, NULL, (void *)passphrase);
    BIO_free(bp);

    if(!pk) {
        /* Try OpenSSH format */
        st = _libssh2_sk_pub_openssh_keyfilememory(session, NULL, NULL,
                                                   method,
                                                   method_len,
                                                   pubkeydata,
                                                   pubkeydata_len,
                                                   algorithm,
                                                   flags,
                                                   application,
                                                   key_handle,
                                                   handle_len,
                                                   privatekeydata,
                                                   privatekeydata_len,
                                            (unsigned const char *)passphrase);
    }

    return st;
}

void
_libssh2_dh_init(_libssh2_dh_ctx *dhctx)
{
    *dhctx = BN_new();                          /* Random from client */
}

int
_libssh2_dh_key_pair(_libssh2_dh_ctx *dhctx, _libssh2_bn *public,
                     _libssh2_bn *g, _libssh2_bn *p, int group_order,
                     _libssh2_bn_ctx *bnctx)
{
    /* Generate x and e */
    BN_rand(*dhctx, group_order * 8 - 1, 0, -1);
    BN_mod_exp(public, g, *dhctx, p, bnctx);
    return 0;
}

int
_libssh2_dh_secret(_libssh2_dh_ctx *dhctx, _libssh2_bn *secret,
                   _libssh2_bn *f, _libssh2_bn *p,
                   _libssh2_bn_ctx *bnctx)
{
    /* Compute the shared secret */
    BN_mod_exp(secret, f, *dhctx, p, bnctx);
    return 0;
}

void
_libssh2_dh_dtor(_libssh2_dh_ctx *dhctx)
{
    BN_clear_free(*dhctx);
    *dhctx = NULL;
}

/* _libssh2_supported_key_sign_algorithms
 *
 * Return supported key hash algo upgrades, see crypto.h
 *
 */

const char *
_libssh2_supported_key_sign_algorithms(LIBSSH2_SESSION *session,
                                       unsigned char *key_method,
                                       size_t key_method_len)
{
    (void)session;

#if LIBSSH2_RSA_SHA2
    if(key_method_len == 7 &&
       memcmp(key_method, "ssh-rsa", key_method_len) == 0) {
        return "rsa-sha2-512,rsa-sha2-256,ssh-rsa";
    }
#endif

    return NULL;
}

#endif /* LIBSSH2_CRYPTO_C */
