/* SPDX-FileCopyrightText: 2020, Andreas Droescher */
/* SPDX-License-Identifier: BSD-2-Clause */

#ifdef HAVE_CONFIG_H
#include "config.h" // IWYU pragma: keep
#endif

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

#include <mbedtls/aes.h>
#include <mbedtls/ctr_drbg.h>
#include <mbedtls/ecdh.h>
#include <mbedtls/entropy.h>
#include <mbedtls/md.h>
#include <mbedtls/rsa.h>

#include "tss2_esys.h"
#include "tss2_mu.h"
#include "util/aux_util.h"

#define LOGMODULE esys_crypto
#include "util/log.h"

#if MBEDTLS_VERSION_MAJOR > 2
#define MBEDTLS_ACCESS(member) MBEDTLS_PRIVATE(member)
#else
#define MBEDTLS_ACCESS(member) member
#endif

/** Context to hold temporary values for iesys_crypto */
typedef struct ESYS_CRYPTO_CONTEXT_BLOB {
    enum {
        IESYS_CRYPTMBED_TYPE_HASH = 1,
        IESYS_CRYPTMBED_TYPE_HMAC,
    } type; /**< The type of context to hold; hash or hmac */
    union {
        struct {
            mbedtls_md_context_t mbed_context;
            size_t               hash_len;
        } hash; /**< the state variables for a hash context */
        struct {
            mbedtls_md_context_t mbed_context;
            size_t               hmac_len;
        } hmac; /**< the state variables for an hmac context */
    };
} IESYS_CRYPTMBED_CONTEXT;

/** 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_cryptmbed_hash_start(ESYS_CRYPTO_CONTEXT_BLOB **context,
                           TPM2_ALG_ID                hashAlg,
                           void                      *userdata) {
    UNUSED(userdata);

    TSS2_RC                  r = TSS2_RC_SUCCESS;
    const mbedtls_md_info_t *md_info = NULL;

    if (context == NULL) {
        return_error(TSS2_ESYS_RC_BAD_REFERENCE, "Null-Pointer passed in for context");
    }
    IESYS_CRYPTMBED_CONTEXT *mycontext = calloc(1, sizeof(IESYS_CRYPTMBED_CONTEXT));
    return_if_null(mycontext, "Out of Memory", TSS2_ESYS_RC_MEMORY);

    mbedtls_md_init(&mycontext->hash.mbed_context);

    switch (hashAlg) {
    case TPM2_ALG_SHA1:
        md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA1);
        break;
    case TPM2_ALG_SHA256:
        md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
        break;
    case TPM2_ALG_SHA384:
        md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA384);
        break;
    case TPM2_ALG_SHA512:
        md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512);
        break;
    }

    if (md_info == NULL) {
        goto_error(r, TSS2_ESYS_RC_NOT_IMPLEMENTED, "Unsupported hash algorithm (%" PRIu16 ")",
                   cleanup, hashAlg);
    }

    mycontext->hash.hash_len = mbedtls_md_get_size(md_info);

    if (mbedtls_md_setup(&mycontext->hash.mbed_context, md_info, true) != 0) {
        goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE, "MBED HASH setup", cleanup);
    }

    if (mbedtls_md_starts(&mycontext->hash.mbed_context) != 0) {
        goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE, "MBED HASH start", cleanup);
    }

    mycontext->type = IESYS_CRYPTMBED_TYPE_HASH;

    *context = (ESYS_CRYPTO_CONTEXT_BLOB *)mycontext;

    return TSS2_RC_SUCCESS;

cleanup:
    mbedtls_md_free(&mycontext->hash.mbed_context);
    SAFE_FREE(mycontext);
    return r;
}

/** 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_cryptmbed_hash_update(ESYS_CRYPTO_CONTEXT_BLOB *context,
                            const uint8_t            *buffer,
                            size_t                    size,
                            void                     *userdata) {
    UNUSED(userdata);

    if (context == NULL || buffer == NULL) {
        return_error(TSS2_ESYS_RC_BAD_REFERENCE, "Null-Pointer passed");
    }
    IESYS_CRYPTMBED_CONTEXT *mycontext = (IESYS_CRYPTMBED_CONTEXT *)context;
    if (mycontext->type != IESYS_CRYPTMBED_TYPE_HASH) {
        return_error(TSS2_ESYS_RC_BAD_REFERENCE, "bad context");
    }

    if (mbedtls_md_update(&mycontext->hash.mbed_context, buffer, size) != 0) {
        return_error(TSS2_ESYS_RC_GENERAL_FAILURE, "MBED HASH update");
    }

    return TSS2_RC_SUCCESS;
}

/** 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_cryptmbed_hash_finish(ESYS_CRYPTO_CONTEXT_BLOB **context,
                            uint8_t                   *buffer,
                            size_t                    *size,
                            void                      *userdata) {
    UNUSED(userdata);

    TSS2_RC r = TSS2_RC_SUCCESS;

    if (context == NULL || *context == NULL || buffer == NULL || size == NULL) {
        return_error(TSS2_ESYS_RC_BAD_REFERENCE, "Null-Pointer passed");
    }
    IESYS_CRYPTMBED_CONTEXT *mycontext = (IESYS_CRYPTMBED_CONTEXT *)*context;
    if (mycontext->type != IESYS_CRYPTMBED_TYPE_HASH) {
        return_error(TSS2_ESYS_RC_BAD_REFERENCE, "bad context");
    }

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

    if (mbedtls_md_finish(&mycontext->hmac.mbed_context, buffer) != 0) {
        goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE, "MBED HASH finish", cleanup);
    }

    *size = mycontext->hash.hash_len;

cleanup:
    mbedtls_md_free(&mycontext->hash.mbed_context);
    SAFE_FREE(mycontext);
    *context = NULL;

    return r;
}

/** 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_cryptmbed_hash_abort(ESYS_CRYPTO_CONTEXT_BLOB **context, void *userdata) {
    UNUSED(userdata);

    if (context == NULL || *context == NULL) {
        return;
    }

    IESYS_CRYPTMBED_CONTEXT *mycontext = (IESYS_CRYPTMBED_CONTEXT *)*context;
    if (mycontext->type != IESYS_CRYPTMBED_TYPE_HASH) {
        return;
    }

    mbedtls_md_free(&mycontext->hash.mbed_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_cryptmbed_hmac_start(ESYS_CRYPTO_CONTEXT_BLOB **context,
                           TPM2_ALG_ID                hashAlg,
                           const uint8_t             *key,
                           size_t                     size,
                           void                      *userdata) {
    UNUSED(userdata);

    TSS2_RC                  r = TSS2_RC_SUCCESS;
    const mbedtls_md_info_t *md_info = NULL;

    if (context == NULL || key == NULL) {
        return_error(TSS2_ESYS_RC_BAD_REFERENCE, "Null-Pointer passed in for context");
    }
    IESYS_CRYPTMBED_CONTEXT *mycontext = calloc(1, sizeof(IESYS_CRYPTMBED_CONTEXT));
    return_if_null(mycontext, "Out of Memory", TSS2_ESYS_RC_MEMORY);

    mbedtls_md_init(&mycontext->hash.mbed_context);

    switch (hashAlg) {
    case TPM2_ALG_SHA1:
        md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA1);
        break;
    case TPM2_ALG_SHA256:
        md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
        break;
    case TPM2_ALG_SHA384:
        md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA384);
        break;
    case TPM2_ALG_SHA512:
        md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512);
        break;
    }

    if (md_info == NULL) {
        goto_error(r, TSS2_ESYS_RC_NOT_IMPLEMENTED, "Unsupported hash algorithm (%" PRIu16 ")",
                   cleanup, hashAlg);
    }

    mycontext->hmac.hmac_len = mbedtls_md_get_size(md_info);

    if (mbedtls_md_setup(&mycontext->hash.mbed_context, md_info, true) != 0) {
        goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE, "MBED HMAC setup", cleanup);
    }

    if (mbedtls_md_hmac_starts(&mycontext->hash.mbed_context, key, size) != 0) {
        goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE, "MBED HMAC start", cleanup);
    }

    mycontext->type = IESYS_CRYPTMBED_TYPE_HMAC;

    *context = (ESYS_CRYPTO_CONTEXT_BLOB *)mycontext;

    return TSS2_RC_SUCCESS;

cleanup:
    mbedtls_md_free(&mycontext->hmac.mbed_context);
    SAFE_FREE(mycontext);
    return r;
}

/** 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_cryptmbed_hmac_update(ESYS_CRYPTO_CONTEXT_BLOB *context,
                            const uint8_t            *buffer,
                            size_t                    size,
                            void                     *userdata) {
    UNUSED(userdata);

    if (context == NULL || buffer == NULL) {
        return_error(TSS2_ESYS_RC_BAD_REFERENCE, "Null-Pointer passed");
    }
    IESYS_CRYPTMBED_CONTEXT *mycontext = (IESYS_CRYPTMBED_CONTEXT *)context;
    if (mycontext->type != IESYS_CRYPTMBED_TYPE_HMAC) {
        return_error(TSS2_ESYS_RC_BAD_REFERENCE, "bad context");
    }

    if (mbedtls_md_hmac_update(&mycontext->hmac.mbed_context, buffer, size) != 0) {
        return_error(TSS2_ESYS_RC_GENERAL_FAILURE, "MBED HMAC update");
    }

    return TSS2_RC_SUCCESS;
}

/** 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_cryptmbed_hmac_finish(ESYS_CRYPTO_CONTEXT_BLOB **context,
                            uint8_t                   *buffer,
                            size_t                    *size,
                            void                      *userdata) {
    UNUSED(userdata);

    TSS2_RC r = TSS2_RC_SUCCESS;

    if (context == NULL || *context == NULL || buffer == NULL || size == NULL) {
        return_error(TSS2_ESYS_RC_BAD_REFERENCE, "Null-Pointer passed");
    }
    IESYS_CRYPTMBED_CONTEXT *mycontext = (IESYS_CRYPTMBED_CONTEXT *)*context;
    if (mycontext->type != IESYS_CRYPTMBED_TYPE_HMAC) {
        return_error(TSS2_ESYS_RC_BAD_REFERENCE, "bad context");
    }

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

    if (mbedtls_md_hmac_finish(&mycontext->hmac.mbed_context, buffer) != 0) {
        goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE, "MBED HMAC finish", cleanup);
    }

    *size = mycontext->hmac.hmac_len;

cleanup:
    mbedtls_md_free(&mycontext->hmac.mbed_context);
    SAFE_FREE(mycontext);
    *context = NULL;
    return r;
}

/** 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_cryptmbed_hmac_abort(ESYS_CRYPTO_CONTEXT_BLOB **context, void *userdata) {
    UNUSED(userdata);

    if (context == NULL || *context == NULL) {
        return;
    }

    IESYS_CRYPTMBED_CONTEXT *mycontext = (IESYS_CRYPTMBED_CONTEXT *)*context;
    if (mycontext->type != IESYS_CRYPTMBED_TYPE_HMAC) {
        return;
    }

    mbedtls_md_free(&mycontext->hmac.mbed_context);
    free(mycontext);
    *context = NULL;
}

/** Wrapper for mbedtls random number generator
 *
 * @param[in] context Optional unused parameter.
 * @param[out] buffer Buffer to write randomness to.
 * @param[om] buf_size Number of bytes to write to buffer.
 */
static int
get_random(void *context, unsigned char *buffer, size_t buf_size) {
    UNUSED(context);

    int r = 0; // success in terms of mbedtls

    mbedtls_entropy_context entropy_context;
    mbedtls_entropy_init(&entropy_context);

    mbedtls_ctr_drbg_context drbg_context;
    mbedtls_ctr_drbg_init(&drbg_context);

    if (mbedtls_ctr_drbg_seed(&drbg_context, mbedtls_entropy_func, &entropy_context, NULL, 0)
        != 0) {
        goto_error(r, MBEDTLS_ERR_CTR_DRBG_ENTROPY_SOURCE_FAILED,
                   "Could not seed random number generator.", cleanup);
    }

    if (mbedtls_ctr_drbg_random(&drbg_context, &buffer[0], buf_size) != 0) {
        goto_error(r, MBEDTLS_ERR_CTR_DRBG_REQUEST_TOO_BIG, "Failure in random number generator.",
                   cleanup);
    }

cleanup:
    mbedtls_ctr_drbg_free(&drbg_context);
    mbedtls_entropy_free(&entropy_context);
    return r;
}

/** 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.
 *
 * NOTE: the TPM should not be used to obtain the random data
 */
TSS2_RC
iesys_cryptmbed_random2b(TPM2B_NONCE *nonce, size_t num_bytes, void *userdata) {
    UNUSED(userdata);

    if (num_bytes == 0) {
        nonce->size = sizeof(nonce->buffer);
    } else {
        nonce->size = num_bytes;
    }

    if (get_random(NULL, &nonce->buffer[0], nonce->size) != 0) {
        return_error(TSS2_ESYS_RC_GENERAL_FAILURE, "Hash algorithm not supported");
    }

    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_cryptmbed_pk_encrypt(TPM2B_PUBLIC *pub_tpm_key,
                           size_t        in_size,
                           BYTE         *in_buffer,
                           size_t        max_out_size,
                           BYTE         *out_buffer,
                           size_t       *out_size,
                           const char   *label,
                           void         *userdata) {
    UNUSED(userdata);

    TSS2_RC             r = TSS2_RC_SUCCESS;
    int                 hash_id = 0;
    mbedtls_rsa_context rsa_context;

    switch (pub_tpm_key->publicArea.nameAlg) {
    case TPM2_ALG_SHA1:
        hash_id = MBEDTLS_MD_SHA1;
        break;
    case TPM2_ALG_SHA256:
        hash_id = MBEDTLS_MD_SHA256;
        break;
    case TPM2_ALG_SHA384:
        hash_id = MBEDTLS_MD_SHA384;
        break;
    case TPM2_ALG_SHA512:
        hash_id = MBEDTLS_MD_SHA512;
        break;
    default:
        return_error(TSS2_ESYS_RC_NOT_IMPLEMENTED, "Hash algorithm not supported");
    }

#if MBEDTLS_VERSION_MAJOR > 2
    mbedtls_rsa_init(&rsa_context);
    mbedtls_rsa_set_padding(&rsa_context, MBEDTLS_RSA_PKCS_V21, hash_id);
#else
    mbedtls_rsa_init(&rsa_context, MBEDTLS_RSA_PKCS_V21, hash_id);
#endif

    if (pub_tpm_key->publicArea.unique.rsa.size == 0) {
        goto_error(r, TSS2_ESYS_RC_BAD_VALUE, "Public key size may not be 0", cleanup);
    }

    UINT8  exp[sizeof(UINT32)] = { 0x00, 0x01, 0x00, 0x01 }; // big-endian 65537
    UINT32 exp_as_int = pub_tpm_key->publicArea.parameters.rsaDetail.exponent;
    if (exp_as_int != 0) {
        exp[0] = (exp_as_int >> 24) & 0xff;
        exp[1] = (exp_as_int >> 16) & 0xff;
        exp[2] = (exp_as_int >> 8) & 0xff;
        exp[3] = (exp_as_int >> 0) & 0xff;
    }

    if (mbedtls_rsa_import_raw(&rsa_context, pub_tpm_key->publicArea.unique.rsa.buffer,
                               pub_tpm_key->publicArea.unique.rsa.size, NULL /* p */, 0,
                               NULL /* q */, 0, NULL /* d */, 0, &exp[0], sizeof(UINT32))
        != 0) {
        goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE, "Could not import public key", cleanup);
    }

    if (mbedtls_rsa_complete(&rsa_context) != 0) {
        goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE, "Could not complete key import", cleanup);
    }

    if (mbedtls_rsa_get_len(&rsa_context) > max_out_size) {
        goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE, "Encrypted data too big", cleanup);
    }

    switch (pub_tpm_key->publicArea.parameters.rsaDetail.scheme.scheme) {
    case TPM2_ALG_RSAES:
        if (mbedtls_rsa_pkcs1_encrypt(&rsa_context, get_random, NULL,
#if MBEDTLS_VERSION_MAJOR <= 2
                                      MBEDTLS_RSA_PUBLIC,
#endif
                                      in_size, in_buffer, out_buffer)
            != 0) {
            goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE, "Could not encrypt data.", cleanup);
        }
        break;
    case TPM2_ALG_OAEP:
        if (mbedtls_rsa_rsaes_oaep_encrypt(&rsa_context, get_random, NULL,
#if MBEDTLS_VERSION_MAJOR <= 2
                                           MBEDTLS_RSA_PUBLIC,
#endif
                                           (const UINT8 *)label, strlen(label) + 1, in_size,
                                           in_buffer, out_buffer)
            != 0) {
            goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE, "Could not encrypt data.", cleanup);
        }
        break;
    default:
        goto_error(r, TSS2_ESYS_RC_BAD_VALUE, "Illegal RSA scheme", cleanup);
        break;
    }
    *out_size = mbedtls_rsa_get_len(&rsa_context);

    r = TSS2_RC_SUCCESS;

cleanup:
    mbedtls_rsa_free(&rsa_context);
    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 key are 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_cryptmbed_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,
                               void                *userdata) {
    UNUSED(userdata);

    TSS2_RC           r = TSS2_RC_SUCCESS;
    mbedtls_ecp_group ecp_group;
    mbedtls_mpi       ecp_d;
    mbedtls_ecp_point ecp_Q;
    mbedtls_ecp_point ecp_Qp;
    mbedtls_mpi       ecp_z;

    mbedtls_ecp_group_init(&ecp_group);
    mbedtls_mpi_init(&ecp_d);
    mbedtls_ecp_point_init(&ecp_Q);
    mbedtls_ecp_point_init(&ecp_Qp);
    mbedtls_mpi_init(&ecp_z);

    /* Load named curve */
    switch (key->publicArea.parameters.eccDetail.curveID) {
    case TPM2_ECC_NIST_P192:
        if (mbedtls_ecp_group_load(&ecp_group, MBEDTLS_ECP_DP_SECP192R1) != 0) {
            goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE, "ECP group load failed", cleanup);
        }
        break;
    case TPM2_ECC_NIST_P224:
        if (mbedtls_ecp_group_load(&ecp_group, MBEDTLS_ECP_DP_SECP224R1) != 0) {
            goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE, "ECP group load failed", cleanup);
        }
        break;
    case TPM2_ECC_NIST_P256:
        if (mbedtls_ecp_group_load(&ecp_group, MBEDTLS_ECP_DP_SECP256R1) != 0) {
            goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE, "ECP group load failed", cleanup);
        }
        break;
    case TPM2_ECC_NIST_P384:
        if (mbedtls_ecp_group_load(&ecp_group, MBEDTLS_ECP_DP_SECP384R1) != 0) {
            goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE, "ECP group load failed", cleanup);
        }
        break;
    case TPM2_ECC_NIST_P521:
        if (mbedtls_ecp_group_load(&ecp_group, MBEDTLS_ECP_DP_SECP521R1) != 0) {
            goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE, "ECP group load failed", cleanup);
        }
        break;
    default:
        return_error(TSS2_ESYS_RC_NOT_IMPLEMENTED, "ECC curve not implemented.");
    }

    /* Generate ephemeral key */
    if (mbedtls_ecdh_gen_public(&ecp_group, &ecp_d, &ecp_Q, get_random, NULL) != 0) {
        goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE, "ECDH gen failed", cleanup);
    }

    /* Write affine coordinates of ephemeral pub key to TPM point Q
     *
     * Note
     * mpi_write_bin prepends numbers with 0 if they are smaller as
     * TPM2_MAX_ECC_KEY_BYTES. This is for big-endian mathematicaly
     * correct but breaks the interface if we want to set bignum.size
     * to anything but TPM2_MAX_ECC_KEY_BYTES. The easiest fix is
     * to shrink out_size to mpi_size.
     */

    Q->x.size = mbedtls_mpi_size(&ecp_Q.MBEDTLS_ACCESS(X));
    if (Q->x.size > TPM2_MAX_ECC_KEY_BYTES) {
        goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE, "Write Q.x does not fit into byte buffer",
                   cleanup);
    }
    if (mbedtls_mpi_write_binary(&ecp_Q.MBEDTLS_ACCESS(X), &Q->x.buffer[0], Q->x.size) != 0) {
        goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE, "Write Q.x to byte buffer failed", cleanup);
    }

    Q->y.size = mbedtls_mpi_size(&ecp_Q.MBEDTLS_ACCESS(Y));
    if (Q->y.size > TPM2_MAX_ECC_KEY_BYTES) {
        goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE, "Write Q.y does not fit into byte buffer",
                   cleanup);
    }
    if (mbedtls_mpi_write_binary(&ecp_Q.MBEDTLS_ACCESS(Y), &Q->y.buffer[0], Q->y.size) != 0) {
        goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE, "Write Q.y to byte buffer failed", cleanup);
    }

    /* Initialise Qp.Z (Qp would be zero, or "at infinity", if Z == 0) */
    if (mbedtls_mpi_lset(&ecp_Qp.MBEDTLS_ACCESS(Z), 1) != 0) {
        goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE, "Init of Qp.z to 1 failed", cleanup);
    }

    /* Read ephemeral pub key from TPM to Qp */
    if (mbedtls_mpi_read_binary(&ecp_Qp.MBEDTLS_ACCESS(X), &key->publicArea.unique.ecc.x.buffer[0],
                                key->publicArea.unique.ecc.x.size)
        != 0) {
        goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE, "Read of Qp.x from byte buffer failed",
                   cleanup);
    }
    if (mbedtls_mpi_read_binary(&ecp_Qp.MBEDTLS_ACCESS(Y), &key->publicArea.unique.ecc.y.buffer[0],
                                key->publicArea.unique.ecc.y.size)
        != 0) {
        goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE, "Read of Qp.y from byte buffer failed",
                   cleanup);
    }

    /* Validate TPM's pub key */
    if (mbedtls_ecp_check_pubkey(&ecp_group, &ecp_Qp) != 0) {
        goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE, "Point Qp is invalid", cleanup);
    }

    /* Calculate shared secret */
    if (mbedtls_ecdh_compute_shared(&ecp_group, &ecp_z, &ecp_Qp, &ecp_d, get_random, NULL) != 0) {
        goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE, "ECDH compute shared failed", cleanup);
    }

    /* Write shared secret to TPM ECC param Z */
    Z->size = mbedtls_mpi_size(&ecp_z);
    if (Z->size > TPM2_MAX_ECC_KEY_BYTES) {
        goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE, "Write Q.y does not fit into byte buffer",
                   cleanup);
    }
    if (mbedtls_mpi_write_binary(&ecp_z, &Z->buffer[0], Z->size) != 0) {
        goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE, "Write Z to byte buffer failed", cleanup);
    }

    /* Write the public ephemeral key in TPM format to out buffer */
    size_t offset = 0;
    r = Tss2_MU_TPMS_ECC_POINT_Marshal(Q, &out_buffer[0], max_out_size, &offset);
    goto_if_error(r, "Error marshaling Q", cleanup);
    *out_size = offset;

cleanup:
    mbedtls_mpi_free(&ecp_z);
    mbedtls_ecp_point_free(&ecp_Qp);
    mbedtls_ecp_point_free(&ecp_Q);
    mbedtls_mpi_free(&ecp_d);
    mbedtls_ecp_group_free(&ecp_group);
    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 (CFB).
 *            For parameter encryption only CFB can be used.
 * @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.
 * @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_cryptmbed_sym_aes_encrypt(uint8_t          *key,
                                TPM2_ALG_ID       tpm_sym_alg,
                                TPMI_AES_KEY_BITS key_bits,
                                TPM2_ALG_ID       tpm_mode,
                                uint8_t          *buffer,
                                size_t            buffer_size,
                                uint8_t          *iv,
                                void             *userdata) {
    UNUSED(userdata);

    TSS2_RC             r = TSS2_RC_SUCCESS;
    mbedtls_aes_context aes_ctx;

    if (key == NULL || buffer == NULL) {
        return_error(TSS2_ESYS_RC_BAD_REFERENCE, "Bad reference");
    }

    mbedtls_aes_init(&aes_ctx);

    if (tpm_sym_alg != TPM2_ALG_AES || tpm_mode != TPM2_ALG_CFB) {
        goto_error(r, TSS2_ESYS_RC_BAD_VALUE, "AES encrypt called with wrong algorithm.", cleanup);
    }

    if (mbedtls_aes_setkey_enc(&aes_ctx, key, key_bits) != 0) {
        goto_error(r, TSS2_ESYS_RC_BAD_VALUE, "Key size not not implemented.", cleanup);
    }

    size_t iv_off = 0;
    if (mbedtls_aes_crypt_cfb128(&aes_ctx, MBEDTLS_AES_ENCRYPT, buffer_size, &iv_off, iv, buffer,
                                 buffer)
        != 0) {
        goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE, "Enncrypt", cleanup);
    }

cleanup:
    mbedtls_aes_free(&aes_ctx);
    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).
 *            For parameter encryption only CFB can be used.
 * @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.
 * @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_cryptmbed_sym_aes_decrypt(uint8_t          *key,
                                TPM2_ALG_ID       tpm_sym_alg,
                                TPMI_AES_KEY_BITS key_bits,
                                TPM2_ALG_ID       tpm_mode,
                                uint8_t          *buffer,
                                size_t            buffer_size,
                                uint8_t          *iv,
                                void             *userdata) {
    UNUSED(userdata);

    TSS2_RC             r = TSS2_RC_SUCCESS;
    mbedtls_aes_context aes_ctx;

    if (key == NULL || buffer == NULL) {
        return_error(TSS2_ESYS_RC_BAD_REFERENCE, "Bad reference");
    }

    mbedtls_aes_init(&aes_ctx);

    if (tpm_sym_alg != TPM2_ALG_AES || tpm_mode != TPM2_ALG_CFB) {
        goto_error(r, TSS2_ESYS_RC_BAD_VALUE, "AES encrypt called with wrong algorithm.", cleanup);
    }

    /* Note in mbedTLS Documentation:
     * For CFB, you must set up the context with mbedtls_aes_setkey_enc(),
     * regardless of whether you are performing an encryption or decryption
     * operation, that is, regardless of the mode parameter. This is because
     * CFB mode uses the same key schedule for encryption and decryption.
     */
    if (mbedtls_aes_setkey_enc(&aes_ctx, key, key_bits) != 0) {
        goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE, "Key size not not implemented.", cleanup);
    }

    size_t iv_off = 0;
    if (mbedtls_aes_crypt_cfb128(&aes_ctx, MBEDTLS_AES_DECRYPT, buffer_size, &iv_off, iv, buffer,
                                 buffer)
        != 0) {
        goto_error(r, TSS2_ESYS_RC_GENERAL_FAILURE, "Dencrypt", cleanup);
    }

cleanup:
    mbedtls_aes_free(&aes_ctx);
    return r;
}

TSS2_RC
iesys_cryptmbed_init(void *userdata) {
    UNUSED(userdata);

    return TSS2_RC_SUCCESS;
}
