/*
 * This file is part of the SSH Library
 *
 * Copyright (c) 2017 Sartura d.o.o.
 *
 * Author: Juraj Vijtiuk <juraj.vijtiuk@sartura.hr>
 *
 * The SSH Library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or (at your
 * option) any later version.
 *
 * The SSH Library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with the SSH Library; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
 * MA 02111-1307, USA.
 */

#include "config.h"

#ifdef HAVE_LIBMBEDCRYPTO
#include <mbedtls/pk.h>
#include <mbedtls/error.h>
#include "mbedcrypto-compat.h"

#include "libssh/priv.h"
#include "libssh/pki.h"
#include "libssh/pki_priv.h"
#include "libssh/buffer.h"
#include "libssh/bignum.h"
#include "libssh/misc.h"

#define MAX_PASSPHRASE_SIZE 1024
#define MAX_KEY_SIZE 32

void pki_key_clean(ssh_key key)
{
    if (key == NULL)
        return;

    if (key->pk != NULL) {
        mbedtls_pk_free(key->pk);
        SAFE_FREE(key->pk);
    }

    if (key->ecdsa != NULL) {
        mbedtls_ecdsa_free(key->ecdsa);
        SAFE_FREE(key->ecdsa);
    }
}

ssh_string pki_private_key_to_pem(const ssh_key key, const char *passphrase,
        ssh_auth_callback auth_fn, void *auth_data)
{
    (void) key;
    (void) passphrase;
    (void) auth_fn;
    (void) auth_data; return NULL;
}

static int pki_key_ecdsa_to_nid(mbedtls_ecdsa_context *ecdsa)
{
    mbedtls_ecp_group_id id;

    id = ecdsa->MBEDTLS_PRIVATE(grp.id);
    if (id == MBEDTLS_ECP_DP_SECP256R1) {
        return NID_mbedtls_nistp256;
    } else if (id == MBEDTLS_ECP_DP_SECP384R1) {
        return NID_mbedtls_nistp384;
    } else if (id == MBEDTLS_ECP_DP_SECP521R1) {
        return NID_mbedtls_nistp521;
    }

    return -1;
}

static enum ssh_keytypes_e pki_key_ecdsa_to_key_type(mbedtls_ecdsa_context *ecdsa)
{
    int nid;

    nid = pki_key_ecdsa_to_nid(ecdsa);

    switch (nid) {
        case NID_mbedtls_nistp256:
            return SSH_KEYTYPE_ECDSA_P256;
        case NID_mbedtls_nistp384:
            return SSH_KEYTYPE_ECDSA_P384;
        case NID_mbedtls_nistp521:
            return SSH_KEYTYPE_ECDSA_P521;
        default:
            return SSH_KEYTYPE_UNKNOWN;
    }
}

ssh_key pki_private_key_from_base64(const char *b64_key, const char *passphrase,
        ssh_auth_callback auth_fn, void *auth_data)
{
    ssh_key key = NULL;
    mbedtls_pk_context *pk = NULL;
    mbedtls_pk_type_t mbed_type;
    int valid;
    /* mbedtls pk_parse_key expects strlen to count the 0 byte */
    size_t b64len = strlen(b64_key) + 1;
    unsigned char tmp[MAX_PASSPHRASE_SIZE] = {0};
#if MBEDTLS_VERSION_MAJOR > 2
    mbedtls_ctr_drbg_context *ctr_drbg = ssh_get_mbedtls_ctr_drbg_context();
#endif

    pk = malloc(sizeof(mbedtls_pk_context));
    if (pk == NULL) {
        goto fail;
    }
    mbedtls_pk_init(pk);

    if (passphrase == NULL) {
        if (auth_fn) {
            valid = auth_fn("Passphrase for private key:",
                            (char *)tmp,
                            MAX_PASSPHRASE_SIZE,
                            0,
                            0,
                            auth_data);
            if (valid < 0) {
                goto fail;
            }
            valid = mbedtls_pk_parse_key(
                pk,
                (const unsigned char *)b64_key,
                b64len,
                tmp,
                strnlen((const char *)tmp, MAX_PASSPHRASE_SIZE)
#if MBEDTLS_VERSION_MAJOR > 2
                    ,
                mbedtls_ctr_drbg_random,
                ctr_drbg
#endif
            );
        } else {
            valid = mbedtls_pk_parse_key(pk,
                                         (const unsigned char *)b64_key,
                                         b64len,
                                         NULL,
                                         0
#if MBEDTLS_VERSION_MAJOR > 2
                                         ,
                                         mbedtls_ctr_drbg_random,
                                         ctr_drbg
#endif
            );
        }
    } else {
        valid = mbedtls_pk_parse_key(pk,
                                     (const unsigned char *)b64_key,
                                     b64len,
                                     (const unsigned char *)passphrase,
                                     strnlen(passphrase, MAX_PASSPHRASE_SIZE)
#if MBEDTLS_VERSION_MAJOR > 2
                                         ,
                                     mbedtls_ctr_drbg_random,
                                     ctr_drbg
#endif
        );
    }
    if (valid != 0) {
        char error_buf[100];
        mbedtls_strerror(valid, error_buf, 100);
        SSH_LOG(SSH_LOG_WARN, "Parsing private key %s", error_buf);
        goto fail;
    }

    mbed_type = mbedtls_pk_get_type(pk);

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

    switch (mbed_type) {
    case MBEDTLS_PK_RSA:
    case MBEDTLS_PK_RSA_ALT:
        key->pk = pk;
        pk = NULL;
        key->type = SSH_KEYTYPE_RSA;
        break;
    case MBEDTLS_PK_ECKEY:
    case MBEDTLS_PK_ECDSA: {
        /* type will be set later */
        mbedtls_ecp_keypair *keypair = mbedtls_pk_ec(*pk);

        key->ecdsa = malloc(sizeof(mbedtls_ecdsa_context));
        if (key->ecdsa == NULL) {
            goto fail;
        }

        mbedtls_ecdsa_init(key->ecdsa);
        mbedtls_ecdsa_from_keypair(key->ecdsa, keypair);
        key->pk = pk;

        key->ecdsa_nid = pki_key_ecdsa_to_nid(key->ecdsa);

        /* pki_privatekey_type_from_string always returns P256 for ECDSA
         * keys, so we need to figure out the correct type here */
        key->type = pki_key_ecdsa_to_key_type(key->ecdsa);
        if (key->type == SSH_KEYTYPE_UNKNOWN) {
            SSH_LOG(SSH_LOG_TRACE, "Invalid private key.");
            goto fail;
        }
        break;
    }
    default:
        SSH_LOG(SSH_LOG_WARN,
                "Unknown or invalid private key type %d",
                mbed_type);
        return NULL;
    }

    key->type_c = ssh_key_type_to_char(key->type);
    key->flags = SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC;

    return key;
fail:
    ssh_key_free(key);
    if (pk != NULL) {
        mbedtls_pk_free(pk);
        SAFE_FREE(pk);
    }
    return NULL;
}

int pki_privkey_build_rsa(ssh_key key,
                          ssh_string n,
                          ssh_string e,
                          ssh_string d,
                          UNUSED_PARAM(ssh_string iqmp),
                          ssh_string p,
                          ssh_string q)
{
    mbedtls_rsa_context *rsa = NULL;
    const mbedtls_pk_info_t *pk_info = NULL;
    int rc;

    key->pk = malloc(sizeof(mbedtls_pk_context));
    if (key->pk == NULL) {
        return SSH_ERROR;
    }

    mbedtls_pk_init(key->pk);
    pk_info = mbedtls_pk_info_from_type(MBEDTLS_PK_RSA);
    mbedtls_pk_setup(key->pk, pk_info);

    rc = mbedtls_pk_can_do(key->pk, MBEDTLS_PK_RSA);
    if (rc == 0) {
        goto fail;
    }

    rsa = mbedtls_pk_rsa(*key->pk);
    rc = mbedtls_rsa_import_raw(rsa,
                                ssh_string_data(n), ssh_string_len(n),
                                ssh_string_data(p), ssh_string_len(p),
                                ssh_string_data(q), ssh_string_len(q),
                                ssh_string_data(d), ssh_string_len(d),
                                ssh_string_data(e), ssh_string_len(e));
    if (rc != 0) {
        SSH_LOG(SSH_LOG_TRACE, "Failed to import private RSA key");
        goto fail;
    }

    rc = mbedtls_rsa_complete(rsa);
    if (rc != 0) {
        SSH_LOG(SSH_LOG_TRACE, "Failed to complete private RSA key");
        goto fail;
    }

    rc = mbedtls_rsa_check_privkey(rsa);
    if (rc != 0) {
        SSH_LOG(SSH_LOG_TRACE, "Inconsistent private RSA key");
        goto fail;
    }

    return SSH_OK;

fail:
    mbedtls_pk_free(key->pk);
    SAFE_FREE(key->pk);
    return SSH_ERROR;
}

int pki_pubkey_build_rsa(ssh_key key, ssh_string e, ssh_string n)
{
    mbedtls_rsa_context *rsa = NULL;
    const mbedtls_pk_info_t *pk_info = NULL;
#if MBEDTLS_VERSION_MAJOR > 2
    mbedtls_mpi N;
    mbedtls_mpi E;
#endif
    int rc;

    key->pk = malloc(sizeof(mbedtls_pk_context));
    if (key->pk == NULL) {
        return SSH_ERROR;
    }

    mbedtls_pk_init(key->pk);
    pk_info = mbedtls_pk_info_from_type(MBEDTLS_PK_RSA);
    mbedtls_pk_setup(key->pk, pk_info);

    rc = mbedtls_pk_can_do(key->pk, MBEDTLS_PK_RSA);
    if (rc == 0) {
        goto fail;
    }

    rsa = mbedtls_pk_rsa(*key->pk);
#if MBEDTLS_VERSION_MAJOR > 2
    mbedtls_mpi_init(&N);
    mbedtls_mpi_init(&E);

    rc = mbedtls_mpi_read_binary(&N, ssh_string_data(n),
                                 ssh_string_len(n));
#else
    rc = mbedtls_mpi_read_binary(&rsa->N, ssh_string_data(n),
                                 ssh_string_len(n));
#endif
    if (rc != 0) {
        goto fail;
    }
#if MBEDTLS_VERSION_MAJOR > 2
    rc = mbedtls_mpi_read_binary(&E, ssh_string_data(e),
                                 ssh_string_len(e));
#else
    rc = mbedtls_mpi_read_binary(&rsa->E, ssh_string_data(e),
                                 ssh_string_len(e));
#endif
    if (rc != 0) {
        goto fail;
    }

#if MBEDTLS_VERSION_MAJOR > 2
    rc = mbedtls_rsa_import(rsa, &N, NULL, NULL, NULL, &E);
    if (rc != 0) {
        goto fail;
    }

    rc = mbedtls_rsa_complete(rsa);
    if (rc != 0) {
        goto fail;
    }

#else
    rsa->len = (mbedtls_mpi_bitlen(&rsa->N) + 7) >> 3;
#endif
    rc = SSH_OK;
    goto exit;
fail:
    rc = SSH_ERROR;
    mbedtls_pk_free(key->pk);
    SAFE_FREE(key->pk);
exit:
#if MBEDTLS_VERSION_MAJOR > 2
    mbedtls_mpi_free(&N);
    mbedtls_mpi_free(&E);
#endif
    return rc;
}

ssh_key pki_key_dup(const ssh_key key, int demote)
{
    ssh_key new = NULL;
    int rc;
    const mbedtls_pk_info_t *pk_info = NULL;
#if MBEDTLS_VERSION_MAJOR > 2
    mbedtls_mpi N;
    mbedtls_mpi E;
    mbedtls_mpi D;
    mbedtls_mpi P;
    mbedtls_mpi Q;
#endif

    new = pki_key_dup_common_init(key, demote);
    if (new == NULL) {
        return NULL;
    }

#if MBEDTLS_VERSION_MAJOR > 2
    mbedtls_mpi_init(&N);
    mbedtls_mpi_init(&E);
    mbedtls_mpi_init(&D);
    mbedtls_mpi_init(&P);
    mbedtls_mpi_init(&Q);
#endif

    switch(key->type) {
        case SSH_KEYTYPE_RSA: {
            mbedtls_rsa_context *rsa, *new_rsa;

            new->pk = malloc(sizeof(mbedtls_pk_context));
            if (new->pk == NULL) {
                goto fail;
            }

            mbedtls_pk_init(new->pk);
            pk_info = mbedtls_pk_info_from_type(MBEDTLS_PK_RSA);
            mbedtls_pk_setup(new->pk, pk_info);

            if (!mbedtls_pk_can_do(key->pk, MBEDTLS_PK_RSA) ||
                !mbedtls_pk_can_do(new->pk, MBEDTLS_PK_RSA)) {
                goto fail;
            }

            rsa = mbedtls_pk_rsa(*key->pk);
            new_rsa = mbedtls_pk_rsa(*new->pk);

            if (!demote && (key->flags & SSH_KEY_FLAG_PRIVATE)) {
#if MBEDTLS_VERSION_MAJOR > 2
                rc = mbedtls_rsa_export(rsa, &N, &P, &Q, &D, &E);
                if (rc != 0) {
                    goto fail;
                }
                rc = mbedtls_rsa_import(new_rsa, &N, &P, &Q, &D, &E);
                if (rc != 0) {
                    goto fail;
                }
#else
                rc = mbedtls_mpi_copy(&new_rsa->N, &rsa->N);
                if (rc != 0) {
                    goto fail;
                }

                rc = mbedtls_mpi_copy(&new_rsa->E, &rsa->E);
                if (rc != 0) {
                    goto fail;
                }

                new_rsa->len = (mbedtls_mpi_bitlen(&new_rsa->N) + 7) >> 3;

                rc = mbedtls_mpi_copy(&new_rsa->D, &rsa->D);
                if (rc != 0) {
                    goto fail;
                }

                rc = mbedtls_mpi_copy(&new_rsa->P, &rsa->P);
                if (rc != 0) {
                    goto fail;
                }

                rc = mbedtls_mpi_copy(&new_rsa->Q, &rsa->Q);
                if (rc != 0) {
                    goto fail;
                }

                rc = mbedtls_mpi_copy(&new_rsa->DP, &rsa->DP);
                if (rc != 0) {
                    goto fail;
                }

                rc = mbedtls_mpi_copy(&new_rsa->DQ, &rsa->DQ);
                if (rc != 0) {
                    goto fail;
                }

                rc = mbedtls_mpi_copy(&new_rsa->QP, &rsa->QP);
                if (rc != 0) {
                    goto fail;
                }
#endif
            } else {
#if MBEDTLS_VERSION_MAJOR > 2
                rc = mbedtls_rsa_export(rsa, &N, NULL, NULL, NULL, &E);
                if (rc != 0) {
                    goto fail;
                }
                rc = mbedtls_rsa_import(new_rsa, &N, NULL, NULL, NULL, &E);
                if (rc != 0) {
                    goto fail;
                }
#else
                rc = mbedtls_mpi_copy(&new_rsa->N, &rsa->N);
                if (rc != 0) {
                    goto fail;
                }

                rc = mbedtls_mpi_copy(&new_rsa->E, &rsa->E);
                if (rc != 0) {
                    goto fail;
                }

                new_rsa->len = (mbedtls_mpi_bitlen(&new_rsa->N) + 7) >> 3;
#endif
            }

#if MBEDTLS_VERSION_MAJOR > 2
            rc = mbedtls_rsa_complete(new_rsa);
            if (rc != 0) {
                goto fail;
            }
#endif

            break;
        }
        case SSH_KEYTYPE_ECDSA_P256:
        case SSH_KEYTYPE_ECDSA_P384:
        case SSH_KEYTYPE_ECDSA_P521:
        case SSH_KEYTYPE_SK_ECDSA:
            new->ecdsa_nid = key->ecdsa_nid;

            new->ecdsa = malloc(sizeof(mbedtls_ecdsa_context));

            if (new->ecdsa == NULL) {
                goto fail;
            }

            mbedtls_ecdsa_init(new->ecdsa);

            if ((demote && ssh_key_is_private(key)) ||
                is_sk_key_type(key->type)) {
                rc = mbedtls_ecp_copy(&new->ecdsa->MBEDTLS_PRIVATE(Q),
                                &key->ecdsa->MBEDTLS_PRIVATE(Q));
                if (rc != 0) {
                    goto fail;
                }

                rc = mbedtls_ecp_group_copy(&new->ecdsa->MBEDTLS_PRIVATE(grp),
                                &key->ecdsa->MBEDTLS_PRIVATE(grp));
                if (rc != 0) {
                    goto fail;
                }
            } else {
                mbedtls_ecdsa_from_keypair(new->ecdsa, key->ecdsa);
            }

            break;
        case SSH_KEYTYPE_ED25519:
        case SSH_KEYTYPE_SK_ED25519:
            rc = pki_ed25519_key_dup(new, key);
            if (rc != SSH_OK) {
                goto fail;
            }
            break;
        default:
            goto fail;
    }

    goto cleanup;

fail:
    SSH_KEY_FREE(new);
cleanup:
#if MBEDTLS_VERSION_MAJOR > 2
    mbedtls_mpi_free(&N);
    mbedtls_mpi_free(&E);
    mbedtls_mpi_free(&D);
    mbedtls_mpi_free(&P);
    mbedtls_mpi_free(&Q);
#endif
    return new;
}

int pki_key_generate_rsa(ssh_key key, int parameter)
{
    int rc;
    const mbedtls_pk_info_t *info = NULL;

    if (parameter == 0) {
        parameter = RSA_DEFAULT_KEY_SIZE;
    }

    key->pk = malloc(sizeof(mbedtls_pk_context));
    if (key->pk == NULL) {
        return SSH_ERROR;
    }

    mbedtls_pk_init(key->pk);

    info = mbedtls_pk_info_from_type(MBEDTLS_PK_RSA);
    rc = mbedtls_pk_setup(key->pk, info);
    if (rc != 0) {
        return SSH_ERROR;
    }

    if (mbedtls_pk_can_do(key->pk, MBEDTLS_PK_RSA)) {
        rc = mbedtls_rsa_gen_key(mbedtls_pk_rsa(*key->pk),
                                 mbedtls_ctr_drbg_random,
                                 ssh_get_mbedtls_ctr_drbg_context(),
                                 parameter,
                                 65537);
        if (rc != 0) {
            mbedtls_pk_free(key->pk);
            return SSH_ERROR;
        }
    }

    return SSH_OK;
}

int pki_key_compare(const ssh_key k1, const ssh_key k2, enum ssh_keycmp_e what)
{
    int rc = 0;
#if MBEDTLS_VERSION_MAJOR > 2
    mbedtls_mpi N1;
    mbedtls_mpi N2;
    mbedtls_mpi P1;
    mbedtls_mpi P2;
    mbedtls_mpi Q1;
    mbedtls_mpi Q2;
    mbedtls_mpi E1;
    mbedtls_mpi E2;

    mbedtls_mpi_init(&N1);
    mbedtls_mpi_init(&N2);
    mbedtls_mpi_init(&P1);
    mbedtls_mpi_init(&P2);
    mbedtls_mpi_init(&Q1);
    mbedtls_mpi_init(&Q2);
    mbedtls_mpi_init(&E1);
    mbedtls_mpi_init(&E2);
#endif

    switch (ssh_key_type_plain(k1->type)) {
        case SSH_KEYTYPE_RSA: {
            mbedtls_rsa_context *rsa1, *rsa2;
            if (!mbedtls_pk_can_do(k1->pk, MBEDTLS_PK_RSA) ||
                !mbedtls_pk_can_do(k2->pk, MBEDTLS_PK_RSA)) {
                break;
            }

            if (mbedtls_pk_get_type(k1->pk) != mbedtls_pk_get_type(k2->pk) ||
                mbedtls_pk_get_bitlen(k1->pk) !=
                    mbedtls_pk_get_bitlen(k2->pk)) {
                rc = 1;
                goto cleanup;
            }

            if (what == SSH_KEY_CMP_PUBLIC) {
#if MBEDTLS_VERSION_MAJOR > 2
                rsa1 = mbedtls_pk_rsa(*k1->pk);
                rc = mbedtls_rsa_export(rsa1, &N1, NULL, NULL, NULL, &E1);
                if (rc != 0) {
                    rc = 1;
                    goto cleanup;
                }

                rsa2 = mbedtls_pk_rsa(*k2->pk);
                rc = mbedtls_rsa_export(rsa2, &N2, NULL, NULL, NULL, &E2);
                if (rc != 0) {
                    rc = 1;
                    goto cleanup;
                }

                if (mbedtls_mpi_cmp_mpi(&N1, &N2) != 0) {
                    rc = 1;
                    goto cleanup;
                }

                if (mbedtls_mpi_cmp_mpi(&E1, &E2) != 0) {
                    rc = 1;
                    goto cleanup;
                }
#else
                rsa1 = mbedtls_pk_rsa(*k1->pk);
                rsa2 = mbedtls_pk_rsa(*k2->pk);
                if (mbedtls_mpi_cmp_mpi(&rsa1->N, &rsa2->N) != 0) {
                    rc = 1;
                    goto cleanup;
                }

                if (mbedtls_mpi_cmp_mpi(&rsa1->E, &rsa2->E) != 0) {
                    rc = 1;
                    goto cleanup;
                }
#endif
            } else if (what == SSH_KEY_CMP_PRIVATE) {
#if MBEDTLS_VERSION_MAJOR > 2
                rsa1 = mbedtls_pk_rsa(*k1->pk);
                rc = mbedtls_rsa_export(rsa1, &N1, &P1, &Q1, NULL, &E1);
                if (rc != 0) {
                    rc = 1;
                    goto cleanup;
                }

                rsa2 = mbedtls_pk_rsa(*k2->pk);
                rc = mbedtls_rsa_export(rsa2, &N2, &P2, &Q2, NULL, &E2);
                if (rc != 0) {
                    rc = 1;
                    goto cleanup;
                }

                if (mbedtls_mpi_cmp_mpi(&N1, &N2) != 0) {
                    rc = 1;
                    goto cleanup;
                }

                if (mbedtls_mpi_cmp_mpi(&E1, &E2) != 0) {
                    rc = 1;
                    goto cleanup;
                }

                if (mbedtls_mpi_cmp_mpi(&P1, &P2) != 0) {
                    rc = 1;
                    goto cleanup;
                }

                if (mbedtls_mpi_cmp_mpi(&Q1, &Q2) != 0) {
                    rc = 1;
                    goto cleanup;
                }
#else
                rsa1 = mbedtls_pk_rsa(*k1->pk);
                rsa2 = mbedtls_pk_rsa(*k2->pk);
                if (mbedtls_mpi_cmp_mpi(&rsa1->N, &rsa2->N) != 0) {
                    rc = 1;
                    goto cleanup;
                }

                if (mbedtls_mpi_cmp_mpi(&rsa1->E, &rsa2->E) != 0) {
                    rc = 1;
                    goto cleanup;
                }

                if (mbedtls_mpi_cmp_mpi(&rsa1->P, &rsa2->P) != 0) {
                    rc = 1;
                    goto cleanup;
                }

                if (mbedtls_mpi_cmp_mpi(&rsa1->Q, &rsa2->Q) != 0) {
                    rc = 1;
                    goto cleanup;
                }
#endif
            }
            break;
        }
        case SSH_KEYTYPE_ECDSA_P256:
        case SSH_KEYTYPE_ECDSA_P384:
        case SSH_KEYTYPE_ECDSA_P521:
        case SSH_KEYTYPE_SK_ECDSA: {
            mbedtls_ecp_keypair *ecdsa1 = k1->ecdsa;
            mbedtls_ecp_keypair *ecdsa2 = k2->ecdsa;

            if (ecdsa1->MBEDTLS_PRIVATE(grp).id !=
                ecdsa2->MBEDTLS_PRIVATE(grp).id) {
                rc = 1;
                goto cleanup;
            }

            if (mbedtls_mpi_cmp_mpi(&ecdsa1->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(X),
                &ecdsa2->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(X)))
            {
                rc = 1;
                goto cleanup;
            }

            if (mbedtls_mpi_cmp_mpi(&ecdsa1->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(Y),
                &ecdsa2->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(Y)))
            {
                rc = 1;
                goto cleanup;
            }

            if (mbedtls_mpi_cmp_mpi(&ecdsa1->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(Z),
                &ecdsa2->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(Z)))
            {
                rc = 1;
                goto cleanup;
            }

            if (what == SSH_KEY_CMP_PRIVATE &&
                k1->type != SSH_KEYTYPE_SK_ECDSA) {
                if (mbedtls_mpi_cmp_mpi(&ecdsa1->MBEDTLS_PRIVATE(d),
                    &ecdsa2->MBEDTLS_PRIVATE(d)))
                {
                    rc = 1;
                    goto cleanup;
                }
            }

            break;
        }
        case SSH_KEYTYPE_ED25519:
        case SSH_KEYTYPE_SK_ED25519:
            /* ed25519 keys handled globally */
            rc = 1;
            break;
        default:
            rc = 1;
	    break;
    }

cleanup:
#if MBEDTLS_VERSION_MAJOR > 2
    mbedtls_mpi_free(&N1);
    mbedtls_mpi_free(&N2);
    mbedtls_mpi_free(&P1);
    mbedtls_mpi_free(&P2);
    mbedtls_mpi_free(&Q1);
    mbedtls_mpi_free(&Q2);
    mbedtls_mpi_free(&E1);
    mbedtls_mpi_free(&E2);
#endif
    return rc;
}

ssh_string make_ecpoint_string(const mbedtls_ecp_group *g, const
        mbedtls_ecp_point *p)
{
    ssh_string s = NULL;
    size_t len = 1;
    int rc;

    s = ssh_string_new(len);
    if (s == NULL) {
        return NULL;
    }

    rc = mbedtls_ecp_point_write_binary(g, p, MBEDTLS_ECP_PF_UNCOMPRESSED,
                &len, ssh_string_data(s), ssh_string_len(s));
    if (rc == MBEDTLS_ERR_ECP_BUFFER_TOO_SMALL) {
        SSH_STRING_FREE(s);

        s = ssh_string_new(len);
        if (s == NULL) {
            return NULL;
        }

        rc = mbedtls_ecp_point_write_binary(g, p, MBEDTLS_ECP_PF_UNCOMPRESSED,
                &len, ssh_string_data(s), ssh_string_len(s));
    }

    if (rc != 0) {
        SSH_STRING_FREE(s);
        return NULL;
    }

    if (len != ssh_string_len(s)) {
        SSH_STRING_FREE(s);
        return NULL;
    }

    return s;
}

static const char* pki_key_ecdsa_nid_to_char(int nid)
{
    switch (nid) {
        case NID_mbedtls_nistp256:
            return "nistp256";
        case NID_mbedtls_nistp384:
            return "nistp384";
        case NID_mbedtls_nistp521:
            return "nistp521";
        default:
            break;
    }

    return "unknown";
}

ssh_string pki_key_to_blob(const ssh_key key, enum ssh_key_e type)
{
    ssh_buffer buffer = NULL;
    ssh_string type_s = NULL;
    ssh_string e = NULL;
    ssh_string n = NULL;
    ssh_string p = NULL;
    ssh_string q = NULL;
    ssh_string d = NULL;
    ssh_string iqmp = NULL;
    ssh_string str = NULL;
    int rc;
#if MBEDTLS_VERSION_MAJOR > 2
    mbedtls_mpi E = {0};
    mbedtls_mpi N = {0};
    mbedtls_mpi D = {0};
    mbedtls_mpi IQMP = {0};
    mbedtls_mpi P = {0};
    mbedtls_mpi Q = {0};

    mbedtls_mpi_init(&E);
    mbedtls_mpi_init(&N);
    mbedtls_mpi_init(&D);
    mbedtls_mpi_init(&IQMP);
    mbedtls_mpi_init(&P);
    mbedtls_mpi_init(&Q);
#endif

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

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

        goto makestring;
    }

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

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

    switch (key->type) {
    case SSH_KEYTYPE_RSA: {
        mbedtls_rsa_context *rsa = NULL;
        mbedtls_mpi *E_ptr = NULL, *N_ptr = NULL;

        if (mbedtls_pk_can_do(key->pk, MBEDTLS_PK_RSA) == 0) {
            SSH_BUFFER_FREE(buffer);
            return NULL;
        }

        rsa = mbedtls_pk_rsa(*key->pk);
#if MBEDTLS_VERSION_MAJOR > 2
        rc = mbedtls_rsa_export(rsa, &N, NULL, NULL, NULL, &E);
        if (rc != 0) {
            goto out;
        }

        E_ptr = &E;
        N_ptr = &N;
#else
        E_ptr = &rsa->E;
        N_ptr = &rsa->N;
#endif

        e = ssh_make_bignum_string(E_ptr);
        if (e == NULL) {
            goto out;
        }

        n = ssh_make_bignum_string(N_ptr);
        if (n == NULL) {
            goto out;
        }

        if (type == SSH_KEY_PUBLIC) {
            /* The N and E parts are swapped in the public key export ! */
            rc = ssh_buffer_add_ssh_string(buffer, e);
            if (rc < 0) {
                goto out;
            }

            rc = ssh_buffer_add_ssh_string(buffer, n);
            if (rc < 0) {
                goto out;
            }
        } else if (type == SSH_KEY_PRIVATE) {
            mbedtls_mpi *P_ptr = NULL, *Q_ptr = NULL, *D_ptr = NULL;
            mbedtls_mpi *IQMP_ptr = NULL;

            rc = ssh_buffer_add_ssh_string(buffer, n);
            if (rc < 0) {
                goto out;
            }

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

#if MBEDTLS_VERSION_MAJOR > 2
            rc = mbedtls_rsa_export(rsa, NULL, &P, &Q, &D, NULL);
            if (rc != 0) {
                goto out;
            }

            rc = mbedtls_rsa_export_crt(rsa, NULL, NULL, &IQMP);
            if (rc != 0) {
                goto out;
            }

            P_ptr = &P;
            Q_ptr = &Q;
            D_ptr = &D;
            IQMP_ptr = &IQMP;
#else
            P_ptr = &rsa->P;
            Q_ptr = &rsa->Q;
            D_ptr = &rsa->D;
            IQMP_ptr = &rsa->QP;
#endif

            p = ssh_make_bignum_string(P_ptr);
            if (p == NULL) {
                goto out;
            }

            q = ssh_make_bignum_string(Q_ptr);
            if (q == NULL) {
                goto out;
            }

            d = ssh_make_bignum_string(D_ptr);
            if (d == NULL) {
                goto out;
            }

            iqmp = ssh_make_bignum_string(IQMP_ptr);
            if (iqmp == NULL) {
                goto out;
            }

            rc = ssh_buffer_add_ssh_string(buffer, d);
            if (rc < 0) {
                goto out;
            }

            rc = ssh_buffer_add_ssh_string(buffer, iqmp);
            if (rc < 0) {
                goto out;
            }

            rc = ssh_buffer_add_ssh_string(buffer, p);
            if (rc < 0) {
                goto out;
            }

            rc = ssh_buffer_add_ssh_string(buffer, q);
            if (rc < 0) {
                goto out;
            }
        }
        break;
    }
    case SSH_KEYTYPE_ECDSA_P256:
    case SSH_KEYTYPE_ECDSA_P384:
    case SSH_KEYTYPE_ECDSA_P521:
    case SSH_KEYTYPE_SK_ECDSA:
        type_s =
            ssh_string_from_char(pki_key_ecdsa_nid_to_char(key->ecdsa_nid));
        if (type_s == NULL) {
            SSH_BUFFER_FREE(buffer);
            return NULL;
        }

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

        e = make_ecpoint_string(&key->ecdsa->MBEDTLS_PRIVATE(grp),
                                &key->ecdsa->MBEDTLS_PRIVATE(Q));

        if (e == NULL) {
            SSH_BUFFER_FREE(buffer);
            return NULL;
        }

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

        if (type == SSH_KEY_PRIVATE && key->type != SSH_KEYTYPE_SK_ECDSA) {
            d = ssh_make_bignum_string(&key->ecdsa->MBEDTLS_PRIVATE(d));

            if (d == NULL) {
                SSH_BUFFER_FREE(buffer);
                goto out;
            }

            rc = ssh_buffer_add_ssh_string(buffer, d);
            if (rc < 0) {
                goto out;
            }
        } else if (type == SSH_KEY_PRIVATE &&
                   key->type == SSH_KEYTYPE_SK_ECDSA) {
            rc = pki_buffer_pack_sk_priv_data(buffer, key);
            if (rc != SSH_OK) {
                goto out;
            }
        } else if (type == SSH_KEY_PUBLIC &&
                   key->type == SSH_KEYTYPE_SK_ECDSA) {
            /* public key can contain certificate sk information */
            rc = ssh_buffer_add_ssh_string(buffer, key->sk_application);
            if (rc < 0) {
                goto out;
            }
        }
        break;
    case SSH_KEYTYPE_ED25519:
    case SSH_KEYTYPE_SK_ED25519:
        if (type == SSH_KEY_PUBLIC) {
            rc = pki_ed25519_public_key_to_blob(buffer, key);
            if (rc == SSH_ERROR) {
                goto out;
            }
            /* public key can contain certificate sk information */
            if (key->type == SSH_KEYTYPE_SK_ED25519) {
                rc = ssh_buffer_add_ssh_string(buffer, key->sk_application);
                if (rc < 0) {
                    goto out;
                }
            }
        } else {
            if (key->type == SSH_KEYTYPE_SK_ED25519) {
                rc = pki_ed25519_public_key_to_blob(buffer, key);
                if (rc == SSH_ERROR) {
                    goto out;
                }

                rc = pki_buffer_pack_sk_priv_data(buffer, key);
                if (rc == SSH_ERROR) {
                    goto out;
                }
            } else {
                rc = pki_ed25519_private_key_to_blob(buffer, key);
                if (rc == SSH_ERROR) {
                    goto out;
                }
            }
        }
        break;
    default:
        goto out;
    }
makestring:
    str = ssh_string_new(ssh_buffer_get_len(buffer));
    if (str == NULL) {
        goto out;
    }

    rc = ssh_string_fill(str,
                         ssh_buffer_get(buffer),
                         ssh_buffer_get_len(buffer));
    if (rc < 0) {
        ssh_string_burn(str);
        SSH_STRING_FREE(str);
    }

out:
    SSH_BUFFER_FREE(buffer);
    ssh_string_burn(e);
    SSH_STRING_FREE(e);
    ssh_string_burn(n);
    SSH_STRING_FREE(n);
    ssh_string_burn(d);
    SSH_STRING_FREE(d);
    ssh_string_burn(iqmp);
    SSH_STRING_FREE(iqmp);
    ssh_string_burn(p);
    SSH_STRING_FREE(p);
    ssh_string_burn(q);
    SSH_STRING_FREE(q);
#if MBEDTLS_VERSION_MAJOR > 2
    mbedtls_mpi_free(&N);
    mbedtls_mpi_free(&E);
    mbedtls_mpi_free(&D);
    mbedtls_mpi_free(&IQMP);
    mbedtls_mpi_free(&P);
    mbedtls_mpi_free(&Q);
#endif

    return str;
}

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

    switch(sig->type) {
        case SSH_KEYTYPE_RSA:
            sig_blob = ssh_string_copy(sig->rsa_sig);
            break;
        case SSH_KEYTYPE_ECDSA_P256:
        case SSH_KEYTYPE_ECDSA_P384:
        case SSH_KEYTYPE_ECDSA_P521: {
            ssh_string r = NULL;
            ssh_string s = NULL;
            ssh_buffer b = NULL;
            int rc;

            b = ssh_buffer_new();
            if (b == NULL) {
                return NULL;
            }

            r = ssh_make_bignum_string(sig->ecdsa_sig.r);
            if (r == NULL) {
                SSH_BUFFER_FREE(b);
                return NULL;
            }

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

            s = ssh_make_bignum_string(sig->ecdsa_sig.s);
            if (s == NULL) {
                SSH_BUFFER_FREE(b);
                return NULL;
            }

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

            sig_blob = ssh_string_new(ssh_buffer_get_len(b));
            if (sig_blob == NULL) {
                SSH_BUFFER_FREE(b);
                return NULL;
            }

            rc = ssh_string_fill(sig_blob, ssh_buffer_get(b), ssh_buffer_get_len(b));
            SSH_BUFFER_FREE(b);
            if (rc < 0) {
                SSH_STRING_FREE(sig_blob);
                return NULL;
            }

            break;
        }
        case SSH_KEYTYPE_ED25519:
            sig_blob = pki_ed25519_signature_to_blob(sig);
            break;
        case SSH_KEYTYPE_SK_ECDSA:
        case SSH_KEYTYPE_SK_ED25519:
            /* For SK keys, signature data is already in raw_sig */
            sig_blob = ssh_string_copy(sig->raw_sig);
            break;
        default:
            SSH_LOG(SSH_LOG_TRACE, "Unknown signature key type: %s",
                    sig->type_c);
            return NULL;
    }

    return sig_blob;
}

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

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

    if (pubkey->pk == NULL) {
        SSH_LOG(SSH_LOG_TRACE, "Pubkey RSA field NULL");
        goto errout;
    }

    rsalen = mbedtls_pk_get_bitlen(pubkey->pk) / 8;
    if (len > rsalen) {
        SSH_LOG(SSH_LOG_TRACE,
                "Signature is too big: %lu > %lu",
                (unsigned long) len,
                (unsigned long) rsalen);
        goto errout;
    }
#ifdef DEBUG_CRYPTO
    SSH_LOG(SSH_LOG_TRACE, "RSA signature len: %lu", (unsigned long)len);
    ssh_log_hexdump("RSA signature", ssh_string_data(sig_blob), len);
#endif

    if (len == rsalen) {
        sig->rsa_sig = ssh_string_copy(sig_blob);
    } else {
        SSH_LOG(SSH_LOG_DEBUG, "RSA signature len %lu < %lu",
                (unsigned long) len,
                (unsigned long) rsalen);
        pad_len = rsalen - len;

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

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

        ssh_burn(blob_padded_data, pad_len);
        memcpy(blob_padded_data + pad_len, blob_orig, len);

        sig->rsa_sig = sig_blob_padded;
    }

    return sig;

errout:
    ssh_signature_free(sig);
    return NULL;
}
ssh_signature pki_signature_from_blob(const ssh_key pubkey,
                                      const ssh_string sig_blob,
                                      enum ssh_keytypes_e type,
                                      enum ssh_digest_e hash_type)
{
    ssh_signature sig = NULL;
    int rc;

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

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

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

    switch(type) {
        case SSH_KEYTYPE_RSA:
            sig = pki_signature_from_rsa_blob(pubkey, sig_blob, sig);
            if (sig == NULL) {
                return NULL;
            }
            break;
        case SSH_KEYTYPE_ECDSA_P256:
        case SSH_KEYTYPE_ECDSA_P384:
        case SSH_KEYTYPE_ECDSA_P521:
        case SSH_KEYTYPE_SK_ECDSA: {
            ssh_buffer b = NULL;
            ssh_string r = NULL;
            ssh_string s = NULL;
            size_t rlen;

            b = ssh_buffer_new();
            if (b == NULL) {
                ssh_signature_free(sig);
                return NULL;
            }

            rc = ssh_buffer_add_data(b, ssh_string_data(sig_blob),
                    ssh_string_len(sig_blob));

            if (rc < 0) {
                SSH_BUFFER_FREE(b);
                ssh_signature_free(sig);
                return NULL;
            }

            r = ssh_buffer_get_ssh_string(b);
            if (r == NULL) {
                SSH_BUFFER_FREE(b);
                ssh_signature_free(sig);
                return NULL;
            }
#ifdef DEBUG_CRYPTO
            ssh_log_hexdump("r", ssh_string_data(r), ssh_string_len(r));
#endif
            sig->ecdsa_sig.r = ssh_make_string_bn(r);
            ssh_string_burn(r);
            SSH_STRING_FREE(r);
            if (sig->ecdsa_sig.r == NULL) {
                SSH_BUFFER_FREE(b);
                ssh_signature_free(sig);
                return NULL;
            }

            s = ssh_buffer_get_ssh_string(b);
            rlen = ssh_buffer_get_len(b);
            SSH_BUFFER_FREE(b);
            if (s == NULL) {
                ssh_signature_free(sig);
                return NULL;
            }

#ifdef DEBUG_CRYPTO
            ssh_log_hexdump("s", ssh_string_data(s), ssh_string_len(s));
#endif
            sig->ecdsa_sig.s = ssh_make_string_bn(s);
            ssh_string_burn(s);
            SSH_STRING_FREE(s);
            if (sig->ecdsa_sig.s == NULL) {
                ssh_signature_free(sig);
                return NULL;
            }

            if (rlen != 0) {
                SSH_LOG(SSH_LOG_TRACE, "Signature has remaining bytes in inner "
                        "sigblob: %lu",
                        (unsigned long)rlen);
                ssh_signature_free(sig);
                return NULL;
            }

            break;
        }
        case SSH_KEYTYPE_ED25519:
        case SSH_KEYTYPE_SK_ED25519:
            rc = pki_signature_from_ed25519_blob(sig, sig_blob);
            if (rc == SSH_ERROR) {
                ssh_signature_free(sig);
                return NULL;
            }
            break;
        default:
            SSH_LOG(SSH_LOG_TRACE, "Unknown signature type");
            return NULL;
    }

    return sig;
}

static ssh_string rsa_do_sign_hash(const unsigned char *digest,
                                   int dlen,
                                   mbedtls_pk_context *privkey,
                                   enum ssh_digest_e hash_type)
{
    ssh_string sig_blob = NULL;
    mbedtls_md_type_t md = 0;
    unsigned char *sig = NULL;
    size_t slen;
    size_t sig_size;
    int ok;

    switch (hash_type) {
    case SSH_DIGEST_SHA1:
        md = MBEDTLS_MD_SHA1;
        break;
    case SSH_DIGEST_SHA256:
        md = MBEDTLS_MD_SHA256;
        break;
    case SSH_DIGEST_SHA512:
        md = MBEDTLS_MD_SHA512;
        break;
    case SSH_DIGEST_AUTO:
    default:
        SSH_LOG(SSH_LOG_TRACE, "Incompatible key algorithm");
        return NULL;
    }

    sig_size = mbedtls_pk_get_bitlen(privkey) / 8;
    sig = malloc(sig_size);
    if (sig == NULL) {
        return NULL;
    }

    ok = mbedtls_pk_sign(privkey,
                         md,
                         digest,
                         dlen,
                         sig,
#if MBEDTLS_VERSION_MAJOR > 2
                         sig_size,
#endif
                         &slen,
                         mbedtls_ctr_drbg_random,
                         ssh_get_mbedtls_ctr_drbg_context());

    if (ok != 0) {
        SAFE_FREE(sig);
        return NULL;
    }

    sig_blob = ssh_string_new(slen);
    if (sig_blob == NULL) {
        SAFE_FREE(sig);
        return NULL;
    }

    ok = ssh_string_fill(sig_blob, sig, slen);
    ssh_burn(sig, slen);
    SAFE_FREE(sig);
    if (ok < 0) {
        SSH_STRING_FREE(sig_blob);
        return NULL;
    }

    return sig_blob;
}


ssh_signature pki_do_sign_hash(const ssh_key privkey,
                               const unsigned char *hash,
                               size_t hlen,
                               enum ssh_digest_e hash_type)
{
    ssh_signature sig = NULL;
    int rc;

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

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

    switch(privkey->type) {
        case SSH_KEYTYPE_RSA:
            sig->rsa_sig = rsa_do_sign_hash(hash, hlen, privkey->pk, hash_type);
            if (sig->rsa_sig == NULL) {
                ssh_signature_free(sig);
                return NULL;
            }
            break;
        case SSH_KEYTYPE_ECDSA_P256:
        case SSH_KEYTYPE_ECDSA_P384:
        case SSH_KEYTYPE_ECDSA_P521:
            sig->ecdsa_sig.r = bignum_new();
            if (sig->ecdsa_sig.r == NULL) {
                return NULL;
            }

            sig->ecdsa_sig.s = bignum_new();
            if (sig->ecdsa_sig.s == NULL) {
                bignum_safe_free(sig->ecdsa_sig.r);
                return NULL;
            }

            rc = mbedtls_ecdsa_sign(&privkey->ecdsa->MBEDTLS_PRIVATE(grp),
                                    sig->ecdsa_sig.r,
                                    sig->ecdsa_sig.s,
                                    &privkey->ecdsa->MBEDTLS_PRIVATE(d),
                                    hash,
                                    hlen,
                                    mbedtls_ctr_drbg_random,
                                    ssh_get_mbedtls_ctr_drbg_context());
            if (rc != 0) {
                ssh_signature_free(sig);
                return NULL;
            }
            break;
        case SSH_KEYTYPE_ED25519:
            rc = pki_ed25519_sign(privkey, sig, hash, hlen);
            if (rc != SSH_OK) {
                ssh_signature_free(sig);
                return NULL;
            }
            break;
        default:
            ssh_signature_free(sig);
            return NULL;

    }

    return sig;
}

/**
 * @internal
 *
 * @brief Sign the given input data. The digest of to be signed is calculated
 * internally as necessary.
 *
 * @param[in]   privkey     The private key to be used for signing.
 * @param[in]   hash_type   The digest algorithm to be used.
 * @param[in]   input       The data to be signed.
 * @param[in]   input_len   The length of the data to be signed.
 *
 * @return  a newly allocated ssh_signature or NULL on error.
 */
ssh_signature pki_sign_data(const ssh_key privkey,
                            enum ssh_digest_e hash_type,
                            const unsigned char *input,
                            size_t input_len)
{
    unsigned char hash[SHA512_DIGEST_LEN] = {0};
    const unsigned char *sign_input = NULL;
    uint32_t hlen = 0;
    int rc;

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

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

    switch (hash_type) {
    case SSH_DIGEST_SHA256:
        sha256(input, input_len, hash);
        hlen = SHA256_DIGEST_LEN;
        sign_input = hash;
        break;
    case SSH_DIGEST_SHA384:
        sha384(input, input_len, hash);
        hlen = SHA384_DIGEST_LEN;
        sign_input = hash;
        break;
    case SSH_DIGEST_SHA512:
        sha512(input, input_len, hash);
        hlen = SHA512_DIGEST_LEN;
        sign_input = hash;
        break;
    case SSH_DIGEST_SHA1:
        sha1(input, input_len, hash);
        hlen = SHA_DIGEST_LEN;
        sign_input = hash;
        break;
    case SSH_DIGEST_AUTO:
        if (privkey->type == SSH_KEYTYPE_ED25519) {
            /* SSH_DIGEST_AUTO should only be used with ed25519 */
            sign_input = input;
            hlen = input_len;
            break;
        }
        FALL_THROUGH;
    default:
        SSH_LOG(SSH_LOG_TRACE, "Unknown hash algorithm for type: %d",
                hash_type);
        return NULL;
    }

    return pki_do_sign_hash(privkey, sign_input, hlen, hash_type);
}

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

    unsigned char hash[SHA512_DIGEST_LEN] = {0};
    const unsigned char *verify_input = NULL;
    uint32_t hlen = 0;

    mbedtls_md_type_t md = 0;

    int rc;

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

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

    switch (signature->hash_type) {
    case SSH_DIGEST_SHA256:
        sha256(input, input_len, hash);
        hlen = SHA256_DIGEST_LEN;
        md = MBEDTLS_MD_SHA256;
        verify_input = hash;
        break;
    case SSH_DIGEST_SHA384:
        sha384(input, input_len, hash);
        hlen = SHA384_DIGEST_LEN;
        md = MBEDTLS_MD_SHA384;
        verify_input = hash;
        break;
    case SSH_DIGEST_SHA512:
        sha512(input, input_len, hash);
        hlen = SHA512_DIGEST_LEN;
        md = MBEDTLS_MD_SHA512;
        verify_input = hash;
        break;
    case SSH_DIGEST_SHA1:
        sha1(input, input_len, hash);
        hlen = SHA_DIGEST_LEN;
        md = MBEDTLS_MD_SHA1;
        verify_input = hash;
        break;
    case SSH_DIGEST_AUTO:
        if (pubkey->type == SSH_KEYTYPE_ED25519 ||
            pubkey->type == SSH_KEYTYPE_ED25519_CERT01 ||
            pubkey->type == SSH_KEYTYPE_SK_ED25519 ||
            pubkey->type == SSH_KEYTYPE_ED25519_CERT01)
        {
            verify_input = input;
            hlen = input_len;
            break;
        }
        FALL_THROUGH;
    default:
        SSH_LOG(SSH_LOG_TRACE, "Unknown sig->hash_type: %d",
                signature->hash_type);
        return SSH_ERROR;
    }

    switch (pubkey->type) {
        case SSH_KEYTYPE_RSA:
        case SSH_KEYTYPE_RSA_CERT01:
            rc = mbedtls_pk_verify(pubkey->pk,
                                   md,
                                   hash,
                                   hlen,
                                   ssh_string_data(signature->rsa_sig),
                                   ssh_string_len(signature->rsa_sig));
            if (rc != 0) {
                char error_buf[100];
                mbedtls_strerror(rc, error_buf, 100);
                SSH_LOG(SSH_LOG_TRACE, "RSA error: %s", error_buf);
                return SSH_ERROR;
            }
            break;
        case SSH_KEYTYPE_ECDSA_P256:
        case SSH_KEYTYPE_ECDSA_P384:
        case SSH_KEYTYPE_ECDSA_P521:
        case SSH_KEYTYPE_ECDSA_P256_CERT01:
        case SSH_KEYTYPE_ECDSA_P384_CERT01:
        case SSH_KEYTYPE_ECDSA_P521_CERT01:
        case SSH_KEYTYPE_SK_ECDSA:
        case SSH_KEYTYPE_SK_ECDSA_CERT01:
            rc = mbedtls_ecdsa_verify(&pubkey->ecdsa->MBEDTLS_PRIVATE(grp), hash,
                    hlen, &pubkey->ecdsa->MBEDTLS_PRIVATE(Q),
                    signature->ecdsa_sig.r,
                    signature->ecdsa_sig.s);
            if (rc != 0) {
                char error_buf[100];
                mbedtls_strerror(rc, error_buf, 100);
                SSH_LOG(SSH_LOG_TRACE, "ECDSA error: %s", error_buf);
                return SSH_ERROR;

            }
            break;
        case SSH_KEYTYPE_ED25519:
        case SSH_KEYTYPE_ED25519_CERT01:
        case SSH_KEYTYPE_SK_ED25519:
        case SSH_KEYTYPE_SK_ED25519_CERT01:
            rc = pki_ed25519_verify(pubkey, signature, verify_input, hlen);
            if (rc != SSH_OK) {
                SSH_LOG(SSH_LOG_TRACE, "ED25519 error: Signature invalid");
                return SSH_ERROR;
            }
            break;
        default:
            SSH_LOG(SSH_LOG_TRACE, "Unknown public key type");
            return SSH_ERROR;
    }

    return SSH_OK;
}

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

    return "unknown";
}

int pki_key_ecdsa_nid_from_name(const char *name)
{
    if (strcmp(name, "nistp256") == 0) {
        return NID_mbedtls_nistp256;
    } else if (strcmp(name, "nistp384") == 0) {
        return NID_mbedtls_nistp384;
    } else if (strcmp(name, "nistp521") == 0) {
        return NID_mbedtls_nistp521;
    }

    return -1;
}

static mbedtls_ecp_group_id pki_key_ecdsa_nid_to_mbed_gid(int nid)
{
    switch (nid) {
        case NID_mbedtls_nistp256:
            return MBEDTLS_ECP_DP_SECP256R1;
        case NID_mbedtls_nistp384:
            return MBEDTLS_ECP_DP_SECP384R1;
        case NID_mbedtls_nistp521:
            return MBEDTLS_ECP_DP_SECP521R1;
    }

    return MBEDTLS_ECP_DP_NONE;
}

int pki_privkey_build_ecdsa(ssh_key key, int nid, ssh_string e, ssh_string exp)
{
    int rc;
    mbedtls_ecp_keypair keypair;
    mbedtls_ecp_group group;
    mbedtls_ecp_point Q;

    key->ecdsa_nid = nid;

    key->ecdsa = malloc(sizeof(mbedtls_ecdsa_context));
    if (key->ecdsa == NULL) {
        return SSH_ERROR;
    }

    mbedtls_ecdsa_init(key->ecdsa);
    mbedtls_ecp_keypair_init(&keypair);
    mbedtls_ecp_group_init(&group);
    mbedtls_ecp_point_init(&Q);

    rc = mbedtls_ecp_group_load(&group,
                                pki_key_ecdsa_nid_to_mbed_gid(nid));
    if (rc != 0) {
        goto fail;
    }

    rc = mbedtls_ecp_point_read_binary(&group, &Q, ssh_string_data(e),
                                       ssh_string_len(e));
    if (rc != 0) {
        goto fail;
    }

    rc = mbedtls_ecp_copy(&keypair.MBEDTLS_PRIVATE(Q), &Q);
    if (rc != 0) {
        goto fail;
    }

    rc = mbedtls_ecp_group_copy(&keypair.MBEDTLS_PRIVATE(grp), &group);
    if (rc != 0) {
        goto fail;
    }

    rc = mbedtls_mpi_read_binary(&keypair.MBEDTLS_PRIVATE(d),
                    ssh_string_data(exp),
                    ssh_string_len(exp));
    if (rc != 0) {
        goto fail;
    }

    rc = mbedtls_ecdsa_from_keypair(key->ecdsa, &keypair);
    if (rc != 0) {
        goto fail;
    }

    mbedtls_ecp_point_free(&Q);
    mbedtls_ecp_group_free(&group);
    mbedtls_ecp_keypair_free(&keypair);
    return SSH_OK;

fail:
    mbedtls_ecdsa_free(key->ecdsa);
    mbedtls_ecp_point_free(&Q);
    mbedtls_ecp_group_free(&group);
    mbedtls_ecp_keypair_free(&keypair);
    SAFE_FREE(key->ecdsa);
    return SSH_ERROR;
}

int pki_pubkey_build_ecdsa(ssh_key key, int nid, ssh_string e)
{
    int rc;
    mbedtls_ecp_keypair keypair;
    mbedtls_ecp_group group;
    mbedtls_ecp_point Q;

    key->ecdsa_nid = nid;

    key->ecdsa = malloc(sizeof(mbedtls_ecdsa_context));
    if (key->ecdsa == NULL) {
        return SSH_ERROR;
    }

    mbedtls_ecdsa_init(key->ecdsa);
    mbedtls_ecp_keypair_init(&keypair);
    mbedtls_ecp_group_init(&group);
    mbedtls_ecp_point_init(&Q);

    rc = mbedtls_ecp_group_load(&group,
            pki_key_ecdsa_nid_to_mbed_gid(nid));
    if (rc != 0) {
        goto fail;
    }

    rc = mbedtls_ecp_point_read_binary(&group, &Q, ssh_string_data(e),
            ssh_string_len(e));
    if (rc != 0) {
        goto fail;
    }

    rc = mbedtls_ecp_copy(&keypair.MBEDTLS_PRIVATE(Q), &Q);
    if (rc != 0) {
        goto fail;
    }

    rc = mbedtls_ecp_group_copy(&keypair.MBEDTLS_PRIVATE(grp), &group);
    if (rc != 0) {
        goto fail;
    }

    mbedtls_mpi_init(&keypair.MBEDTLS_PRIVATE(d));

    rc = mbedtls_ecdsa_from_keypair(key->ecdsa, &keypair);
    if (rc != 0) {
        goto fail;
    }

    mbedtls_ecp_point_free(&Q);
    mbedtls_ecp_group_free(&group);
    mbedtls_ecp_keypair_free(&keypair);
    return SSH_OK;
fail:
    mbedtls_ecdsa_free(key->ecdsa);
    mbedtls_ecp_point_free(&Q);
    mbedtls_ecp_group_free(&group);
    mbedtls_ecp_keypair_free(&keypair);
    SAFE_FREE(key->ecdsa);
    return SSH_ERROR;
}

int pki_key_generate_ecdsa(ssh_key key, int parameter)
{
    int ok;

    switch (parameter) {
        case 384:
            key->ecdsa_nid = NID_mbedtls_nistp384;
            key->type = SSH_KEYTYPE_ECDSA_P384;
            break;
        case 521:
            key->ecdsa_nid = NID_mbedtls_nistp521;
            key->type = SSH_KEYTYPE_ECDSA_P521;
            break;
        case 256:
        default:
            key->ecdsa_nid = NID_mbedtls_nistp256;
            key->type = SSH_KEYTYPE_ECDSA_P256;
            break;
    }

    key->ecdsa = malloc(sizeof(mbedtls_ecdsa_context));
    if (key->ecdsa == NULL) {
        return SSH_ERROR;
    }

    mbedtls_ecdsa_init(key->ecdsa);

    ok = mbedtls_ecdsa_genkey(key->ecdsa,
                              pki_key_ecdsa_nid_to_mbed_gid(key->ecdsa_nid),
                              mbedtls_ctr_drbg_random,
                              ssh_get_mbedtls_ctr_drbg_context());

    if (ok != 0) {
        mbedtls_ecdsa_free(key->ecdsa);
        SAFE_FREE(key->ecdsa);
    }

    return SSH_OK;
}

int ssh_key_size(ssh_key key)
{
    switch (key->type) {
    case SSH_KEYTYPE_RSA:
    case SSH_KEYTYPE_RSA_CERT01:
    case SSH_KEYTYPE_RSA1:
        return mbedtls_pk_get_bitlen(key->pk);
    case SSH_KEYTYPE_ECDSA_P256:
    case SSH_KEYTYPE_ECDSA_P256_CERT01:
    case SSH_KEYTYPE_SK_ECDSA:
    case SSH_KEYTYPE_SK_ECDSA_CERT01:
        return 256;
    case SSH_KEYTYPE_ECDSA_P384:
    case SSH_KEYTYPE_ECDSA_P384_CERT01:
        return 384;
    case SSH_KEYTYPE_ECDSA_P521:
    case SSH_KEYTYPE_ECDSA_P521_CERT01:
        return 521;
    case SSH_KEYTYPE_ED25519:
    case SSH_KEYTYPE_ED25519_CERT01:
    case SSH_KEYTYPE_SK_ED25519:
    case SSH_KEYTYPE_SK_ED25519_CERT01:
        /* ed25519 keys have fixed size */
        return 255;
    case SSH_KEYTYPE_DSS:   /* deprecated */
    case SSH_KEYTYPE_DSS_CERT01:    /* deprecated */
    case SSH_KEYTYPE_UNKNOWN:
    default:
        return SSH_ERROR;
    }
}

#ifdef WITH_PKCS11_URI
int pki_uri_import(const char *uri_name, ssh_key *key, enum ssh_key_e key_type)
{
    (void) uri_name;
    (void) key;
    (void) key_type;
    SSH_LOG(SSH_LOG_WARN,
            "mbedcrypto does not support PKCS #11");
    return SSH_ERROR;
}
#endif /* WITH_PKCS11_URI */
#endif /* HAVE_LIBMBEDCRYPTO */
