/* SPDX-License-Identifier: BSD-2-Clause */
/***********************************************************************
 * Copyright (c) 2017-2018, Intel Corporation
 *
 * All rights reserved.
 ***********************************************************************/
#ifdef HAVE_CONFIG_H
#include "config.h" // IWYU pragma: keep
#endif

#include <inttypes.h> // for PRIu16, uint8_t
#include <stdbool.h>  // for bool, false, true
#include <stdlib.h>   // for free, calloc
#include <string.h>   // for memcpy, NULL, memset, strlen, size_t
#include <uthash.h>   // for UT_hash_handle, HASH_DEL, HASH_FIND_INT

#include "session-util.h"     // for SESSION, GetEntity, ENTITY, KDFa, chec...
#include "sys-util.h"         // for ConcatSizedByteBuffer, CopySizedByteBu...
#include "test-common.h"      // for TEST_ABI_VERSION
#include "tss2_common.h"      // for TSS2_RC, BYTE, TSS2_RC_SUCCESS, TSS2_S...
#include "tss2_sys.h"         // for TSS2_SYS_CONTEXT, TSS2L_SYS_AUTH_COMMAND
#include "tss2_tcti.h"        // for TSS2_TCTI_CONTEXT
#include "tss2_tpm2_types.h"  // for TPM2B_MAX_BUFFER, TPM2B_DIGEST, TPM2_R...
#include "util/tpm2b.h"       // for TPM2B
#include "util/tss2_endian.h" // for BE_TO_HOST_32

#define LOGMODULE test
#include "util/log.h" // for LOGBLOB_DEBUG, LOG_ERROR, LOGBLOB_ERROR

static SESSION *sessions = NULL;

SESSION *
get_session(TPMI_SH_AUTH_SESSION hndl) {
    SESSION *s;

    HASH_FIND_INT(sessions, &hndl, s);
    return s;
}

static TSS2_RC
start_auth_session(SESSION *session, TSS2_SYS_CONTEXT *sys_context) {
    TSS2_RC                rval;
    TPM2B_ENCRYPTED_SECRET key;
    char                   label[] = "ATH";
    UINT16                 bytes;

    key.size = 0;

    if (session->nonceOlder.size == 0)
        session->nonceOlder.size = GetDigestSize(session->authHash);

    memset(session->nonceOlder.buffer, '\0', session->nonceOlder.size);
    session->nonceNewer.size = session->nonceOlder.size;
    session->nonceTpmDecrypt.size = 0;
    session->nonceTpmEncrypt.size = 0;

    rval = Tss2_Sys_StartAuthSession(sys_context, session->tpmKey, session->bind, 0,
                                     &session->nonceOlder, &session->encryptedSalt,
                                     session->sessionType, &session->symmetric, session->authHash,
                                     &session->sessionHandle, &session->nonceNewer, 0);
    if (rval != TPM2_RC_SUCCESS)
        return rval;

    if (session->tpmKey == TPM2_RH_NULL)
        session->salt.size = 0;

    if (session->bind == TPM2_RH_NULL)
        session->authValueBind.size = 0;

    session->sessionKey.size = 0;
    if (session->tpmKey == TPM2_RH_NULL && session->bind == TPM2_RH_NULL)
        return rval;

    /* Generate the key used as input to the KDF. */
    rval = ConcatSizedByteBuffer((TPM2B_MAX_BUFFER *)&key, (TPM2B *)&session->authValueBind);
    if (rval != TPM2_RC_SUCCESS) {
        Tss2_Sys_FlushContext(sys_context, session->sessionHandle);
        return rval;
    }

    rval = ConcatSizedByteBuffer((TPM2B_MAX_BUFFER *)&key, (TPM2B *)&session->salt);
    if (rval != TPM2_RC_SUCCESS) {
        Tss2_Sys_FlushContext(sys_context, session->sessionHandle);
        return rval;
    }

    bytes = GetDigestSize(session->authHash) * 8;

    rval = KDFa(session->authHash, (TPM2B *)&key, label, (TPM2B *)&session->nonceNewer,
                (TPM2B *)&session->nonceOlder, bytes, (TPM2B_MAX_BUFFER *)&session->sessionKey);

    return rval;
}

static TSS2_RC
compute_session_auth(TSS2_SYS_CONTEXT  *sysContext,
                     SESSION           *session,
                     TPMS_AUTH_COMMAND *pSessionDataIn,
                     bool               command,
                     TPM2_HANDLE        handle1,
                     TPM2_HANDLE        handle2,
                     TPM2_HANDLE        handle3,
                     TPM2B_MAX_BUFFER  *hmacKey) {
    TPM2B_DIGEST *buffer_list[7];
    TPM2B_DIGEST  pHash = TPM2B_DIGEST_INIT;
    TPM2B_DIGEST  sessionAttributesByteBuffer = { .size = 1,
                                                  .buffer = {
                                                     pSessionDataIn->sessionAttributes,
                                                 } };
    UINT16        i;
    TSS2_RC       rval;
    TPM2_CC       cmdCode;

    rval
        = tpm_calc_phash(sysContext, handle1, handle2, handle3, session->authHash, command, &pHash);
    if (rval != TPM2_RC_SUCCESS) {
        LOG_ERROR("tpm_calc_phash failed with RC: 0x%x", rval);
        return rval;
    }

    rval = Tss2_Sys_GetCommandCode(sysContext, (UINT8 *)&cmdCode);
    if (rval != TPM2_RC_SUCCESS) {
        LOG_ERROR("Tss2_Sys_GetCommandCode failed with RC: 0x%x", rval);
        return rval;
    }

    /* cmdCode comes back as BigEndian; not suited for comparisons below. */
    cmdCode = BE_TO_HOST_32(cmdCode);
    LOGBLOB_DEBUG(hmacKey->buffer, hmacKey->size, "hmacKey=");

    i = 0;
    buffer_list[i++] = (TPM2B_DIGEST *)&pHash;
    buffer_list[i++] = (TPM2B_DIGEST *)&session->nonceNewer;
    buffer_list[i++] = (TPM2B_DIGEST *)&session->nonceOlder;
    buffer_list[i++] = (TPM2B_DIGEST *)&session->nonceTpmDecrypt;
    buffer_list[i++] = (TPM2B_DIGEST *)&session->nonceTpmEncrypt;
    buffer_list[i++] = (TPM2B_DIGEST *)&sessionAttributesByteBuffer;
    buffer_list[i++] = 0;

    for (int j = 0; buffer_list[j] != 0; j++) {
        LOGBLOB_DEBUG(&buffer_list[j]->buffer[0], buffer_list[j]->size, "bufferlist[%d]:", j);
        ;
    }

    rval = hmac(session->authHash, hmacKey->buffer, hmacKey->size, buffer_list,
                (TPM2B_DIGEST *)&pSessionDataIn->hmac);

    if (rval != TPM2_RC_SUCCESS) {
        LOGBLOB_ERROR(pSessionDataIn->hmac.buffer, pSessionDataIn->hmac.size,
                      "HMAC Failed rval = %d !!!", rval);
        return rval;
    }
    return rval;
}

TSS2_RC
compute_command_hmac(TSS2_SYS_CONTEXT       *sysContext,
                     TPM2_HANDLE             handle1,
                     TPM2_HANDLE             handle2,
                     TPM2_HANDLE             handle3,
                     TSS2L_SYS_AUTH_COMMAND *pSessionsDataIn) {
    TPM2_HANDLE      handles[3] = { handle1, handle2, handle3 };
    ENTITY          *entity;
    SESSION         *session;
    TPM2B_MAX_BUFFER hmac_key;
    TSS2_RC          rval = TPM2_RC_SUCCESS;
    unsigned int     i;
    unsigned int     count = pSessionsDataIn->count;

    if (count > 3) {
        LOG_ERROR("Bad value for session count: %" PRIu16, count);
        return TSS2_SYS_RC_GENERAL_FAILURE;
    }

    for (i = 0; i < count; i++) {
        if (handles[i] == TPM2_RH_NULL)
            break;

        entity = GetEntity(handles[i]);
        if (!entity)
            return TSS2_SYS_RC_GENERAL_FAILURE;

        session = get_session(pSessionsDataIn->auths[i].sessionHandle);
        if (!session)
            return TPM2_RC_SUCCESS;

        CopySizedByteBuffer((TPM2B *)&hmac_key, (TPM2B *)&session->sessionKey);

        if (handles[i] != session->bind || handles[i] == TPM2_RH_NULL)
            ConcatSizedByteBuffer(&hmac_key, (TPM2B *)&entity->entityAuth);

        rval = compute_session_auth(sysContext, session, &pSessionsDataIn->auths[i], true, handle1,
                                    handle2, handle3, &hmac_key);
        if (rval != TPM2_RC_SUCCESS) {
            LOG_ERROR("compute_session_auth failed with RC: 0x%x", rval);
            break;
        }
    }
    return rval;
}

TSS2_RC
check_response_hmac(TSS2_SYS_CONTEXT        *sysContext,
                    TSS2L_SYS_AUTH_COMMAND  *pSessionsDataIn,
                    TPM2_HANDLE              handle1,
                    TPM2_HANDLE              handle2,
                    TPM2_HANDLE              handle3,
                    TSS2L_SYS_AUTH_RESPONSE *pSessionsDataOut) {
    TPM2_HANDLE      handles[3] = { handle1, handle2, handle3 };
    ENTITY          *entity;
    SESSION         *session;
    TPM2B_MAX_BUFFER hmac_key;
    TSS2_RC          rval = TPM2_RC_SUCCESS;
    unsigned int     i;
    unsigned int     count = pSessionsDataIn->count;

    if (count > 3) {
        LOG_ERROR("Bad value for session count: %" PRIu16, count);
        return TSS2_SYS_RC_GENERAL_FAILURE;
    }

    for (i = 0; i < count; i++) {
        if (handles[i] == TPM2_RH_NULL)
            break;

        entity = GetEntity(handles[i]);
        if (!entity)
            return TSS2_SYS_RC_GENERAL_FAILURE;

        session = get_session(pSessionsDataIn->auths[i].sessionHandle);
        if (!session)
            return TPM2_RC_SUCCESS;

        CopySizedByteBuffer((TPM2B *)&hmac_key, (TPM2B *)&session->sessionKey);

        if (handles[i] != session->bind)
            ConcatSizedByteBuffer(&hmac_key, (TPM2B *)&entity->entityAuth);

        rval = compute_session_auth(sysContext, session, &pSessionsDataIn->auths[i], false, handle1,
                                    handle2, handle3, &hmac_key);

        if (rval != TPM2_RC_SUCCESS)
            return rval;

        rval = CompareSizedByteBuffer((TPM2B *)&pSessionsDataIn->auths[i].hmac,
                                      (TPM2B *)&pSessionsDataOut->auths[i].hmac);
        if (rval != TPM2_RC_SUCCESS)
            return TSS2_SYS_RC_GENERAL_FAILURE;
    }
    return rval;
}

TSS2_RC
create_auth_session(SESSION               **psession,
                    TPMI_DH_OBJECT          tpmKey,
                    TPM2B_MAX_BUFFER       *salt,
                    TPMI_DH_ENTITY          bind,
                    TPM2B_AUTH             *bindAuth,
                    TPM2B_NONCE            *nonceCaller,
                    TPM2B_ENCRYPTED_SECRET *encryptedSalt,
                    TPM2_SE                 sessionType,
                    TPMT_SYM_DEF           *symmetric,
                    TPMI_ALG_HASH           algId,
                    TSS2_SYS_CONTEXT       *sysContext) {
    TSS2_RC  rval;
    SESSION *session, *tmp;

    if (psession == NULL)
        return TSS2_SYS_RC_BAD_REFERENCE;

    session = calloc(1, sizeof(SESSION));

    if (!session)
        return TSS2_SYS_RC_GENERAL_FAILURE;

    session->bind = bind;
    session->tpmKey = tpmKey;
    CopySizedByteBuffer((TPM2B *)&session->nonceOlder, (TPM2B *)nonceCaller);
    CopySizedByteBuffer((TPM2B *)&session->encryptedSalt, (TPM2B *)encryptedSalt);
    session->sessionType = sessionType;
    session->symmetric.algorithm = symmetric->algorithm;
    session->symmetric.keyBits.sym = symmetric->keyBits.sym;
    session->symmetric.mode.sym = symmetric->mode.sym;
    session->authHash = algId;
    if (bindAuth != NULL)
        CopySizedByteBuffer((TPM2B *)&session->authValueBind, (TPM2B *)bindAuth);

    if (session->tpmKey != TPM2_RH_NULL)
        CopySizedByteBuffer((TPM2B *)&session->salt, (TPM2B *)salt);

    rval = start_auth_session(session, sysContext);
    if (rval != TSS2_RC_SUCCESS) {
        free(session);
        return rval;
    }
    /* Make sure this session handle is not already in the table */
    HASH_FIND_INT(sessions, &session->sessionHandle, tmp);
    if (tmp)
        HASH_DEL(sessions, tmp);

    HASH_ADD_INT(sessions, sessionHandle, session);
    *psession = session;
    return TSS2_RC_SUCCESS;
}

void
end_auth_session(SESSION *session) {
    HASH_DEL(sessions, session);
    free(session);
}

void
roll_nonces(SESSION *session, TPM2B_NONCE *new_nonce) {
    session->nonceOlder = session->nonceNewer;
    session->nonceNewer = *new_nonce;
}

/*
 * Create a new SysContext from the TCTI of an existing one.
 * This is needed where we need to keep the state of the existing SysContext.
 */
TSS2_RC
sys_context_from_sys_context(TSS2_SYS_CONTEXT *sysContext, TSS2_SYS_CONTEXT **newSysContext) {
    TSS2_ABI_VERSION   abiVersion = TEST_ABI_VERSION;
    TSS2_TCTI_CONTEXT *tctiContext;
    TSS2_RC            rval = TPM2_RC_SUCCESS;
    size_t             size;

    rval = Tss2_Sys_GetTctiContext(sysContext, &tctiContext);
    if (rval != TSS2_RC_SUCCESS) {
        return rval;
    }

    size = Tss2_Sys_GetContextSize(0);
    *newSysContext = (TSS2_SYS_CONTEXT *)calloc(1, size);
    if (*newSysContext == NULL) {
        return 1;
    }
    rval = Tss2_Sys_Initialize(*newSysContext, size, tctiContext, &abiVersion);
    if (rval != TSS2_RC_SUCCESS) {
        free(*newSysContext);
        return rval;
    }

    return TPM2_RC_SUCCESS;
}

TSS2_RC
tpm_calc_phash(TSS2_SYS_CONTEXT *sysContext,
               TPM2_HANDLE       handle1,
               TPM2_HANDLE       handle2,
               TPM2_HANDLE       handle3,
               TPMI_ALG_HASH     authHash,
               bool              command,
               TPM2B_DIGEST     *pHash) {
    TSS2_RC           rval = TPM2_RC_SUCCESS;
    UINT32            i;
    TPM2B_NAME        name1, name2, name3;
    TPM2B_MAX_BUFFER  hashInput;
    UINT8            *hashInputPtr;
    size_t            parametersSize;
    const uint8_t    *startParams;
    TPM2_CC           cmdCode;
    TSS2_SYS_CONTEXT *sysContextCopy;

    name1.size = 0;
    name2.size = 0;
    name3.size = 0;
    hashInput.size = 0;

    if (command) {
        rval = sys_context_from_sys_context(sysContext, &sysContextCopy);
        if (rval != TPM2_RC_SUCCESS) {
            return rval;
        }

        rval = tpm_handle_to_name(sysContextCopy, handle1, &name1);
        if (rval != TPM2_RC_SUCCESS)
            goto cleanup_sys_context_copy;

        rval = tpm_handle_to_name(sysContextCopy, handle2, &name2);
        if (rval != TPM2_RC_SUCCESS)
            goto cleanup_sys_context_copy;

        rval = tpm_handle_to_name(sysContextCopy, handle3, &name3);
        if (rval != TPM2_RC_SUCCESS)
            goto cleanup_sys_context_copy;

    cleanup_sys_context_copy:
        Tss2_Sys_Finalize(sysContextCopy);
        free(sysContextCopy);
        if (rval != TPM2_RC_SUCCESS)
            return rval;

        rval = Tss2_Sys_GetCpBuffer(sysContext, &parametersSize, &startParams);
        if (rval != TPM2_RC_SUCCESS)
            return rval;
    } else {
        rval = Tss2_Sys_GetRpBuffer(sysContext, &parametersSize, &startParams);
        if (rval != TPM2_RC_SUCCESS)
            return rval;

        hashInputPtr = &(hashInput.buffer[hashInput.size]);
        /* This is response code. Assuming 0 (success) */
        *(UINT32 *)hashInputPtr = 0;
        hashInput.size += 4;
    }

    rval = Tss2_Sys_GetCommandCode(sysContext, (UINT8 *)&cmdCode);
    if (rval != TPM2_RC_SUCCESS)
        return rval;

    hashInputPtr = &(hashInput.buffer[hashInput.size]);
    *(UINT32 *)hashInputPtr = cmdCode;
    hashInput.size += 4;

    rval = ConcatSizedByteBuffer(&hashInput, (TPM2B *)&name1);
    if (rval != TPM2_RC_SUCCESS)
        return rval;

    rval = ConcatSizedByteBuffer(&hashInput, (TPM2B *)&name2);
    if (rval != TPM2_RC_SUCCESS)
        return rval;

    rval = ConcatSizedByteBuffer(&hashInput, (TPM2B *)&name3);
    if (rval != TPM2_RC_SUCCESS)
        return rval;

    if (hashInput.size + parametersSize > sizeof(hashInput.buffer))
        return TSS2_SYS_RC_INSUFFICIENT_BUFFER;

    for (i = 0; i < parametersSize; i++)
        hashInput.buffer[hashInput.size + i] = startParams[i];

    hashInput.size += (UINT16)parametersSize;
    LOGBLOB_DEBUG(&hashInput.buffer[0], hashInput.size, "PHASH input bytes=");

    if (hashInput.size > sizeof(hashInput.buffer))
        return TSS2_SYS_RC_INSUFFICIENT_BUFFER;

    rval = hash(authHash, hashInput.buffer, hashInput.size, pHash);
    if (rval != TPM2_RC_SUCCESS)
        return rval;

    LOGBLOB_DEBUG(&pHash->buffer[0], pHash->size, "PHASH =");
    return rval;
}

UINT32
tpm_handle_to_name(TSS2_SYS_CONTEXT *sysContext, TPM2_HANDLE handle, TPM2B_NAME *name) {
    TSS2_RC    rval;
    TPM2B_NAME qualified_name = TPM2B_NAME_INIT;
    TPM2B_PUBLIC public;
    TPM2B_NV_PUBLIC nvPublic;
    UINT8          *namePtr;

    if (!sysContext || !name)
        return TSS2_SYS_RC_BAD_VALUE;

    namePtr = name->name;

    if (handle == TPM2_RH_NULL) {
        name->size = 0;
        return TSS2_RC_SUCCESS;
    }

    switch (handle >> TPM2_HR_SHIFT) {
    case TPM2_HT_NV_INDEX:
        nvPublic.size = 0;
        rval = Tss2_Sys_NV_ReadPublic(sysContext, handle, 0, &nvPublic, name, 0);
        break;

    case TPM2_HT_TRANSIENT:
    case TPM2_HT_PERSISTENT:
        public
        .size = 0;
        rval = Tss2_Sys_ReadPublic(sysContext, handle, 0, &public, name, &qualified_name, 0);
        break;

    default:
        rval = TPM2_RC_SUCCESS;
        name->size = sizeof(TPM2_HANDLE);
        *(TPM2_HANDLE *)namePtr = BE_TO_HOST_32(handle);
    }
    return rval;
}

TSS2_RC
KDFa(TPMI_ALG_HASH     hash,
     TPM2B            *key,
     const char       *label,
     TPM2B            *contextU,
     TPM2B            *contextV,
     UINT16            bits,
     TPM2B_MAX_BUFFER *result_key) {
    TPM2B_DIGEST  digest;
    TPM2B_DIGEST  tpm2blabel, tpm2bbits, tpm2bctr;
    TPM2B_DIGEST *buffer_list[8];
    UINT32        counter;
    TSS2_RC       rval;
    int           i, j;
    UINT16        bytes = bits / 8;

    result_key->size = 0;
    tpm2bctr.size = 4;
    tpm2bbits.size = 4;
    counter = BE_TO_HOST_32(bits);
    memcpy(tpm2bbits.buffer, &counter, 4);
    tpm2blabel.size = strlen(label) + 1;
    memcpy(tpm2blabel.buffer, label, tpm2blabel.size);

    LOG_DEBUG("KDFA, hash = %4.4x", hash);
    LOGBLOB_DEBUG(&key->buffer[0], key->size, "KDFA, key =");
    LOGBLOB_DEBUG(&tpm2blabel.buffer[0], tpm2blabel.size, "KDFA, tpm2blabel =");
    LOGBLOB_DEBUG(&contextU->buffer[0], contextU->size, "KDFA, contextU =");
    LOGBLOB_DEBUG(&contextV->buffer[0], contextV->size, "KDFA, contextV =");

    for (i = 1, j = 0; result_key->size < bytes; j = 0) {
        counter = BE_TO_HOST_32(i++);
        memcpy(tpm2bctr.buffer, &counter, 4);
        buffer_list[j++] = (TPM2B_DIGEST *)&tpm2bctr;
        buffer_list[j++] = (TPM2B_DIGEST *)&tpm2blabel;
        buffer_list[j++] = (TPM2B_DIGEST *)contextU;
        buffer_list[j++] = (TPM2B_DIGEST *)contextV;
        buffer_list[j++] = (TPM2B_DIGEST *)&tpm2bbits;
        buffer_list[j++] = NULL;

        for (j = 0; buffer_list[j] != NULL; j++) {
            LOGBLOB_DEBUG(&buffer_list[j]->buffer[0], buffer_list[j]->size, "bufferlist[%d]:", j);
            ;
        }

        rval = hmac(hash, key->buffer, key->size, buffer_list, &digest);
        if (rval != TPM2_RC_SUCCESS) {
            LOGBLOB_ERROR(digest.buffer, digest.size, "HMAC Failed rval = %d", rval);
            return rval;
        }

        ConcatSizedByteBuffer(result_key, (TPM2B *)&digest);
    }

    /* Truncate the result to the desired size. */
    result_key->size = bytes;
    LOGBLOB_DEBUG(result_key->buffer, result_key->size, "KDFA, key = ");
    return TPM2_RC_SUCCESS;
}

static TSS2_RC
gen_session_key(SESSION          *session,
                TPM2B_MAX_BUFFER *session_key,
                TPM2B_IV         *iv,
                TPM2B_AUTH       *auth_value) {
    TSS2_RC          rval = TSS2_RC_SUCCESS;
    UINT32           aes_block_size = 16;
    TPM2B_MAX_BUFFER key, sessionValue;

    if (iv == NULL || session_key == NULL)
        return TSS2_SYS_RC_BAD_VALUE;

    CopySizedByteBuffer((TPM2B *)&sessionValue, (TPM2B *)&session->sessionKey);
    CatSizedByteBuffer((TPM2B *)&sessionValue, (TPM2B *)auth_value);

    rval = KDFa(session->authHash, (TPM2B *)&sessionValue, "CFB", (TPM2B *)&session->nonceNewer,
                (TPM2B *)&session->nonceOlder, session->symmetric.keyBits.sym + aes_block_size * 8,
                &key);
    if (rval != TSS2_RC_SUCCESS)
        return rval;

    if (key.size != (session->symmetric.keyBits.sym / 8) + aes_block_size)
        return TSS2_SYS_RC_GENERAL_FAILURE;

    iv->size = aes_block_size;
    session_key->size = (session->symmetric.keyBits.sym) / 8;
    UINT16 total_size = session_key->size + iv->size;
    if (iv->size > sizeof(iv->buffer) || (total_size) > TPM2_MAX_DIGEST_BUFFER)
        return TSS2_SYS_RC_GENERAL_FAILURE;

    memcpy(iv->buffer, &key.buffer[session_key->size], iv->size);
    memcpy(session_key->buffer, key.buffer, session_key->size);
    return rval;
}

static TSS2_RC
encrypt_param_cfb(SESSION          *session,
                  TPM2B_MAX_BUFFER *encrypted_data,
                  TPM2B_MAX_BUFFER *clear_data,
                  TPM2B_AUTH       *auth_value) {
    TSS2_RC          rval = TSS2_RC_SUCCESS;
    TPM2B_MAX_BUFFER encryptKey = TPM2B_MAX_BUFFER_INIT;
    TPM2B_IV         iv = TPM2B_IV_INIT;

    rval = gen_session_key(session, &encryptKey, &iv, auth_value);
    if (rval)
        return rval;

    return encrypt_cfb(encrypted_data, clear_data, &encryptKey, &iv);
}

static TSS2_RC
decrypt_param_cfb(SESSION          *session,
                  TPM2B_MAX_BUFFER *clear_data,
                  TPM2B_MAX_BUFFER *encrypted_data,
                  TPM2B_AUTH       *auth_value) {
    TSS2_RC          rval = TSS2_RC_SUCCESS;
    TPM2B_MAX_BUFFER encryptKey = TPM2B_MAX_BUFFER_INIT;
    TPM2B_IV         iv = TPM2B_IV_INIT;

    rval = gen_session_key(session, &encryptKey, &iv, auth_value);
    if (rval)
        return rval;

    return decrypt_cfb(clear_data, encrypted_data, &encryptKey, &iv);
}

static TSS2_RC
encrypt_decrypt_xor(SESSION          *session,
                    TPM2B_MAX_BUFFER *output_data,
                    TPM2B_MAX_BUFFER *input_data,
                    TPM2B_AUTH       *auth_value) {
    TSS2_RC          rval = TSS2_RC_SUCCESS;
    TPM2B_MAX_BUFFER key;
    TPM2B_MAX_BUFFER mask = { .size = 0, .buffer = 0 };
    UINT16           i;
    UINT16           size = input_data->size;

    if (size > TPM2_MAX_DIGEST_BUFFER) {
        LOG_ERROR("Bad value for inputData size: %" PRIu16, size);
        return TSS2_SYS_RC_GENERAL_FAILURE;
    }

    CopySizedByteBuffer((TPM2B *)&key, (TPM2B *)&session->sessionKey);
    CatSizedByteBuffer((TPM2B *)&key, (TPM2B *)auth_value);

    rval = KDFa(session->authHash, (TPM2B *)&key, "XOR", (TPM2B *)&session->nonceNewer,
                (TPM2B *)&session->nonceOlder, input_data->size * 8, &mask);

    if (rval)
        return rval;

    for (i = 0; i < size; i++)
        output_data->buffer[i] = input_data->buffer[i] ^ mask.buffer[i];

    output_data->size = size;

    return rval;
}

TSS2_RC
encrypt_command_param(SESSION          *session,
                      TPM2B_MAX_BUFFER *encrypted_data,
                      TPM2B_MAX_BUFFER *clear_data,
                      TPM2B_AUTH       *auth_value) {
    return session->symmetric.algorithm == TPM2_ALG_AES
               ? encrypt_param_cfb(session, encrypted_data, clear_data, auth_value)
               : encrypt_decrypt_xor(session, encrypted_data, clear_data, auth_value);
}

TSS2_RC
decrypt_response_param(SESSION          *session,
                       TPM2B_MAX_BUFFER *clear_data,
                       TPM2B_MAX_BUFFER *encrypted_data,
                       TPM2B_AUTH       *auth_value) {
    return session->symmetric.algorithm == TPM2_ALG_AES
               ? decrypt_param_cfb(session, clear_data, encrypted_data, auth_value)
               : encrypt_decrypt_xor(session, clear_data, encrypted_data, auth_value);
}
