/* SPDX-License-Identifier: BSD-3-Clause */

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

#include <tss2/tss2_mu.h>

#include <openssl/rand.h>

#include "log.h"
#include "tpm2_alg_util.h"
#include "tpm2_convert.h"
#include "tpm2_identity_util.h"
#include "tpm2_kdfa.h"
#include "tpm2_kdfe.h"
#include "tpm2_openssl.h"

// Identity-related functionality that the TPM normally does, but using OpenSSL

static TPM2_KEY_BITS get_pub_asym_key_bits(TPM2B_PUBLIC *public) {

    TPMU_PUBLIC_PARMS *p = &public->publicArea.parameters;
    switch (public->publicArea.type) {
    case TPM2_ALG_ECC:
        /* fall-thru */
    case TPM2_ALG_RSA:
        return p->asymDetail.symmetric.keyBits.sym;
        /* no default */
    }

    return 0;
}

static bool share_secret_with_tpm2_rsa_public_key(TPM2B_DIGEST *protection_seed,
        TPM2B_PUBLIC *parent_pub, const unsigned char *label, int label_len,
        TPM2B_ENCRYPTED_SECRET *encrypted_protection_seed) {
    bool rval = false;
    EVP_PKEY_CTX *ctx = NULL;

    EVP_PKEY *pkey = convert_pubkey_RSA(&parent_pub->publicArea);
    if (pkey == NULL) {
        LOG_ERR("Failed to retrieve public key");
        return false;
    }

    TPMI_ALG_HASH parent_name_alg = parent_pub->publicArea.nameAlg;

    /*
     * RSA Secret Sharing uses a randomly generated seed (Part 1, B.10.3).
     */
    protection_seed->size = tpm2_alg_util_get_hash_size(parent_name_alg);
    int rc = RAND_bytes(protection_seed->buffer, protection_seed->size);
    if (rc != 1) {
        LOG_ERR("Failed to get random bytes");
        goto error;
    }

    /*
     * The seed value will be OAEP encrypted with a given L parameter.
     */
    ctx = EVP_PKEY_CTX_new(pkey, NULL);
    if (!ctx) {
        LOG_ERR("Failed EVP_PKEY_CTX_new");
        goto error;
    }

    rc = EVP_PKEY_encrypt_init(ctx);
    if (rc <= 0) {
        LOG_ERR("Failed EVP_PKEY_encrypt_init");
        goto error;
    }

    rc = EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING);
    if (rc <= 0) {
        LOG_ERR("Failed EVP_PKEY_CTX_set_rsa_padding");
        goto error;
    }

    rc = EVP_PKEY_CTX_set_rsa_oaep_md(ctx,
            tpm2_openssl_md_from_tpmhalg(parent_name_alg));
    if (rc <= 0) {
        LOG_ERR("Failed EVP_PKEY_CTX_set_rsa_oaep_md");
        goto error;
    }

    // the library will take ownership of the label
    char *newlabel = strdup((const char *)label);
    if (newlabel == NULL) {
        LOG_ERR("Failed to allocate label");
        goto error;
    }

    rc = EVP_PKEY_CTX_set0_rsa_oaep_label(ctx, newlabel, label_len);
    if (rc <= 0) {
        LOG_ERR("Failed EVP_PKEY_CTX_set0_rsa_oaep_label");
        free(newlabel);
        goto error;
    }

    size_t outlen = sizeof(TPMU_ENCRYPTED_SECRET);
    if (EVP_PKEY_encrypt(ctx, encrypted_protection_seed->secret, &outlen,
            protection_seed->buffer, protection_seed->size) <= 0) {
        LOG_ERR("Failed EVP_PKEY_encrypt\n");
        goto error;
    }
    encrypted_protection_seed->size = outlen;
    rval = true;

error:
    EVP_PKEY_CTX_free(ctx);
    EVP_PKEY_free(pkey);
    return rval;
}

bool tpm2_identity_util_calc_outer_integrity_hmac_key_and_dupsensitive_enc_key(
        TPM2B_PUBLIC *parent_pub, TPM2B_NAME *pubname,
        TPM2B_DIGEST *protection_seed, TPM2B_MAX_BUFFER *protection_hmac_key,
        TPM2B_MAX_BUFFER *protection_enc_key) {

    TPM2B null_2b = { .size = 0 };

    TPMI_ALG_HASH parent_alg = parent_pub->publicArea.nameAlg;
    UINT16 parent_hash_size = tpm2_alg_util_get_hash_size(parent_alg);

    TSS2_RC rval = tpm2_kdfa(parent_alg, (TPM2B *) protection_seed, "INTEGRITY",
            &null_2b, &null_2b, parent_hash_size * 8, protection_hmac_key);
    if (rval != TPM2_RC_SUCCESS) {
        return false;
    }

    TPM2_KEY_BITS pub_key_bits = get_pub_asym_key_bits(parent_pub);

    rval = tpm2_kdfa(parent_alg, (TPM2B *) protection_seed, "STORAGE",
            (TPM2B *) pubname, &null_2b, pub_key_bits, protection_enc_key);
    if (rval != TPM2_RC_SUCCESS) {
        return false;
    }

    return true;
}

bool tpm2_identity_util_share_secret_with_public_key(
        TPM2B_DIGEST *protection_seed, TPM2B_PUBLIC *parent_pub,
        const unsigned char *label, int label_len,
        TPM2B_ENCRYPTED_SECRET *encrypted_protection_seed) {
    bool result = false;
    TPMI_ALG_PUBLIC alg = parent_pub->publicArea.type;

    switch (alg) {
    case TPM2_ALG_RSA:
        result = share_secret_with_tpm2_rsa_public_key(protection_seed,
                parent_pub, label, label_len, encrypted_protection_seed);
        break;
    case TPM2_ALG_ECC:
        result = ecdh_derive_seed_and_encrypted_seed(parent_pub,
                label, label_len,
                protection_seed, encrypted_protection_seed);
        break;
    default:
        LOG_ERR("Cannot handle algorithm, got: %s",
                tpm2_alg_util_algtostr(alg, tpm2_alg_util_flags_any));
        return false;
    }

    return result;
}

static const EVP_CIPHER *tpm_alg_to_ossl(TPMT_SYM_DEF_OBJECT *sym) {

    switch (sym->algorithm) {
    case TPM2_ALG_AES: {
        switch (sym->keyBits.aes) {
        case 128:
            return EVP_aes_128_cfb();
        case 256:
            return EVP_aes_256_cfb();
            /* no default */
        }
    }
        /* no default */
    }

    LOG_ERR("Unsupported parent key symmetric parameters");

    return NULL;
}

static bool aes_encrypt_buffers(TPMT_SYM_DEF_OBJECT *sym,
        uint8_t *encryption_key, uint8_t *buf1, size_t buf1_len, uint8_t *buf2,
        size_t buf2_len, TPM2B_MAX_BUFFER *cipher_text) {

    bool result = false;

    unsigned offset = 0;
    size_t total_len = buf1_len + buf2_len;

    if (total_len > sizeof(cipher_text->buffer)) {
        LOG_ERR("Plaintext too big, got %zu, expected less then %zu", total_len,
                sizeof(cipher_text->buffer));
        return false;
    }

    const EVP_CIPHER *cipher = tpm_alg_to_ossl(sym);
    if (!cipher) {
        return false;
    }

    const unsigned char iv[512] = { 0 };

    if (((unsigned long) EVP_CIPHER_iv_length(cipher)) > sizeof(iv)) {
        LOG_ERR("IV size is bigger then IV buffer size");
        return false;
    }

    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
    if (!ctx) {
        return false;
    }

    int rc = EVP_EncryptInit_ex(ctx, cipher, NULL, encryption_key, iv);
    if (!rc) {
        return false;
    }

    EVP_CIPHER_CTX_set_padding(ctx, 0);

    uint8_t *bufs[2] = { buf1, buf2 };

    size_t lens[ARRAY_LEN(bufs)] = { buf1_len, buf2_len };

    unsigned i;
    for (i = 0; i < ARRAY_LEN(bufs); i++) {

        uint8_t *b = bufs[i];
        size_t l = lens[i];

        if (!b) {
            continue;
        }

        int output_len = total_len - offset;

        rc = EVP_EncryptUpdate(ctx, &cipher_text->buffer[offset], &output_len,
                b, l);
        if (!rc) {
            LOG_ERR("Encrypt failed");
            goto out;
        }

        offset += l;
    }

    int tmp_len = 0;
    rc = EVP_EncryptFinal_ex(ctx, NULL, &tmp_len);
    if (!rc) {
        LOG_ERR("Encrypt failed");
        goto out;
    }

    cipher_text->size = total_len;

    result = true;

out:
    EVP_CIPHER_CTX_free(ctx);

    return result;
}

static void hmac_outer_integrity(TPMI_ALG_HASH parent_name_alg,
        uint8_t *buffer1, uint16_t buffer1_size, uint8_t *buffer2,
        uint16_t buffer2_size, uint8_t *hmac_key,
        TPM2B_DIGEST *outer_integrity_hmac) {

    uint8_t to_hmac_buffer[TPM2_MAX_DIGEST_BUFFER];
    memcpy(to_hmac_buffer, buffer1, buffer1_size);
    memcpy(to_hmac_buffer + buffer1_size, buffer2, buffer2_size);
    uint32_t size = 0;

    UINT16 hash_size = tpm2_alg_util_get_hash_size(parent_name_alg);

    HMAC(tpm2_openssl_md_from_tpmhalg(parent_name_alg), hmac_key, hash_size,
            to_hmac_buffer, buffer1_size + buffer2_size,
            outer_integrity_hmac->buffer, &size);
    outer_integrity_hmac->size = size;
}

bool tpm2_identity_util_calculate_inner_integrity(TPMI_ALG_HASH name_alg,
        TPM2B_SENSITIVE *sensitive, TPM2B_NAME *pubname,
        TPM2B_DATA *enc_sensitive_key, TPMT_SYM_DEF_OBJECT *sym_alg,
        TPM2B_MAX_BUFFER *encrypted_inner_integrity) {

    TSS2_RC rval;

    //Marshal sensitive area
    uint8_t buffer_marshalled_sensitiveArea[TPM2_MAX_DIGEST_BUFFER] = { 0 };
    size_t marshalled_sensitive_size = 0;
    rval = Tss2_MU_TPMT_SENSITIVE_Marshal(&sensitive->sensitiveArea,
            buffer_marshalled_sensitiveArea + sizeof(uint16_t),
            TPM2_MAX_DIGEST_BUFFER, &marshalled_sensitive_size);
    if (rval != TPM2_RC_SUCCESS)
    {
        LOG_ERR("Error serializing the sensitive data");
        return false;
    }

    size_t marshalled_sensitive_size_info = 0;
    rval = Tss2_MU_UINT16_Marshal(marshalled_sensitive_size,
            buffer_marshalled_sensitiveArea, sizeof(uint16_t),
            &marshalled_sensitive_size_info);
    if (rval != TPM2_RC_SUCCESS)
    {
        LOG_ERR("Error serializing the sensitive size");
        return false;
    }

    //concatenate NAME
    memcpy(buffer_marshalled_sensitiveArea + marshalled_sensitive_size +
        marshalled_sensitive_size_info, pubname->name, pubname->size);

    //Digest marshalled-sensitive || name
    uint8_t *marshalled_sensitive_and_name_digest =
            buffer_marshalled_sensitiveArea + marshalled_sensitive_size
                    + marshalled_sensitive_size_info + pubname->size;
    size_t digest_size_info = 0;
    UINT16 hash_size = tpm2_alg_util_get_hash_size(name_alg);
    rval = Tss2_MU_UINT16_Marshal(hash_size, marshalled_sensitive_and_name_digest,
            sizeof(uint16_t), &digest_size_info);
    if (rval != TPM2_RC_SUCCESS)
    {
        LOG_ERR("Error serializing the name size");
        return false;
    }

    const EVP_MD *md = tpm2_openssl_md_from_tpmhalg(name_alg);
    if (!md) {
        LOG_ERR("Algorithm not supported: %x", name_alg);
        return false;
    }
    int rc = EVP_Digest(buffer_marshalled_sensitiveArea,
                        marshalled_sensitive_size_info + marshalled_sensitive_size
                            + pubname->size,
                        marshalled_sensitive_and_name_digest + digest_size_info,
                        NULL, md, NULL);
    if (!rc) {
        LOG_ERR("Hash calculation failed");
        return false;
    }

    //Inner integrity
    encrypted_inner_integrity->size = marshalled_sensitive_size_info
            + marshalled_sensitive_size + pubname->size;

    return aes_encrypt_buffers(sym_alg, enc_sensitive_key->buffer,
            marshalled_sensitive_and_name_digest, hash_size + digest_size_info,
            buffer_marshalled_sensitiveArea,
            marshalled_sensitive_size_info + marshalled_sensitive_size,
            encrypted_inner_integrity);
}

void tpm2_identity_util_calculate_outer_integrity(TPMI_ALG_HASH parent_name_alg,
        TPM2B_NAME *pubname, TPM2B_MAX_BUFFER *marshalled_sensitive,
        TPM2B_MAX_BUFFER *protection_hmac_key,
        TPM2B_MAX_BUFFER *protection_enc_key, TPMT_SYM_DEF_OBJECT *sym_alg,
        TPM2B_MAX_BUFFER *encrypted_duplicate_sensitive,
        TPM2B_DIGEST *outer_hmac) {

    //Calculate dupSensitive
    encrypted_duplicate_sensitive->size = marshalled_sensitive->size;

    aes_encrypt_buffers(sym_alg, protection_enc_key->buffer,
            marshalled_sensitive->buffer, marshalled_sensitive->size,
            NULL, 0, encrypted_duplicate_sensitive);
    //Calculate outerHMAC
    hmac_outer_integrity(parent_name_alg, encrypted_duplicate_sensitive->buffer,
            encrypted_duplicate_sensitive->size, pubname->name, pubname->size,
            protection_hmac_key->buffer, outer_hmac);
}

bool tpm2_identity_create_name(TPM2B_PUBLIC *public, TPM2B_NAME *pubname) {

    /*
     * A TPM2B_NAME is the name of the algorithm, followed by the hash.
     * Calculate the name by:
     * 1. Marshaling the name algorithm
     * 2. Marshaling the TPMT_PUBLIC past the name algorithm from step 1.
     * 3. Hash the TPMT_PUBLIC portion in marshaled data.
     */
    TSS2_RC rval;

    TPMI_ALG_HASH name_alg = public->publicArea.nameAlg;

    // Step 1 - set beginning of name to hash alg
    size_t hash_offset = 0;
    rval = Tss2_MU_UINT16_Marshal(name_alg, pubname->name, pubname->size,
            &hash_offset);
    if (rval != TPM2_RC_SUCCESS)
    {
        LOG_ERR("Error serializing the name size");
        return false;
    }

    // Step 2 - marshal TPMTP
    TPMT_PUBLIC marshaled_tpmt;
    size_t tpmt_marshalled_size = 0;
    rval = Tss2_MU_TPMT_PUBLIC_Marshal(&public->publicArea,
            (uint8_t *) &marshaled_tpmt, sizeof(public->publicArea),
            &tpmt_marshalled_size);
    if (rval != TPM2_RC_SUCCESS)
    {
        LOG_ERR("Error serializing the public area");
        return false;
    }

    // Step 3 - Hash the data into name just past the alg type.
    const EVP_MD *md = tpm2_openssl_md_from_tpmhalg(name_alg);
    if (!md) {
        LOG_ERR("Algorithm not supported: %x", name_alg);
        return false;
    }

    unsigned int hash_size;
    int rc = EVP_Digest(&marshaled_tpmt, tpmt_marshalled_size,
                        pubname->name + hash_offset, &hash_size, md, NULL);
    if (!rc) {
        LOG_ERR("Hash calculation failed");
        return false;
    }

    //Set the name size, UINT16 followed by HASH
    pubname->size = hash_size + hash_offset;

    return true;
}
