/* SPDX-License-Identifier: BSD-2-Clause */
/*******************************************************************************
 * Copyright 2017-2018, Fraunhofer SIT sponsored by Infineon Technologies AG
 * All rights reserved.
 ******************************************************************************/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <gcrypt.h>
#include <stdio.h>

#include "tss2_esys.h"

#include "esys_crypto.h"
#include "esys_iutil.h"
#include "esys_mu.h"
#define LOGMODULE esys_crypto
#include "util/log.h"
#include "util/aux_util.h"

/** Context to hold temporary values for iesys_crypto */
typedef struct _IESYS_CRYPTO_CONTEXT {
    enum {
        IESYS_CRYPTOGCRY_TYPE_HASH = 1,
        IESYS_CRYPTOGCRY_TYPE_HMAC,
    } type; /**< The type of context to hold; hash or hmac */
    union {
        struct {
            gcry_md_hd_t gcry_context;
            int gcry_hash_alg;
            size_t hash_len;
        } hash; /**< the state variables for a hash context */
        struct {
            gcry_mac_hd_t gcry_context;
            int gcry_hmac_alg;
            size_t hmac_len;
        } hmac; /**< the state variables for an hmac context */
    };
} IESYS_CRYPTOGCRY_CONTEXT;


/* Convert gcrypt mpi number to binary with fixed length */
static gcry_error_t mpi2bin(gcry_mpi_t mpi, unsigned char *bin,
		            size_t  bin_length, size_t max_out_size)
{
    gcry_error_t err;
    size_t size;
    size_t offset;

    /* Determine size of mpi */
    err = gcry_mpi_print(GCRYMPI_FMT_USG, NULL, max_out_size, &size, mpi);
    if (err != GPG_ERR_NO_ERROR) {
        LOG_ERROR("Function gcry_mpi_print");
        return err;
    }

    offset = bin_length - size;
    memset(&bin[0], 0,offset);
    err = gcry_mpi_print(GCRYMPI_FMT_USG, &bin[offset], bin_length - offset, &size, mpi);
    if (err != GPG_ERR_NO_ERROR) {
        LOG_ERROR("Function gcry_mpi_print");
    }
    return err;
}

/** Provide the context for the computation of a hash digest.
 *
 * The context will be created and initialized according to the hash function.
 * @param[out] context The created context (callee-allocated).
 * @param[in] hashAlg The hash algorithm for the creation of the context.
 * @retval TSS2_RC_SUCCESS on success.
 * @retval TSS2_ESYS_RC_BAD_VALUE or TSS2_ESYS_RC_BAD_REFERENCE for invalid parameters.
 * @retval TSS2_ESYS_RC_MEMORY Memory cannot be allocated.
 * @retval TSS2_ESYS_RC_GENERAL_FAILURE for errors of the crypto library.
 */
TSS2_RC
iesys_cryptogcry_hash_start(IESYS_CRYPTO_CONTEXT_BLOB ** context,
                            TPM2_ALG_ID hashAlg)
{
    LOG_TRACE("call: context=%p hashAlg=%"PRIu16, context, hashAlg);
    return_if_null(context, "Context is NULL", TSS2_ESYS_RC_BAD_REFERENCE);

    IESYS_CRYPTOGCRY_CONTEXT *mycontext;
    mycontext = calloc(1, sizeof(IESYS_CRYPTOGCRY_CONTEXT));
    return_if_null(mycontext, "Out of Memory", TSS2_ESYS_RC_MEMORY);

    mycontext->type = IESYS_CRYPTOGCRY_TYPE_HASH;

    switch (hashAlg) {
    case TPM2_ALG_SHA1:
        mycontext->hash.gcry_hash_alg = GCRY_MD_SHA1;
        break;
    case TPM2_ALG_SHA256:
        mycontext->hash.gcry_hash_alg = GCRY_MD_SHA256;
        break;
    case TPM2_ALG_SHA384:
        mycontext->hash.gcry_hash_alg = GCRY_MD_SHA384;
        break;
    default:
        LOG_ERROR("Unsupported hash algorithm (%"PRIu16")", hashAlg);
        free(mycontext);
        return TSS2_ESYS_RC_NOT_IMPLEMENTED;
    }
    int hash_len = gcry_md_get_algo_dlen(mycontext->hash.gcry_hash_alg);
    if (hash_len <= 0) {
        LOG_ERROR("Unsupported hash algorithm (%"PRIu16")", hashAlg);
        free(mycontext);
        return TSS2_ESYS_RC_GENERAL_FAILURE;
    }
    mycontext->hash.hash_len = hash_len;

    gcry_error_t r = gcry_md_open(&mycontext->hash.gcry_context,
                                  mycontext->hash.gcry_hash_alg, 0);
    if (r != 0) {
        LOG_ERROR("GCry error.");
        free(mycontext);
        return TSS2_ESYS_RC_GENERAL_FAILURE;
    }

    *context = (IESYS_CRYPTO_CONTEXT_BLOB *) mycontext;

    return TSS2_RC_SUCCESS;
}

/** Update the digest value of a digest object from a byte buffer.
 *
 * The context of a digest object will be updated according to the hash
 * algorithm of the context.
 * @param[in,out] context The context of the digest object which will be updated.
 * @param[in] buffer The data for the update.
 * @param[in] size The size of the data buffer.
 * @retval TSS2_RC_SUCCESS on success.
 * @retval TSS2_ESYS_RC_BAD_REFERENCE for invalid parameters.
 */
TSS2_RC
iesys_cryptogcry_hash_update(IESYS_CRYPTO_CONTEXT_BLOB * context,
                             const uint8_t * buffer, size_t size)
{
    LOG_TRACE("called for context %p, buffer %p and size %zd", context, buffer,
              size);
    if (context == NULL || buffer == NULL) {
        LOG_ERROR("Null-Pointer passed");
        return TSS2_ESYS_RC_BAD_REFERENCE;
    }
    IESYS_CRYPTOGCRY_CONTEXT *mycontext = (IESYS_CRYPTOGCRY_CONTEXT *) context;
    if (mycontext->type != IESYS_CRYPTOGCRY_TYPE_HASH) {
        LOG_ERROR("bad context");
        return TSS2_ESYS_RC_BAD_REFERENCE;
    }

    LOGBLOB_TRACE(buffer, size, "Updating hash with");

    gcry_md_write(mycontext->hash.gcry_context, buffer, size);

    return TSS2_RC_SUCCESS;
}

/** Update the digest value of a digest object from a TPM2B object.
 *
 * The context of a digest object will be updated according to the hash
 * algorithm of the context.
 * @param[in,out] context The context of the digest object which will be updated.
 * @param[in] b The TPM2B object for the update.
 * @retval TSS2_RC_SUCCESS on success.
 * @retval TSS2_ESYS_RC_BAD_REFERENCE for invalid parameters.
 */
TSS2_RC
iesys_cryptogcry_hash_update2b(IESYS_CRYPTO_CONTEXT_BLOB * context, TPM2B * b)
{
    LOG_TRACE("called for context-pointer %p and 2b-pointer %p", context, b);
    if (context == NULL || b == NULL) {
        LOG_ERROR("Null-Pointer passed");
        return TSS2_ESYS_RC_BAD_REFERENCE;
    }
    TSS2_RC ret = iesys_cryptogcry_hash_update(context, &b->buffer[0], b->size);
    return ret;
}

/** Get the digest value of a digest object and close the context.
 *
 * The digest value will written to a passed buffer and the resources of the
 * digest object are released.
 * @param[in,out] context The context of the digest object to be released
 * @param[out] buffer The buffer for the digest value (caller-allocated).
 * @param[out] size The size of the digest.
 * @retval TSS2_RC_SUCCESS on success.
 * @retval TSS2_ESYS_RC_BAD_REFERENCE for invalid parameters.
 * @retval TSS2_ESYS_RC_GENERAL_FAILURE for errors of the crypto library.
 */
TSS2_RC
iesys_cryptogcry_hash_finish(IESYS_CRYPTO_CONTEXT_BLOB ** context,
                             uint8_t * buffer, size_t * size)
{
    LOG_TRACE("called for context-pointer %p, buffer %p and size-pointer %p",
              context, buffer, size);
    if (context == NULL || *context == NULL || buffer == NULL || size == NULL) {
        LOG_ERROR("Null-Pointer passed");
        return TSS2_ESYS_RC_BAD_REFERENCE;
    }
    IESYS_CRYPTOGCRY_CONTEXT *mycontext = * context;
    if (mycontext->type != IESYS_CRYPTOGCRY_TYPE_HASH) {
        LOG_ERROR("bad context");
        return TSS2_ESYS_RC_BAD_REFERENCE;
    }

    if (*size < mycontext->hash.hash_len) {
        LOG_ERROR("Buffer too small");
        return TSS2_ESYS_RC_BAD_SIZE;
    }

    uint8_t *cpHash = gcry_md_read(mycontext->hash.gcry_context,
                                   mycontext->hash.gcry_hash_alg);
    return_if_null(cpHash, "GCry error.", TSS2_ESYS_RC_GENERAL_FAILURE);

    LOGBLOB_TRACE(cpHash, mycontext->hash.hash_len, "read hash result");

    *size = mycontext->hash.hash_len;
    memmove(buffer, cpHash, *size);

    gcry_md_close(mycontext->hash.gcry_context);

    free(mycontext);
    *context = NULL;

    return TSS2_RC_SUCCESS;
}

/** Release the resources of a digest object.
 *
 * The assigned resources will be released and the context will be set to NULL.
 * @param[in,out] context The context of the digest object.
 */
void
iesys_cryptogcry_hash_abort(IESYS_CRYPTO_CONTEXT_BLOB ** context)
{
    LOG_TRACE("called for context-pointer %p", context);
    if (context == NULL || *context == NULL) {
        LOG_DEBUG("Null-Pointer passed");
        return;
    }
    IESYS_CRYPTOGCRY_CONTEXT *mycontext =
        (IESYS_CRYPTOGCRY_CONTEXT *) * context;
    if (mycontext->type != IESYS_CRYPTOGCRY_TYPE_HASH) {
        LOG_DEBUG("bad context");
        return;
    }

    gcry_md_close(mycontext->hash.gcry_context);
    free(mycontext);
    *context = NULL;
}

/* HMAC */

/** Provide the context an HMAC digest object from a byte buffer key.
 *
 * The context will be created and initialized according to the hash function
 * and the used HMAC key.
 * @param[out] context The created context (callee-allocated).
 * @param[in] hmacAlg The hash algorithm for the HMAC computation.
 * @param[in] key The byte buffer of the HMAC key.
 * @param[in] size The size of the HMAC key.
 * @retval TSS2_RC_SUCCESS on success.
 * @retval TSS2_ESYS_RC_BAD_REFERENCE for invalid parameters.
 * @retval TSS2_ESYS_RC_MEMORY Memory cannot be allocated.
 * @retval TSS2_ESYS_RC_GENERAL_FAILURE for errors of the crypto library.
 */
TSS2_RC
iesys_cryptogcry_hmac_start(IESYS_CRYPTO_CONTEXT_BLOB ** context,
                            TPM2_ALG_ID hmacAlg,
                            const uint8_t * key, size_t size)
{
    TSS2_RC r;

    LOG_TRACE("called for context-pointer %p and hmacAlg %d", context, hmacAlg);
    LOGBLOB_TRACE(key, size, "Starting  hmac with");
    if (context == NULL || key == NULL) {
        LOG_ERROR("Null-Pointer passed in for context");
        return TSS2_ESYS_RC_BAD_REFERENCE;
    }
    IESYS_CRYPTOGCRY_CONTEXT *mycontext =
        calloc(1, sizeof(IESYS_CRYPTOGCRY_CONTEXT));
    return_if_null(mycontext, "Out of Memory", TSS2_ESYS_RC_MEMORY);

    switch (hmacAlg) {
    case TPM2_ALG_SHA1:
        mycontext->hmac.gcry_hmac_alg = GCRY_MAC_HMAC_SHA1;
        break;
    case TPM2_ALG_SHA256:
        mycontext->hmac.gcry_hmac_alg = GCRY_MAC_HMAC_SHA256;
        break;
    default:
        LOG_ERROR("Unsupported hmac algo.");
        free(mycontext);
        return TSS2_ESYS_RC_NOT_IMPLEMENTED;
    }

    int hmac_len = gcry_mac_get_algo_maclen(mycontext->hmac.gcry_hmac_alg);
    if (hmac_len <= 0) {
        LOG_ERROR("GCry error.");
        free(mycontext);
        return TSS2_ESYS_RC_GENERAL_FAILURE;
    }

    mycontext->type = IESYS_CRYPTOGCRY_TYPE_HMAC;
    mycontext->hmac.hmac_len = hmac_len;

    r = gcry_mac_open(&mycontext->hmac.gcry_context,
                      mycontext->hmac.gcry_hmac_alg, 0, NULL);
    if (r != 0) {
        LOG_ERROR("GCry error.");
        free(mycontext);
        return TSS2_ESYS_RC_GENERAL_FAILURE;
    }

    r = gcry_mac_setkey(mycontext->hmac.gcry_context, key, size);
    if (r != 0) {
        LOG_ERROR("GCry error.");
        gcry_mac_close(mycontext->hmac.gcry_context);
        free(mycontext);
        return TSS2_ESYS_RC_GENERAL_FAILURE;
    }

    *context = (IESYS_CRYPTO_CONTEXT_BLOB *) mycontext;

    return TSS2_RC_SUCCESS;
}

/** Update and HMAC digest value from a byte buffer.
 *
 * The context of a digest object will be updated according to the hash
 * algorithm and the key of the context.
 * @param[in,out] context The context of the digest object which will be updated.
 * @param[in] buffer The data for the update.
 * @param[in] size The size of the data buffer.
 * @retval TSS2_RC_SUCCESS on success.
 * @retval TSS2_ESYS_RC_BAD_REFERENCE for invalid parameters.
 */
TSS2_RC
iesys_cryptogcry_hmac_update(IESYS_CRYPTO_CONTEXT_BLOB * context,
                             const uint8_t * buffer, size_t size)
{
    LOG_TRACE("called for context %p, buffer %p and size %zd",
              context, buffer, size);
    if (context == NULL || buffer == NULL) {
        LOG_ERROR("Null-Pointer passed");
        return TSS2_ESYS_RC_BAD_REFERENCE;
    }
    IESYS_CRYPTOGCRY_CONTEXT *mycontext = (IESYS_CRYPTOGCRY_CONTEXT *) context;
    if (mycontext->type != IESYS_CRYPTOGCRY_TYPE_HMAC) {
        LOG_ERROR("bad context");
        return TSS2_ESYS_RC_BAD_REFERENCE;
    }

    LOGBLOB_TRACE(buffer, size, "Updating hmac with");

    if (GPG_ERR_NO_ERROR != gcry_mac_write(mycontext->hmac.gcry_context, buffer, size)) {
        return_error(TSS2_ESYS_RC_GENERAL_FAILURE, "Gcrypt hmac update");
    }

    return TSS2_RC_SUCCESS;
}

/** Update and HMAC digest value from a TPM2B object.
 *
 * The context of a digest object will be updated according to the hash
 * algorithm and the key of the context.
 * @param[in,out] context The context of the digest object which will be updated.
 * @param[in] b The TPM2B object for the update.
 * @retval TSS2_RC_SUCCESS on success.
 * @retval TSS2_ESYS_RC_BAD_REFERENCE for invalid parameters.
 */
TSS2_RC
iesys_cryptogcry_hmac_update2b(IESYS_CRYPTO_CONTEXT_BLOB * context, TPM2B * b)
{
    LOG_TRACE("called for context-pointer %p and 2b-pointer %p", context, b);
    if (context == NULL || b == NULL) {
        LOG_ERROR("Null-Pointer passed");
        return TSS2_ESYS_RC_BAD_REFERENCE;
    }
    TSS2_RC ret = iesys_cryptogcry_hmac_update(context, &b->buffer[0], b->size);
    return ret;
}

/** Write the HMAC digest value to a byte buffer and close the context.
 *
 * The digest value will written to a passed buffer and the resources of the
 * HMAC object are released.
 * @param[in,out] context The context of the HMAC object.
 * @param[out] buffer The buffer for the digest value (caller-allocated).
 * @param[out] size The size of the digest.
 * @retval TSS2_RC_SUCCESS on success.
 * @retval TSS2_ESYS_RC_BAD_REFERENCE for invalid parameters.
 * @retval TSS2_ESYS_RC_BAD_SIZE If the size passed is lower than the HMAC length.
 * @retval TSS2_ESYS_RC_GENERAL_FAILURE for errors of the crypto library.
 */
TSS2_RC
iesys_cryptogcry_hmac_finish(IESYS_CRYPTO_CONTEXT_BLOB ** context,
                             uint8_t * buffer, size_t * size)
{
    LOG_TRACE("called for context-pointer %p, buffer %p and size-pointer %p",
              context, buffer, size);
    if (context == NULL || *context == NULL || buffer == NULL || size == NULL) {
        LOG_ERROR("Null-Pointer passed");
        return TSS2_ESYS_RC_BAD_REFERENCE;
    }
    IESYS_CRYPTOGCRY_CONTEXT *mycontext =
        (IESYS_CRYPTOGCRY_CONTEXT *) * context;
    if (mycontext->type != IESYS_CRYPTOGCRY_TYPE_HMAC) {
        LOG_ERROR("bad context");
        return TSS2_ESYS_RC_BAD_REFERENCE;
    }

    if (*size < mycontext->hmac.hmac_len) {
        LOG_ERROR("Buffer too small");
        return TSS2_ESYS_RC_BAD_SIZE;
    }

    TSS2_RC r = gcry_mac_read(mycontext->hmac.gcry_context, buffer, size);
    if (r != 0) {
        LOG_ERROR("GCry error.");
        return TSS2_ESYS_RC_GENERAL_FAILURE;
    }

    LOGBLOB_TRACE(buffer, *size, "read hmac result");

    gcry_mac_close(mycontext->hmac.gcry_context);

    free(mycontext);
    *context = NULL;

    return TSS2_RC_SUCCESS;
}

/** Write the HMAC digest value to a TPM2B object and close the context.
 *
 * The digest value will written to a passed TPM2B object and the resources of
 * the HMAC object are released.
 * @param[in,out] context The context of the HMAC object.
 * @param[out] hmac The buffer for the digest value (caller-allocated).
 * @retval TSS2_RC_SUCCESS on success.
 * @retval TSS2_ESYS_RC_BAD_REFERENCE for invalid parameters.
 * @retval TSS2_ESYS_RC_BAD_SIZE if the size passed is lower than the HMAC length.
 * @retval TSS2_ESYS_RC_GENERAL_FAILURE for errors of the crypto library.
 */
TSS2_RC
iesys_cryptogcry_hmac_finish2b(IESYS_CRYPTO_CONTEXT_BLOB ** context, TPM2B * hmac)
{
    LOG_TRACE("called for context-pointer %p and 2b-pointer %p", context, hmac);
    if (context == NULL || *context == NULL || hmac == NULL) {
        LOG_ERROR("Null-Pointer passed");
        return TSS2_ESYS_RC_BAD_REFERENCE;
    }
    size_t s = hmac->size;
    TSS2_RC ret = iesys_cryptogcry_hmac_finish(context, &hmac->buffer[0], &s);
    hmac->size = s;
    return ret;
}

/** Release the resources of an HAMC object.
 *
 * The assigned resources will be released and the context will be set to NULL.
 * @param[in,out] context The context of the HMAC object.
 */
void
iesys_cryptogcry_hmac_abort(IESYS_CRYPTO_CONTEXT_BLOB ** context)
{
    LOG_TRACE("called for context-pointer %p", context);
    if (context == NULL || *context == NULL) {
        LOG_DEBUG("Null-Pointer passed");
        return;
    }
    if (*context != NULL) {
        IESYS_CRYPTOGCRY_CONTEXT *mycontext =
            (IESYS_CRYPTOGCRY_CONTEXT *) * context;
        if (mycontext->type != IESYS_CRYPTOGCRY_TYPE_HMAC) {
            LOG_DEBUG("bad context");
            return;
        }

        gcry_mac_close(mycontext->hmac.gcry_context);

        free(mycontext);
        *context = NULL;
    }
}

/** Compute random TPM2B data.
 *
 * The random data will be generated and written to a passed TPM2B structure.
 * @param[out] nonce The TPM2B structure for the random data (caller-allocated).
 * @param[in] num_bytes The number of bytes to be generated.
 * @retval TSS2_RC_SUCCESS on success.
 */
TSS2_RC
iesys_cryptogcry_random2b(TPM2B_NONCE * nonce, size_t num_bytes)
{
    if (num_bytes == 0) {
        nonce->size = sizeof(TPMU_HA);
    } else {
        nonce->size = num_bytes;
    }
    /*
     * possible values for random level:
     *  GCRY_WEAK_RANDOM GCRY_STRONG_RANDOM  GCRY_VERY_STRONG_RANDOM
     */
    gcry_randomize(&nonce->buffer[0], nonce->size, GCRY_STRONG_RANDOM);
    return TSS2_RC_SUCCESS;
}

/** Encryption of a buffer using a public (RSA) key.
 *
 * Encrypting a buffer using a public key is used for example during
 * Esys_StartAuthSession in order to encrypt the salt value.
 * @param[in] key The key to be used for encryption.
 * @param[in] in_size The size of the buffer to be encrypted.
 * @param[in] in_buffer The data buffer to be encrypted.
 * @param[in] max_out_size The maximum size for the output encrypted buffer.
 * @param[out] out_buffer The encrypted buffer.
 * @param[out] out_size The size of the encrypted output.
 * @param[in] label The label used in the encryption scheme.
 * @retval TSS2_RC_SUCCESS on success
 * @retval TSS2_ESYS_RC_BAD_VALUE The algorithm of key is not implemented.
 * @retval TSS2_ESYS_RC_GENERAL_FAILURE The internal crypto engine failed.
 */
TSS2_RC
iesys_cryptogcry_pk_encrypt(TPM2B_PUBLIC * key,
                            size_t in_size,
                            BYTE * in_buffer,
                            size_t max_out_size,
                            BYTE * out_buffer,
                            size_t * out_size, const char *label)
{
    TSS2_RC r;
    gcry_error_t err;
    char *hash_alg;
    size_t lsize = 0;
    BYTE exponent[4] = { 0x00, 0x01, 0x00, 0x01 };
    char *padding;
    gcry_sexp_t sexp_data = NULL, sexp_key = NULL,
                sexp_cipher = NULL, sexp_cipher_a = NULL;
    gcry_mpi_t mpi_cipher = NULL;

    if (label != NULL)
        lsize = strlen(label) + 1;
    switch (key->publicArea.nameAlg) {
    case TPM2_ALG_SHA1:
        hash_alg = "sha1";
        break;
    case TPM2_ALG_SHA256:
        hash_alg = "sha256";
        break;
    default:
        LOG_ERROR("Hash alg not implemented");
        return TSS2_ESYS_RC_NOT_IMPLEMENTED;
    }
    switch (key->publicArea.parameters.rsaDetail.scheme.scheme) {
    case TPM2_ALG_NULL:
        padding = "raw";
        break;
    case TPM2_ALG_RSAES:
        padding = "pkcs1";
        break;
    case TPM2_ALG_OAEP:
        padding = "oaep";
        break;
    default:
        LOG_ERROR("Illegal RSA scheme");
        return TSS2_ESYS_RC_BAD_VALUE;
    }
    size_t offset = 0;
    UINT32 exp;
    if (key->publicArea.parameters.rsaDetail.exponent == 0)
        exp = 65537;
    else
        exp = key->publicArea.parameters.rsaDetail.exponent;
    r = Tss2_MU_UINT32_Marshal(exp, &exponent[0], sizeof(UINT32), &offset);
    if (r != TSS2_RC_SUCCESS) {
        LOG_ERROR("Marshaling");
        return r;
    }
    err = gcry_sexp_build(&sexp_data, NULL,
                          "(data (flags %s) (hash-algo %s) (label %b) (value %b) )",
                          padding, hash_alg, lsize, label, (int)in_size,
                          in_buffer);
    if (err != GPG_ERR_NO_ERROR) {
        LOG_ERROR("Function gcry_sexp_build");
        return TSS2_ESYS_RC_GENERAL_FAILURE;
    }
    err = gcry_sexp_build(&sexp_key, NULL, "(public-key (rsa (n %b) (e %b)))",
                          (int)key->publicArea.unique.rsa.size,
                          &key->publicArea.unique.rsa.buffer[0], 4, exponent);
    if (err != GPG_ERR_NO_ERROR) {
        goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE,
                   "Function gcry_sexp_build", cleanup);
    }
    err = gcry_pk_encrypt(&sexp_cipher, sexp_data, sexp_key);
    if (err != GPG_ERR_NO_ERROR) {
        fprintf (stderr, "Failure: %s/%s\n",
                 gcry_strsource (err),
                 gcry_strerror (err));
        goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE,
                   "Function gcry_pk_encrypt", cleanup);
    }
    sexp_cipher_a = gcry_sexp_find_token(sexp_cipher, "a", 0);
    mpi_cipher = gcry_sexp_nth_mpi(sexp_cipher_a, 1, GCRYMPI_FMT_USG);
    if (!mpi_cipher) {
        LOG_ERROR("Function gcry_sexp_nth_mpi");
        return TSS2_ESYS_RC_MEMORY;
    }
    err = mpi2bin(mpi_cipher, &out_buffer[0], key->publicArea.unique.rsa.size, max_out_size);
    if (err != GPG_ERR_NO_ERROR) {
        goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE,
                   "Function mpi2bin", cleanup);
    }

    *out_size = key->publicArea.unique.rsa.size;
    gcry_mpi_release(mpi_cipher);
    gcry_sexp_release(sexp_data);
    gcry_sexp_release(sexp_key);
    gcry_sexp_release(sexp_cipher);
    gcry_sexp_release(sexp_cipher_a);
    return TSS2_RC_SUCCESS;

cleanup:
    if (mpi_cipher)
        gcry_mpi_release(mpi_cipher);

    if (mpi_cipher)
        gcry_sexp_release(sexp_data);

    if (mpi_cipher)
        gcry_sexp_release(sexp_key);

    if (mpi_cipher)
        gcry_sexp_release(sexp_cipher);

    if (mpi_cipher)
        gcry_sexp_release(sexp_cipher_a);

    return r;
}

/** Computation of ephemeral ECC key and shared secret Z.
 *
 * According to the description in  TPM spec part 1 C 6.1 a shared secret
 * between application and TPM is computed (ECDH). An ephemeral ECC key and a
 * TPM keyare used for the ECDH key exchange.
 * @param[in] key The key to be used for ECDH key exchange.
 * @param[in] max_out_size the max size for the output of the public key of the
 *            computed ephemeral key.
 * @param[out] Z The computed shared secret.
 * @param[out] Q The public part of the ephemeral key in TPM format.
 * @param[out] out_buffer The public part of the ephemeral key will be marshaled
 *             to this buffer.
 * @param[out] out_size The size of the marshaled output.
 * @retval TSS2_RC_SUCCESS on success
 * @retval TSS2_ESYS_RC_BAD_VALUE The algorithm of key is not implemented.
 * @retval TSS2_ESYS_RC_GENERAL_FAILURE The internal crypto engine failed.
 */
TSS2_RC
iesys_cryptogcry_get_ecdh_point(TPM2B_PUBLIC *key,
                                size_t max_out_size,
                                TPM2B_ECC_PARAMETER *Z,
                                TPMS_ECC_POINT *Q,
                                BYTE * out_buffer,
                                size_t * out_size)
{
/*
 * Format strings for some gcrypt sexps have to be created with sprintf due to
 * a bug in libgcrypt. %s does not work in libgcypt with these sexps.
 */
#define SEXP_GENKEY_ECC  "(genkey (ecc (curve %s)))"
#define SEXP_ECC_POINT "(ecc (curve %s) (q.x  %sb) (q.y %sb))"

    TSS2_RC r;
    char *curveId;
    gcry_sexp_t mpi_tpm_sq = NULL;     /* sexp for public part of TPM  key*/
    gcry_sexp_t mpi_sd = NULL;         /* sexp for private part of ephemeral key */
    gcry_sexp_t mpi_s_pub_q = NULL;    /* sexp for public part of ephemeral key */
    gcry_mpi_point_t mpi_q = NULL;     /* public point of ephemeral key */
    gcry_mpi_point_t mpi_tpm_q = NULL; /* public point of TPM key */
    gcry_mpi_t mpi_d = NULL;           /* private part of ephemeral key */
    gcry_mpi_point_t mpi_qd = NULL;    /* result of mpi_tpm_q * mpi_d */
    gcry_ctx_t ctx = NULL;             /* context for ec curves */
    size_t offset = 0;
    gcry_mpi_t mpi_x = gcry_mpi_new(521);  /* big number for x coordinate */
    gcry_mpi_t mpi_y = gcry_mpi_new(521);  /* big number for y coordinate */
    size_t max_ecc_size;                   /* max size of ecc coordinate */

    /* Set libcrypt constant for curve type */
    switch (key->publicArea.parameters.eccDetail.curveID) {
    case TPM2_ECC_NIST_P192:
        curveId = "\"NIST P-192\"";
        max_ecc_size = (192+7)/8;
        break;
    case TPM2_ECC_NIST_P224:
        curveId = "\"NIST P-224\"";
        max_ecc_size = (224+7)/8;
        break;
    case TPM2_ECC_NIST_P256:
        curveId = "\"NIST P-256\"";
        max_ecc_size = (256+7)/8;
        break;
    case TPM2_ECC_NIST_P384:
        curveId = "\"NIST P-384\"";
        max_ecc_size = (384+7)/8;
        break;
    case TPM2_ECC_NIST_P521:
        curveId = "\"NIST P-521\"";
        max_ecc_size = (521+7)/8;
        break;
    default:
        LOG_ERROR("Illegal ECC curve ID");
        return TSS2_ESYS_RC_BAD_VALUE;
    }

    /* compute ephemeral ecc key */
    gcry_sexp_t ekey_spec = NULL, ekey_pair = NULL;
    { /* scope for sexp_ecc_key */
        char sexp_ecc_key [sizeof(SEXP_GENKEY_ECC)+strlen(curveId)
                           -1];  // -1 = (-2 for %s +1 for \0)

        if (sprintf(&sexp_ecc_key[0], SEXP_GENKEY_ECC, curveId) < 1) {
            goto_error(r, TSS2_ESYS_RC_MEMORY, "asprintf", cleanup);
        }

        if (gcry_sexp_build(&ekey_spec, NULL,
                            sexp_ecc_key) != GPG_ERR_NO_ERROR) {
            goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE, "gcry_sexp_build", cleanup);
        }
    }

    if (gcry_pk_genkey (&ekey_pair, ekey_spec) != GPG_ERR_NO_ERROR) {
        goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE, "Create ephemeral ecc key",
                   cleanup);
    }

    /* Get private ephemeral key d  */
    mpi_sd = gcry_sexp_find_token(ekey_pair, "d", 0);
    if (mpi_sd == NULL) {
        goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE,
                   "Get private part of ecc key", cleanup);
    }
    mpi_d = gcry_sexp_nth_mpi(mpi_sd, 1, GCRYMPI_FMT_USG);
    if (mpi_d == NULL) {
        goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE,
                   "Get private part of ecc key from sexp", cleanup);
    }

    /* Construct ephemeral public key */
    mpi_s_pub_q = gcry_sexp_find_token(ekey_pair, "public-key", 0);
    if (mpi_s_pub_q == NULL) {
        goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE, "Get public part ecc key",
                   cleanup);
    }

    if (gcry_mpi_ec_new (&ctx, mpi_s_pub_q, curveId) != GPG_ERR_NO_ERROR) {
        goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE, "Create ec", cleanup);
    }
    mpi_q =  gcry_mpi_ec_get_point ("q", ctx, 1);
    if (mpi_q == NULL) {
        goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE, "Get ecc point", cleanup);
    }

    /* Check whether point is on curve */
    if (!gcry_mpi_ec_curve_point(mpi_q, ctx)) {
        goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE, "Point not on curve", cleanup);
    }

    /* Store ephemeral public key in Q */
    if (gcry_mpi_ec_get_affine (mpi_x, mpi_y, mpi_q, ctx)) {
        goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE, "Point is at infinity",
                   cleanup);
    }

    if (mpi2bin(mpi_x, &Q->x.buffer[0], max_ecc_size, max_out_size)) {
        goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE, "Get x part of point",
                   cleanup);
    }

    if (mpi2bin(mpi_y, &Q->y.buffer[0], max_ecc_size, max_out_size)) {
        goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE, "Get y part of point",
                   cleanup);
    }

    Q->x.size = max_ecc_size;
    Q->y.size = max_ecc_size;
    gcry_ctx_release(ctx);

    { /* scope for sexp_point */

        /* Get public point from TPM key */
        char sexp_point [sizeof(SEXP_ECC_POINT) + strlen(curveId)
                         + key->publicArea.unique.ecc.x.size
                         + key->publicArea.unique.ecc.y.size
                         - 5];  /* -1 = (-4 for 2*%sb -2 for %s +1 for \0) */

        if (sprintf(&sexp_point[0], SEXP_ECC_POINT,
                    curveId, "%", "%") <1 ) {
            goto_error(r, TSS2_ESYS_RC_MEMORY, "asprintf", cleanup);
        }

        if ( gcry_sexp_build(&mpi_tpm_sq, NULL,
                              sexp_point,
                              key->publicArea.unique.ecc.x.size,
                              &key->publicArea.unique.ecc.x.buffer[0],
                              key->publicArea.unique.ecc.y.size,
                             &key->publicArea.unique.ecc.y.buffer[0])) {
            goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE,
                       "Function gcry_mpi_scan", cleanup);

        }
    }
    offset = 0;
    r = Tss2_MU_TPMS_ECC_POINT_Marshal(Q,  &out_buffer[0], max_out_size, &offset);
    return_if_error(r, "Error marshaling");
    *out_size = offset;

    /* Multiply d and Q */
    if (gcry_mpi_ec_new (&ctx, mpi_tpm_sq, curveId)) {
        goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE, "create ec curve", cleanup);
    }
    mpi_tpm_q =  gcry_mpi_ec_get_point ("q", ctx, 1);
    mpi_qd = gcry_mpi_point_new(256);
    gcry_mpi_ec_mul(mpi_qd , mpi_d, mpi_tpm_q, ctx);

    /* Store the x coordinate of d*Q in Z which will be used for KDFe */
    if (gcry_mpi_ec_get_affine (mpi_x, mpi_y, mpi_qd, ctx)) {
        goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE,
                   "Point is at infinity", cleanup);
    }

    if (mpi2bin(mpi_x, &Z->buffer[0], max_ecc_size, TPM2_MAX_ECC_KEY_BYTES)) {
        goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE,
                   "Get x coordinate d*Q", cleanup);
    }

    Z->size = max_ecc_size;
    LOGBLOB_DEBUG(&Z->buffer[0], Z->size, "Z (Q*d)");

 cleanup:
    if (ctx)
        gcry_ctx_release(ctx);

    if (mpi_x)
        gcry_mpi_release(mpi_x);

    if (mpi_y)
        gcry_mpi_release(mpi_y);

    if (mpi_d)
        gcry_mpi_release(mpi_d);

    if (mpi_sd)
        gcry_sexp_release(mpi_sd);

    if (mpi_tpm_q)
        gcry_mpi_point_release(mpi_tpm_q);

    if (mpi_qd)
        gcry_mpi_point_release(mpi_qd);

    if (mpi_q)
        gcry_mpi_point_release(mpi_q);

    if (mpi_tpm_sq)
        gcry_sexp_release(mpi_tpm_sq);

    if (mpi_s_pub_q)
        gcry_sexp_release(mpi_s_pub_q);

    if (ekey_spec)
        gcry_sexp_release(ekey_spec);

    if (ekey_pair)
        gcry_sexp_release(ekey_pair);

    return r;
}

/** Initialize AES context for encryption / decryption.
 *
 * @param[out] handle for AES context
 * @param[in] key key used for AES.
 * @param[in] tpm_sym_alg AES type in TSS2 notation.
 * @param[in] key_bits Key size in bits.
 * @param[in] tpm_mode Block cipher mode of opertion in TSS2 notation (CFB).
 *         For parameter encryption only CFB can be used.
 * @param[in] iv_len Length of initialization vector (iv) in byte.
 * @param[in] iv The initialization vector.
 * @retval TSS2_RC_SUCCESS on success, or TSS2_ESYS_RC_BAD_VALUE for invalid
 *         parameters, TSS2_ESYS_RC_GENERAL_FAILURE for errors of the crypto
 *         library.
 */
static TSS2_RC
iesys_cryptogcry_sym_aes_init(gcry_cipher_hd_t * cipher_hd,
                              uint8_t * key,
                              TPM2_ALG_ID tpm_sym_alg,
                              TPMI_AES_KEY_BITS key_bits,
                              TPM2_ALG_ID tpm_mode,
                              size_t iv_len, uint8_t * iv)
{

    LOGBLOB_TRACE(key, (key_bits + 7) / 8, "IESYS AES key");
    LOGBLOB_TRACE(iv, iv_len, "IESYS AES iv");
    int algo, mode, len;
    size_t key_len = 0;
    gcry_error_t err;
    TSS2_RC r = TSS2_RC_SUCCESS;

    switch (tpm_sym_alg) {
    case TPM2_ALG_AES:
        switch (key_bits) {
        case 128:
            algo = GCRY_CIPHER_AES128;
            len = 128;
            break;
        case 192:
            algo = GCRY_CIPHER_AES192;
            len = 192;
            break;
        case 256:
            algo = GCRY_CIPHER_AES256;
            len = 256;
            break;
        default:
            LOG_ERROR("Illegal key length.");
            return TSS2_ESYS_RC_BAD_VALUE;
        }
        switch (tpm_mode) {
        case TPM2_ALG_CFB:
            mode = GCRY_CIPHER_MODE_CFB;
            break;
        default:
            LOG_ERROR("Illegal symmetric algorithm.");
            return TSS2_ESYS_RC_BAD_VALUE;
        }
        break;
    default:
        LOG_ERROR("Illegal symmetric algorithm.");
        return TSS2_ESYS_RC_BAD_VALUE;
    }
    key_len = (len + 7) / 8;
    err = gcry_cipher_open(cipher_hd, algo, mode, 0);
    if (err != GPG_ERR_NO_ERROR) {
        LOG_ERROR("Opening gcrypt context");
        return TSS2_ESYS_RC_GENERAL_FAILURE;
    }
    if (iv_len != 0) {
        err = gcry_cipher_setiv(*cipher_hd, &iv[0], iv_len);
        if (err != GPG_ERR_NO_ERROR) {
            LOG_ERROR("Function gcry_cipher_setiv");
            gcry_cipher_close(*cipher_hd);
            r = TSS2_ESYS_RC_GENERAL_FAILURE;
        }
    }
    if (r == TSS2_RC_SUCCESS) {
        err = gcry_cipher_setkey(*cipher_hd, key, key_len);
        if (err != GPG_ERR_NO_ERROR) {
            LOG_ERROR("Function gcry_cipher_setkey");
            gcry_cipher_close(*cipher_hd);
            r = TSS2_ESYS_RC_GENERAL_FAILURE;
        }
    }
    return r;
}

/** Encrypt data with AES.
 *
 * @param[in] key key used for AES.
 * @param[in] tpm_sym_alg AES type in TSS2 notation (must be TPM2_ALG_AES).
 * @param[in] key_bits Key size in bits.
 * @param[in] tpm_mode Block cipher mode of opertion in TSS2 notation.
 * @param[in] blk_len Length Block length of AES.
 * @param[in,out] buffer Data to be encrypted. The encrypted date will be stored
 *                in this buffer.
 * @param[in] buffer_size size of data to be encrypted.
 * @param[in] iv The initialization vector. The size is equal to blk_len.
 * @retval TSS2_RC_SUCCESS on success, or TSS2_ESYS_RC_BAD_VALUE and
 * @retval TSS2_ESYS_RC_BAD_REFERENCE for invalid parameters,
 * @retval TSS2_ESYS_RC_GENERAL_FAILURE for errors of the crypto library.
 */
TSS2_RC
iesys_cryptogcry_sym_aes_encrypt(uint8_t * key,
                                 TPM2_ALG_ID tpm_sym_alg,
                                 TPMI_AES_KEY_BITS key_bits,
                                 TPM2_ALG_ID tpm_mode,
                                 size_t blk_len,
                                 uint8_t * buffer,
                                 size_t buffer_size,
                                 uint8_t * iv)
{
    gcry_cipher_hd_t cipher_hd;
    gcry_error_t err;
    TSS2_RC r;

    if (key == NULL || buffer == NULL) {
        LOG_ERROR("Bad reference");
        return TSS2_ESYS_RC_BAD_REFERENCE;
    }

    r = iesys_cryptogcry_sym_aes_init(&cipher_hd, key, tpm_sym_alg,
                                      key_bits, tpm_mode, blk_len, iv);
    if (r != TSS2_RC_SUCCESS)
        return r;
    LOGBLOB_TRACE(buffer, buffer_size, "IESYS AES input");
    err = gcry_cipher_encrypt(cipher_hd, buffer, buffer_size, NULL, 0);
    LOGBLOB_TRACE(buffer, buffer_size, "IESYS AES output");
    if (err != GPG_ERR_NO_ERROR) {
        LOG_ERROR("Function gcry_cipher_encrypt");
        r = TSS2_ESYS_RC_GENERAL_FAILURE;
    }
    gcry_cipher_close(cipher_hd);
    return r;
}

/** Decrypt data with AES.
 *
 * @param[in] key key used for AES.
 * @param[in] tpm_sym_alg AES type in TSS2 notation (must be TPM2_ALG_AES).
 * @param[in] key_bits Key size in bits.
 * @param[in] tpm_mode Block cipher mode of opertion in TSS2 notation (CFB).
 * @param[in] blk_len Length Block length of AES.
 * @param[in,out] buffer Data to be decrypted. The decrypted date will be stored
 *                in this buffer.
 * @param[in] buffer_size size of data to be encrypted.
 * @param[in] iv The initialization vector. The size is equal to blk_len.
 * @retval TSS2_RC_SUCCESS on success, or TSS2_ESYS_RC_BAD_VALUE and
 * @retval TSS2_ESYS_RC_BAD_REFERENCE for invalid parameters,
 * @retval TSS2_ESYS_RC_GENERAL_FAILURE for errors of the crypto library.
 */
TSS2_RC
iesys_cryptogcry_sym_aes_decrypt(uint8_t * key,
                                 TPM2_ALG_ID tpm_sym_alg,
                                 TPMI_AES_KEY_BITS key_bits,
                                 TPM2_ALG_ID tpm_mode,
                                 size_t blk_len,
                                 uint8_t * buffer,
                                 size_t buffer_size,
                                 uint8_t * iv)
{
    gcry_cipher_hd_t cipher_hd;
    gcry_error_t err;
    TSS2_RC r;

    if (key == NULL || buffer == NULL) {
        LOG_ERROR("Bad reference");
        return TSS2_ESYS_RC_BAD_REFERENCE;
    }

    if (tpm_sym_alg != TPM2_ALG_AES) {
        LOG_ERROR("AES expected");
        return TSS2_ESYS_RC_BAD_VALUE;
    }

    r = iesys_cryptogcry_sym_aes_init(&cipher_hd, key, tpm_sym_alg,
                                      key_bits, tpm_mode, blk_len, iv);
    if (r != TSS2_RC_SUCCESS)
        return r;
    err = gcry_cipher_decrypt(cipher_hd, buffer, buffer_size, NULL, 0);
    if (err != GPG_ERR_NO_ERROR) {
        LOG_ERROR("Function gcry_cipher_decrypt");
        r = TSS2_ESYS_RC_GENERAL_FAILURE;
    }
    gcry_cipher_close(cipher_hd);
    return r;
}

/** Initialize gcrypt crypto backend.
 *
 * Initialize gcrypt internal tables.
 *
 * @retval TSS2_RC_SUCCESS ong success.
 * @retval TSS2_ESYS_RC_GENERAL_FAILURE for version mismatch.
 */
TSS2_RC
iesys_cryptogcry_init() {
    if (!gcry_check_version (GCRYPT_VERSION))
    {
        LOG_ERROR("Version mismatch for gcrypt");
        return TSS2_ESYS_RC_GENERAL_FAILURE;
    }
    return TSS2_RC_SUCCESS;
}
