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

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

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

#include "tss2_mu.h"
#include "fapi_util.h"
#include "fapi_crypto.h"
#include "ifapi_policy_execute.h"
#include "ifapi_helpers.h"
#include "ifapi_json_deserialize.h"
#include "tpm_json_deserialize.h"
#include "ifapi_policy_callbacks.h"
#define LOGMODULE fapi
#include "util/log.h"
#include "util/aux_util.h"

/** Copy the policy digests from a branch list to a digest list.
 *
 * @param[in] branches The list of policy branches.
 * @param[in] current_hash_alg The hash algorithm used for computation.
 * @param[out] digest_list The list of policies computed for every branch.
 * @retval TSS2_RC_SUCCESS on success.
 * @retval TSS2_FAPI_RC_BAD_VALUE If no appropriate digest was found in
 *         the branch list.
 */
static TSS2_RC
compute_or_digest_list(
    TPML_POLICYBRANCHES *branches,
    TPMI_ALG_HASH current_hash_alg,
    TPML_DIGEST *digest_list)
{
    size_t i;
    size_t digest_idx, hash_size;
    bool digest_found = false;

    if (!(hash_size = ifapi_hash_get_digest_size(current_hash_alg))) {
        return_error2(TSS2_FAPI_RC_BAD_VALUE,
                      "Unsupported hash algorithm (%" PRIu16 ")",
                              current_hash_alg);
    }
    /* Determine digest values with appropriate hash alg */
    TPML_DIGEST_VALUES *branch_digests = &branches->authorizations[0].policyDigests;
    for (i = 0; i < branch_digests->count; i++) {
        if (branch_digests->digests[i].hashAlg == current_hash_alg) {
            digest_idx = i;
            digest_found = true;
            break;
        }
    }
    if (!digest_found) {
         return_error(TSS2_FAPI_RC_BAD_VALUE, "No digest found for hash alg");
    }

    digest_list->count = branches->count;
    for (i = 0; i < branches->count; i++) {
        if (i > 7) {
            return_error(TSS2_FAPI_RC_BAD_VALUE, "Too much or branches.");
        }
        digest_list->digests[i].size = hash_size;
        memcpy(&digest_list->digests[i].buffer[0],
               &branches->authorizations[i].policyDigests.
               digests[digest_idx].digest, hash_size);
        LOGBLOB_DEBUG(&digest_list->digests[i].buffer[0],
                      digest_list->digests[i].size, "Compute digest list");
    }
    return TSS2_RC_SUCCESS;
}

/** Add a new authorization to a policy.
 *
 * The the signed hash computed from the policy digest and the policyRef together with
 * the public key of the key used for signing will be stored in the policy.
 *
 * @param[in,out] policy The policy to be authorized.
 * @param[in] authorization The structure with the signature, the policyRef and
 *                          the public key.
 *
 * @retval TSS2_RC_SUCCESS on success.
 * @retval TSS2_FAPI_RC_MEMORY If the memory for the authorization list cannot be
 *         allocated.
 */
TSS2_RC
ifapi_extend_authorization(
    TPMS_POLICY *policy,
    TPMS_POLICYAUTHORIZATION *authorization)
{
    TPML_POLICYAUTHORIZATIONS *save = NULL;
    size_t n = 0;
    size_t i;

    if (policy->policyAuthorizations) {
        /* Extend old authorizations */
        n = policy->policyAuthorizations->count;
        save = policy->policyAuthorizations;
        policy->policyAuthorizations =
            malloc((n + 1) * sizeof(TPMS_POLICYAUTHORIZATION)
                   + sizeof(TPML_POLICYAUTHORIZATIONS));
        return_if_null(policy->policyAuthorizations->authorizations,
                       "Out of memory.", TSS2_FAPI_RC_MEMORY);

        for (i = 0; i < n; i++)
            policy->policyAuthorizations->authorizations[i] =
                save->authorizations[i];
        policy->policyAuthorizations->authorizations[n] = *authorization;
        policy->policyAuthorizations->count = n + 1;
        SAFE_FREE(save);
    } else {
        /* No old authorizations exits */
        policy->policyAuthorizations = malloc(sizeof(TPMS_POLICYAUTHORIZATION)
                                               + sizeof(TPML_POLICYAUTHORIZATIONS));
        return_if_null(policy->policyAuthorizations->authorizations,
                       "Out of memory.", TSS2_FAPI_RC_MEMORY);

        policy->policyAuthorizations->count = 1;
        policy->policyAuthorizations->authorizations[0] = *authorization;
    }
    return TSS2_RC_SUCCESS;
}
/** Compute the index for the current digest list and clear the digest.
 *
 * The list entry with the appropriate hash algorithm will be searched.
 * The found digest will be set to zero.
 *
 * @param[in,out] digest_values The list of policy digests and corresponding
 *                hash algorithms.
 * @param[in] hashAlg The hash algorithm to be searched.
 * @param[out] idx The index of the found digest.
 * @retval TSS2_RC_SUCCESS on success.
 * @retval TSS2_FAPI_RC_BAD_VALUE If no appropriate digest was found in
 *         the digest list.
 */
TSS2_RC
get_policy_digest_idx(TPML_DIGEST_VALUES *digest_values, TPMI_ALG_HASH hashAlg,
                      size_t *idx)
{
    size_t i;
    for (i = 0; i < digest_values->count; i++) {
        /* Check whether current hashAlg is appropriate. */
        if (digest_values->digests[i].hashAlg == hashAlg) {
            *idx = i;
            return TSS2_RC_SUCCESS;
        }
    }

    if (i >= TPM2_NUM_PCR_BANKS) {
        return_error(TSS2_FAPI_RC_BAD_VALUE, "Table overflow");
    }
    digest_values->digests[i].hashAlg = hashAlg;
    memset(&digest_values->digests[i].digest, 0, sizeof(TPMU_HA));
    *idx = i;
    digest_values->count += 1;
    return TSS2_RC_SUCCESS;
}

/** Execute policy PCR.
 *
 * This command is used to cause conditional gating of a policy based on PCR.
 *
 * @param[in,out] *esys_ctx The ESAPI context which is needed to execute the
 *                policy command.
 * @param[in,out] policy The PCR policy which will be executed. The policy
 *                digest will be added to the policy.
 * @param[in]     current_hash_alg The hash algorithm wich will be used for
 *                policy computation.
 * @param[in,out] current_policy The policy context which stores the state
 *                of the policy execution.
 * @retval TSS2_RC_SUCCESS on success.
 * @retval TSS2_FAPI_RC_TRY_AGAIN if an I/O operation is not finished yet and
 *         this function needs to be called again.
 * @retval TSS2_FAPI_RC_BAD_SEQUENCE if the context has an asynchronous
 *         operation already pending.
 * @retval TSS2_ESYS_RC_* possible error codes of ESAPI.
 * @retval TSS2_FAPI_RC_BAD_VALUE if an invalid value was passed into
 *         the function.
 * @retval TSS2_FAPI_RC_BAD_REFERENCE a invalid null pointer is passed.
 * @retval TSS2_FAPI_RC_MEMORY if not enough memory can be allocated.
 * @retval TSS2_FAPI_RC_GENERAL_FAILURE if an internal error occured.
 */
static TSS2_RC
execute_policy_pcr(
    ESYS_CONTEXT *esys_ctx,
    TPMS_POLICYPCR *policy,
    TPMI_ALG_HASH current_hash_alg,
    IFAPI_POLICY_EXEC_CTX *current_policy)
{
    TSS2_RC r = TSS2_RC_SUCCESS;
    TPML_PCR_SELECTION pcr_selection;
    TPM2B_DIGEST pcr_digest;

    LOG_TRACE("call");

    switch (current_policy->state) {
    statecase(current_policy->state, POLICY_EXECUTE_INIT)
        /* Compute PCR selection and pcr digest */
        r = ifapi_compute_policy_digest(policy->pcrs, &pcr_selection,
                                        current_hash_alg, &pcr_digest);
        return_if_error(r, "Compute policy digest and selection.");

        LOGBLOB_DEBUG(&pcr_digest.buffer[0], pcr_digest.size, "PCR Digest");

        /* Prepare the policy execution. */
        r = Esys_PolicyPCR_Async(esys_ctx,
                                 current_policy->session,
                                 ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE,
                                 &pcr_digest, &pcr_selection);
        return_if_error(r, "Execute PolicyPCR.");
        fallthrough;

    statecase(current_policy->state, POLICY_EXECUTE_FINISH)
        /* Finalize the policy execution if possible. */
        r = Esys_PolicyPCR_Finish(esys_ctx);
        try_again_or_error(r, "Execute PolicyPCR_Finish.");

        current_policy->state = POLICY_EXECUTE_INIT;
        return r;

    statecasedefault(current_policy->state);
    }
    return r;
}

/** Execute policy duplicate.
 *
 * @param[in,out] *esys_ctx The ESAPI context which is needed to execute the
 *                policy command.
 * @param[in,out] policy The duplicate policy which will be executed. The policy
 *                digest will be added to the policy.
 * @param[in]     current_hash_alg The hash algorithm wich will be used for
 *                policy computation.
 * @param[in,out] current_policy The policy context which stores the state
 *                of the policy execution.
 * @retval TSS2_RC_SUCCESS on success.
 * @retval TSS2_FAPI_RC_TRY_AGAIN if an I/O operation is not finished yet and
 *         this function needs to be called again.
 * @retval TSS2_FAPI_RC_BAD_SEQUENCE if the context has an asynchronous
 *         operation already pending.
 * @retval TSS2_FAPI_RC_BAD_REFERENCE a invalid null pointer is passed.
 * @retval TSS2_ESYS_RC_* possible error codes of ESAPI.
 */
static TSS2_RC
execute_policy_duplicate(
    ESYS_CONTEXT *esys_ctx,
    TPMS_POLICYDUPLICATIONSELECT *policy,
    IFAPI_POLICY_EXEC_CTX *current_policy)
{
    TSS2_RC r = TSS2_RC_SUCCESS;

    LOG_TRACE("call");

    switch (current_policy->state) {
    statecase(current_policy->state, POLICY_EXECUTE_INIT)
        ifapi_policyeval_EXEC_CB *cb = &current_policy->callbacks;
        r = cb->cbdup(&policy->objectName, cb->cbdup_userdata);
        return_if_error(r, "Get name for policy duplicate select.");

        /* Prepare the policy execution. */
        r = Esys_PolicyDuplicationSelect_Async(esys_ctx,
                                               current_policy->session,
                                               ESYS_TR_NONE, ESYS_TR_NONE,
                                               ESYS_TR_NONE,
                                               &policy->objectName,
                                               &policy->newParentName,
                                               policy->includeObject);
        return_if_error(r, "Execute PolicyDuplicatonSelect_Async.");
        fallthrough;

    statecase(current_policy->state, POLICY_EXECUTE_FINISH)
        /* Finalize the policy execution if possible. */
        r = Esys_PolicyDuplicationSelect_Finish(esys_ctx);
        try_again_or_error(r, "Execute PolicyDuplicationselect_Finish.");

        current_policy->state = POLICY_EXECUTE_INIT;
        return r;

    statecasedefault(current_policy->state);
    }
    return r;
}

/** Execute policy NV.
 *
 * A policy based on the contents of an NV Index will be executed. The
 * NV authorization is done by a callback. For an example callback
 * implementation see ifapi_policyeval_cbauth()
 *
 * @param[in,out] *esys_ctx The ESAPI context which is needed to execute the
 *                policy command.
 * @param[in,out] policy The NV policy which will be executed. The policy
 *                digest will be added to the policy.
 * @param[in]     current_hash_alg The hash algorithm wich will be used for
 *                policy computation.
 * @param[in,out] current_policy The policy context which stores the state
 *                of the policy execution.
 * @retval TSS2_RC_SUCCESS on success.
 * @retval TSS2_FAPI_RC_TRY_AGAIN if an I/O operation is not finished yet and
 *         this function needs to be called again.
 * @retval TSS2_FAPI_RC_BAD_SEQUENCE if the context has an asynchronous
 *         operation already pending.
 * @retval TSS2_FAPI_RC_BAD_REFERENCE a invalid null pointer is passed.
 * @retval TSS2_ESYS_RC_* possible error codes of ESAPI.
 * @retval TSS2_FAPI_RC_MEMORY if not enough memory can be allocated.
 * @retval TSS2_FAPI_RC_BAD_VALUE if an invalid value was passed into
 *         the function.
 * @retval TSS2_FAPI_RC_GENERAL_FAILURE if an internal error occured.
 * @retval TSS2_FAPI_RC_PATH_NOT_FOUND if a FAPI object path was not found
 *         during authorization.
 * @retval TSS2_FAPI_RC_KEY_NOT_FOUND if a key was not found.
 * @retval TSS2_FAPI_RC_BAD_PATH if the path is used in inappropriate context
 *         or contains illegal characters.
 * @retval TSS2_FAPI_RC_NOT_PROVISIONED FAPI was not provisioned.
 * @retval TSS2_FAPI_RC_IO_ERROR if an error occured while accessing the
 *         object store.
 * @retval TSS2_FAPI_RC_AUTHORIZATION_UNKNOWN if a required authorization callback
 *         is not set.
 * @retval TSS2_FAPI_RC_AUTHORIZATION_FAILED if the authorization attempt fails.
 * @retval TSS2_FAPI_RC_POLICY_UNKNOWN if policy search for a certain policy digest
 *         was not successful.
 */
static TSS2_RC
execute_policy_nv(
    ESYS_CONTEXT *esys_ctx,
    TPMS_POLICYNV *policy,
    IFAPI_POLICY_EXEC_CTX *current_policy)
{
    TSS2_RC r = TSS2_RC_SUCCESS;

    LOG_TRACE("call");

    switch (current_policy->state) {
    statecase(current_policy->state, POLICY_EXECUTE_INIT)
        r = ifapi_nv_get_name(&policy->nvPublic, &current_policy->name);
        return_if_error(r, "Compute NV name");
        fallthrough;

    statecase(current_policy->state, POLICY_AUTH_CALLBACK)
        ifapi_policyeval_EXEC_CB *cb = &current_policy->callbacks;

        /* Authorize NV object. */
        r = cb->cbauth(&current_policy->name,
                       &current_policy->object_handle,
                       &current_policy->auth_handle,
                       &current_policy->auth_session, cb->cbauth_userdata);
        return_try_again(r);
        return_if_error(r, "Execute authorized policy.");

        /* Prepare the policy execution. */
        r = Esys_PolicyNV_Async(esys_ctx,
                                current_policy->object_handle,
                                current_policy->auth_handle,
                                current_policy->session,
                                current_policy->auth_session, ESYS_TR_NONE, ESYS_TR_NONE,
                                &policy->operandB, policy->offset,
                                policy->operation);
        return_if_error(r, "Execute PolicyNV_Async.");
        fallthrough;

    statecase(current_policy->state, POLICY_EXECUTE_FINISH)
        /* Finalize the policy execution if possible. */
        r = Esys_PolicyNV_Finish(esys_ctx);
        try_again_or_error(r, "Execute PolicyNV_Finish.");

        current_policy->state = POLICY_EXECUTE_INIT;
        return r;

    statecasedefault(current_policy->state);
    }
    return r;
}

/** Execute policy for signature based authorization.
 *
 * A signed authorization is included in this policy. The authorization hash
 * of the policy data will be signed via a callback. For an example callback
 * implementation see ifapi_sign_buffer()
 *
 * @param[in,out] *esys_ctx The ESAPI context which is needed to execute the
 *                policy command.
 * @param[in,out] policy The policy to be signed which will be executed. The policy
 *                digest will be added to the policy.
 * @param[in]     current_hash_alg The hash algorithm wich will be used for
 *                policy computation.
 * @param[in,out] current_policy The policy context which stores the state
 *                of the policy execution.
 * @retval TSS2_RC_SUCCESS on success.
 * @retval TSS2_FAPI_RC_MEMORY if not enough memory can be allocated.
 * @retval TSS2_FAPI_RC_TRY_AGAIN if an I/O operation is not finished yet and
 *         this function needs to be called again.
 * @retval TSS2_FAPI_RC_BAD_SEQUENCE if the context has an asynchronous
 *         operation already pending.
 * @retval TSS2_FAPI_RC_BAD_REFERENCE a invalid null pointer is passed.
 * @retval TSS2_FAPI_RC_AUTHORIZATION_UNKNOWN if a required authorization callback
 *         is not set.
 * @retval TSS2_ESYS_RC_* possible error codes of ESAPI.
 * @retval TSS2_FAPI_RC_BAD_VALUE if an invalid value was passed into
 *         the function.
 * @retval TSS2_FAPI_RC_GENERAL_FAILURE if an internal error occured.
 */
static TSS2_RC
execute_policy_signed(
    ESYS_CONTEXT *esys_ctx,
    TPMS_POLICYSIGNED *policy,
    IFAPI_POLICY_EXEC_CTX *current_policy)
{
    TSS2_RC r = TSS2_RC_SUCCESS;
    size_t offset = 0;
    //TPMT_SIGNATURE signature_tpm;
    size_t signature_size;
    const uint8_t *signature_ossl = NULL;

    LOG_TRACE("call");

    switch (current_policy->state) {
    statecase(current_policy->state, POLICY_EXECUTE_INIT);
        current_policy->pem_key = NULL;
        current_policy->object_handle = ESYS_TR_NONE;
        current_policy->buffer_size = sizeof(INT32) + sizeof(TPM2B_NONCE)
            + policy->cpHashA.size + policy->policyRef.size;
        current_policy->buffer = malloc(current_policy->buffer_size);
        return_if_null(current_policy->buffer, "Out of memory.", TSS2_FAPI_RC_MEMORY);

        r = Esys_TRSess_GetNonceTPM(esys_ctx, current_policy->session,
                                    &current_policy->nonceTPM);
        return_if_error(r, "Get TPM nonce.");

        /* Concatenate objects needed for the authorization hash */
        memcpy(&current_policy->buffer[offset], &current_policy->nonceTPM->buffer[0],
               current_policy->nonceTPM->size);
        offset += current_policy->nonceTPM->size;
        memset(&current_policy->buffer[offset], 0, sizeof(INT32));
        offset += sizeof(INT32);
        memcpy(&current_policy->buffer[offset], &policy->cpHashA.buffer[0],
               policy->cpHashA.size);
        offset += policy->cpHashA.size;
        memcpy(&current_policy->buffer[offset], &policy->policyRef.buffer[0],
               policy->policyRef.size);
        offset += policy->policyRef.size;
        current_policy->buffer_size = offset;
        fallthrough;

    statecase(current_policy->state, POLICY_EXECUTE_CALLBACK);
        ifapi_policyeval_EXEC_CB *cb = &current_policy->callbacks;
        int pem_key_size;
        TPM2B_PUBLIC tpm_public;

        /* Recreate pem key from tpm public key */
        if (!current_policy->pem_key) {
            tpm_public.publicArea = policy->keyPublic;
            tpm_public.size = 0;
            r = ifapi_pub_pem_key_from_tpm(&tpm_public, &current_policy->pem_key,
                                       &pem_key_size);
            return_if_error(r, "Convert TPM public key into PEM key.");
        }

        /* Callback for signing the autorization hash. */
        r = cb->cbsign(current_policy->pem_key, policy->publicKeyHint,
                       policy->keyPEMhashAlg, current_policy->buffer,
                       current_policy->buffer_size,
                       &signature_ossl, &signature_size,
                       cb->cbsign_userdata);
        SAFE_FREE(current_policy->pem_key);
        SAFE_FREE(current_policy->buffer);
        try_again_or_error_goto(r, "Execute policy signature callback.", cleanup);

        /* Convert signature into TPM format */
        r = ifapi_der_sig_to_tpm(&policy->keyPublic, signature_ossl,
                                 signature_size, policy->keyPEMhashAlg,
                                 &policy->signature_tpm);
        goto_if_error2(r, "Convert der signature into TPM format", cleanup);

        TPM2B_PUBLIC inPublic;
        inPublic.size = 0;
        inPublic.publicArea = policy->keyPublic;

        /* Prepare the loading of the external public key, user for verificaton. */
        r = Esys_LoadExternal_Async(esys_ctx,
                                    ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE,
                                    NULL, &inPublic, ESYS_TR_RH_OWNER);
        goto_if_error(r, "LoadExternal_Async", cleanup);
        fallthrough;

    statecase(current_policy->state, POLICY_LOAD_KEY);
        r = Esys_LoadExternal_Finish(esys_ctx, &current_policy->object_handle);
        try_again_or_error(r, "Load external key.");

        /* Prepare the policy execution. */
        r = Esys_PolicySigned_Async(esys_ctx,
                                    current_policy->object_handle,
                                    current_policy->session,
                                    ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE,
                                    current_policy->nonceTPM,
                                    &policy->cpHashA,
                                    &policy->policyRef, 0, &policy->signature_tpm);
        SAFE_FREE(current_policy->nonceTPM);
        goto_if_error(r, "Execute PolicySigned.", cleanup);
        fallthrough;

    statecase(current_policy->state, POLICY_EXECUTE_FINISH);
        /* Finalize the policy execution if possible. */
        r = Esys_PolicySigned_Finish(esys_ctx, NULL, NULL);
        try_again_or_error(r, "Execute PolicySigned_Finish.");

        r = Esys_FlushContext_Async(esys_ctx, current_policy->object_handle);
        goto_if_error(r, "FlushContext_Async", cleanup);
        fallthrough;

    statecase(current_policy->state, POLICY_FLUSH_KEY);
        r = Esys_FlushContext_Finish(esys_ctx);
        try_again_or_error(r, "Flush key finish.");

        current_policy->object_handle = ESYS_TR_NONE;
        current_policy->state = POLICY_EXECUTE_INIT;
        return r;

    statecasedefault(current_policy->state);
    }
cleanup:
    SAFE_FREE(current_policy->nonceTPM);
    SAFE_FREE(current_policy->pem_key);
    SAFE_FREE(signature_ossl);
    SAFE_FREE(current_policy->buffer);
    SAFE_FREE(current_policy->pem_key);
    /* In error cases object might not have been flushed. */
    if (current_policy->object_handle != ESYS_TR_NONE)
        Esys_FlushContext(esys_ctx, current_policy->object_handle);
    return r;
}

/** Execute a policy that was signed by a certain key.
 *
 * All policies authorized by the key stored in the policy will be
 * retrieved and one policy will be selected via a branch selection callback
 * (see Fapi_SetBranchCB()) if more then one policy was found.
 * The selected policy will be executed via a callback. For an example callback
 * implementation see ifapi_exec_auth_policy().
 *
 * For an example callback implementation to executie of an authorized policy
 * ifapi_exec_auth_policy()
 *
 * @param[in,out] *esys_ctx The ESAPI context which is needed to execute the
 *                policy command.
 * @param[in,out] policy The policy which defines the signing key and several
 *                additional parameters (nonce, policyRef ...). The policy
 *                digest will be added to the policy.
 * @param[in]     current_hash_alg The hash algorithm wich will be used for
 *                policy computation.
 * @param[in,out] current_policy The policy context which stores the state
 *                of the policy execution.
 * @retval TSS2_RC_SUCCESS on success.
 * @retval TSS2_FAPI_RC_BAD_VALUE if an invalid value was passed into
 *         the function.
 * @retval TSS2_FAPI_RC_TRY_AGAIN if an I/O operation is not finished yet and
 *         this function needs to be called again.
 * @retval TSS2_FAPI_RC_BAD_SEQUENCE if the context has an asynchronous
 *         operation already pending.
 * @retval TSS2_FAPI_RC_GENERAL_FAILURE if an internal error occured.
 * @retval TSS2_FAPI_RC_BAD_REFERENCE a invalid null pointer is passed.
 * @retval TSS2_FAPI_RC_POLICY_UNKNOWN if policy search for a certain policy digest
 *         was not successful.
 * @retval TSS2_FAPI_RC_AUTHORIZATION_UNKNOWN if a required authorization callback
 *         is not set.
 * @retval TSS2_FAPI_RC_MEMORY if not enough memory can be allocated.
 * @retval TSS2_ESYS_RC_* possible error codes of ESAPI.
 * @retval TSS2_FAPI_RC_PATH_NOT_FOUND if a FAPI object path was not found
 *         during authorization.
 * @retval TSS2_FAPI_RC_KEY_NOT_FOUND if a key was not found.
 * @retval TSS2_FAPI_RC_BAD_PATH if the path is used in inappropriate context
 *         or contains illegal characters.
 * @retval TSS2_FAPI_RC_NOT_PROVISIONED FAPI was not provisioned.
 * @retval TSS2_FAPI_RC_IO_ERROR if an error occured while accessing the
 *         object store.
 * @retval TSS2_FAPI_RC_AUTHORIZATION_FAILED if the authorization attempt fails.
 */
static TSS2_RC
execute_policy_authorize(
    ESYS_CONTEXT *esys_ctx,
    TPMS_POLICYAUTHORIZE *policy,
    TPMI_ALG_HASH hash_alg,
    IFAPI_POLICY_EXEC_CTX *current_policy)
{
    TSS2_RC r = TSS2_RC_SUCCESS;
    TPM2B_PUBLIC public2b;
    TPM2B_DIGEST aHash;
    IFAPI_CRYPTO_CONTEXT_BLOB *cryptoContext;
    size_t hash_size;
    size_t size;
    TPMT_TK_VERIFIED *ticket;
    TPM2B_NAME *tmp_name = NULL;

    LOG_TRACE("call");
    public2b.size = 0;
    if (!(hash_size = ifapi_hash_get_digest_size(hash_alg))) {
        goto_error(r, TSS2_FAPI_RC_BAD_VALUE,
                   "Unsupported hash algorithm (%" PRIu16 ")", cleanup,
                   hash_alg);
    }
    switch (current_policy->state) {
    statecase(current_policy->state, POLICY_EXECUTE_INIT);
        current_policy->object_handle = ESYS_TR_NONE;
        /* Execute authorized policy. */
        ifapi_policyeval_EXEC_CB *cb = &current_policy->callbacks;
        r = cb->cbauthpol(&policy->keyPublic, hash_alg, &policy->approvedPolicy,
                          &policy->policyRef,
                          &policy->signature, cb->cbauthpol_userdata);
        return_try_again(r);
        goto_if_error(r, "Execute authorized policy.", cleanup);

        public2b.size = 0;
        public2b.publicArea = policy->keyPublic;
        r = Esys_LoadExternal_Async(esys_ctx,
                                    ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE,
                                    NULL,  &public2b, ESYS_TR_RH_OWNER);
        goto_if_error(r, "LoadExternal_Async", cleanup);
        fallthrough;

    statecase(current_policy->state, POLICY_LOAD_KEY);
        r = Esys_LoadExternal_Finish(esys_ctx, &current_policy->object_handle);
        try_again_or_error(r, "Load external key.");

        /* Save key name for policy execution */
        r = Esys_TR_GetName(esys_ctx, current_policy->object_handle, &tmp_name);
        return_if_error(r, "Get key name.");
        policy->keyName = *tmp_name;
        SAFE_FREE(tmp_name);

        /* Use policyRef and policy to compute authorization hash */
        r = ifapi_crypto_hash_start(&cryptoContext, hash_alg);
        return_if_error(r, "crypto hash start");

        HASH_UPDATE_BUFFER(cryptoContext, &policy->approvedPolicy.buffer[0],
                           hash_size, r, cleanup);
        HASH_UPDATE_BUFFER(cryptoContext, &policy->policyRef.buffer[0],
                           policy->policyRef.size, r, cleanup);
        r = ifapi_crypto_hash_finish(&cryptoContext,
                                     (uint8_t *) &aHash.buffer[0],
                                     &size);
        return_if_error(r, "crypto hash finish");

        aHash.size = size;
        LOGBLOB_TRACE(&policy->policyRef.buffer[0], policy->policyRef.size, "policyRef");
        LOGBLOB_TRACE(&aHash.buffer[0], aHash.size, "aHash");

        /* Verify the signature retrieved from the authorized policy against
           the computed ahash. */
        r = Esys_VerifySignature_Async(esys_ctx, current_policy->object_handle,
                                       ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE,
                                       &aHash,
                                       &policy->signature);
        goto_if_error(r, "Verify signature", cleanup);
        fallthrough;

    statecase(current_policy->state, POLICY_VERIFY);
        r = Esys_VerifySignature_Finish(esys_ctx, &ticket);
        try_again_or_error(r, "Verify signature.");

        /* Execute policy authorize */
        policy->checkTicket = *ticket;
        SAFE_FREE(ticket);
        r = Esys_PolicyAuthorize_Async(esys_ctx,
                                       current_policy->session,
                                       ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE,
                                       &policy->approvedPolicy,
                                       &policy->policyRef,
                                       &policy->keyName,
                                       &policy->checkTicket);
        goto_if_error(r, "Policy Authorize", cleanup);
        fallthrough;

    statecase(current_policy->state, POLICY_EXECUTE_FINISH);
        r = Esys_PolicyAuthorize_Finish(esys_ctx);
        try_again_or_error(r, "Execute PolicyAuthorize.");

        r = Esys_FlushContext_Async(esys_ctx, current_policy->object_handle);
        goto_if_error(r, "FlushContext_Async", cleanup);
        fallthrough;

    statecase(current_policy->state, POLICY_FLUSH_KEY);
        /* Flush key used for verification. */
        r = Esys_FlushContext_Finish(esys_ctx);
        try_again_or_error(r, "Flush key finish.");

        current_policy->object_handle = ESYS_TR_NONE;
        current_policy->state = POLICY_EXECUTE_INIT;
        break;

    statecasedefault(current_policy->state);
    }
cleanup:
    /* In error cases object might not have been flushed. */
    if (current_policy->object_handle != ESYS_TR_NONE)
        Esys_FlushContext(esys_ctx, current_policy->object_handle);

    return r;
}

/** Execute a policy whose digest is stored in the NV ram.
 *
 * The policy will be retrieved from policy store based on the policy digest
 * stored in NV ram.
 * The authorization for the NV object, the policy retrieval, and the execution
 * is done via a callback. For an example callback implementation see
 * ifapi_exec_auth_nv_policy().
 *
 * @param[in,out] *esys_ctx The ESAPI context which is needed to execute the
 *                policy command.
 * @param[in,out] policy The policy which defines the policy to be authorized
 *                and the used NV object.
 *                The policy digest will be added to the policy.
 * @param[in]     current_hash_alg The hash algorithm wich will be used for
 *                policy computation.
 * @param[in,out] current_policy The policy context which stores the state
 *                of the policy execution.
 * @retval TSS2_RC_SUCCESS on success.
 * @retval TSS2_FAPI_RC_TRY_AGAIN if an I/O operation is not finished yet and
 *         this function needs to be called again.
 * @retval TSS2_FAPI_RC_BAD_SEQUENCE if the context has an asynchronous
 *         operation already pending.
 * @retval TSS2_FAPI_RC_BAD_REFERENCE a invalid null pointer is passed.
 * @retval TSS2_FAPI_RC_BAD_VALUE if an invalid value was passed into
 *         the function.
 * @retval TSS2_FAPI_RC_POLICY_UNKNOWN if policy search for a certain policy digest
 *         was not successful.
 * @retval TSS2_ESYS_RC_* possible error codes of ESAPI.
 * @retval TSS2_FAPI_RC_MEMORY if not enough memory can be allocated.
 * @retval TSS2_FAPI_RC_GENERAL_FAILURE if an internal error occured.
 * @retval TSS2_FAPI_RC_PATH_NOT_FOUND if a FAPI object path was not found
 *         during authorization.
 * @retval TSS2_FAPI_RC_KEY_NOT_FOUND if a key was not found.
 * @retval TSS2_FAPI_RC_BAD_PATH if the path is used in inappropriate context
 *         or contains illegal characters.
 * @retval TSS2_FAPI_RC_NOT_PROVISIONED FAPI was not provisioned.
 * @retval TSS2_FAPI_RC_IO_ERROR if an error occured while accessing the
 *         object store.
 * @retval TSS2_FAPI_RC_AUTHORIZATION_UNKNOWN if a required authorization callback
 *         is not set.
 * @retval TSS2_FAPI_RC_AUTHORIZATION_FAILED if the authorization attempt fails.
 */
static TSS2_RC
execute_policy_authorize_nv(
    ESYS_CONTEXT *esys_ctx,
    TPMS_POLICYAUTHORIZENV *policy,
    TPMI_ALG_HASH hash_alg,
    IFAPI_POLICY_EXEC_CTX *current_policy)
{
    TSS2_RC r = TSS2_RC_SUCCESS;
    ifapi_policyeval_EXEC_CB *cb;

    LOG_DEBUG("call");
    cb = &current_policy->callbacks;

    switch (current_policy->state) {
    statecase(current_policy->state, POLICY_EXECUTE_INIT)
        /* Execute the policy stored in the NV object. */
        r = cb->cbauthnv(&policy->nvPublic, hash_alg, cb->cbauthpol_userdata);
        try_again_or_error(r, "Execute policy authorize nv callback.");

        r = ifapi_nv_get_name(&policy->nvPublic, &current_policy->name);
        return_if_error(r, "Compute NV name");
        fallthrough;

    statecase(current_policy->state, POLICY_AUTH_CALLBACK)
        /* Authorize the NV object for policy execution. */
        r = cb->cbauth(&current_policy->name,
                       &current_policy->object_handle,
                       &current_policy->auth_handle,
                       &current_policy->auth_session, cb->cbauth_userdata);
        return_try_again(r);
        goto_if_error(r, "Execute authorized policy.", cleanup);
        fallthrough;

    statecase(current_policy->state, POLICY_EXEC_ESYS)
        LOG_DEBUG("**STATE** POLICY_EXEC_ESYS");
        /* Prepare the policy execution. */
        r = Esys_PolicyAuthorizeNV_Async(esys_ctx,
                                         current_policy->auth_handle,
                                         current_policy->object_handle,
                                         current_policy->session,
                                         current_policy->auth_session, ESYS_TR_NONE,
                                         ESYS_TR_NONE);
        goto_if_error(r, "PolicyAuthorizeNV_Async", cleanup);
        fallthrough;

    statecase(current_policy->state, POLICY_AUTH_SENT)
        /* Finalize the policy execution if possible. */
        r = Esys_PolicyAuthorizeNV_Finish(esys_ctx);
        return_try_again(r);
        goto_if_error(r, "FAPI PolicyAuthorizeNV_Finish", cleanup);
        current_policy->state = POLICY_EXECUTE_INIT;
        break;

    statecasedefault(current_policy->state);
    }
cleanup:
    return r;
}

/** Execute  a policy based on a secret-based authorization.
 *
 * The policy defines an object whose secret is needed for policy execution.
 * The authorization of the object is done via a callback.
 * For an example callback implementation see ifapi_policyeval_cbauth;().
 *
 * @param[in,out] *esys_ctx The ESAPI context which is needed to execute the
 *                policy command.
 * @param[in,out] policy The policy which defines the object whose secret
 *                is needed for policy execution.
 *                The policy digest will be added to the policy.
 * @param[in,out] current_policy The policy context which stores the state
 *                of the policy execution.
 * @retval TSS2_RC_SUCCESS on success.
 * @retval TSS2_FAPI_RC_TRY_AGAIN if an I/O operation is not finished yet and
 *         this function needs to be called again.
 * @retval TSS2_FAPI_RC_BAD_SEQUENCE if the context has an asynchronous
 *         operation already pending.
 * @retval TSS2_FAPI_RC_BAD_REFERENCE a invalid null pointer is passed.
 * @retval TSS2_ESYS_RC_* possible error codes of ESAPI.
 * @retval TSS2_FAPI_RC_PATH_NOT_FOUND if a FAPI object path was not found
 *         during authorization.
 * @retval TSS2_FAPI_RC_MEMORY if not enough memory can be allocated.
 * @retval TSS2_FAPI_RC_KEY_NOT_FOUND if a key was not found.
 * @retval TSS2_FAPI_RC_BAD_PATH if the path is used in inappropriate context
 *         or contains illegal characters.
 * @retval TSS2_FAPI_RC_BAD_VALUE if an invalid value was passed into
 *         the function.
 * @retval TSS2_FAPI_RC_NOT_PROVISIONED FAPI was not provisioned.
 * @retval TSS2_FAPI_RC_IO_ERROR if an error occured while accessing the
 *         object store.
 * @retval TSS2_FAPI_RC_GENERAL_FAILURE if an internal error occured.
 * @retval TSS2_FAPI_RC_AUTHORIZATION_UNKNOWN if a required authorization callback
 *         is not set.
 * @retval TSS2_FAPI_RC_AUTHORIZATION_FAILED if the authorization attempt fails.
 * @retval TSS2_FAPI_RC_POLICY_UNKNOWN if policy search for a certain policy digest
 *         was not successful.
 */
static TSS2_RC
execute_policy_secret(
    ESYS_CONTEXT *esys_ctx,
    TPMS_POLICYSECRET *policy,
    IFAPI_POLICY_EXEC_CTX *current_policy)
{
    TSS2_RC r = TSS2_RC_SUCCESS;

    LOG_DEBUG("call");

    switch (current_policy->state) {
    statecase(current_policy->state, POLICY_EXECUTE_INIT)
        ifapi_policyeval_EXEC_CB *cb = &current_policy->callbacks;
        /* Callback for the object authorization. */
        r = cb->cbauth(&policy->objectName,
                       &current_policy->object_handle,
                       &current_policy->auth_handle,
                   &current_policy->auth_session, cb->cbauth_userdata);
        return_try_again(r);
        goto_if_error(r, "Authorize object callback.", cleanup);
        fallthrough;

    statecase(current_policy->state, POLICY_EXEC_ESYS)
        r = Esys_TRSess_GetNonceTPM(esys_ctx, current_policy->session,
                                    &current_policy->nonceTPM);
        goto_if_error(r, "Get TPM nonce.", cleanup);

        policy->nonceTPM = *(current_policy->nonceTPM);
        SAFE_FREE(current_policy->nonceTPM);

        /* Prepare the policy execution. */
        r = Esys_PolicySecret_Async(esys_ctx,
                                    current_policy->auth_handle,
                                    current_policy->session,
                                    current_policy->auth_session, ESYS_TR_NONE,
                                    ESYS_TR_NONE, &policy->nonceTPM,
                                    &policy->cpHashA, &policy->policyRef,
                                    0);
        goto_if_error(r, "PolicySecret_Async", cleanup);
        fallthrough;

    statecase(current_policy->state, POLICY_AUTH_SENT)
        /* Finalize the policy execution if possible. */
        r = Esys_PolicySecret_Finish(esys_ctx, NULL,
                                     NULL);
        return_try_again(r);
        goto_if_error(r, "FAPI PolicyAuthorizeNV_Finish", error_cleanup);
        if (!current_policy->flush_handle) {
            current_policy->state = POLICY_EXECUTE_INIT;
            return r;
        }
        r = Esys_FlushContext_Async(esys_ctx, current_policy->auth_handle);
        goto_if_error(r, "FlushContext_Async", cleanup);
        fallthrough;

    statecase(current_policy->state, POLICY_FLUSH_KEY);
        r = Esys_FlushContext_Finish(esys_ctx);
        try_again_or_error(r, "Flush key finish.");
        current_policy->state = POLICY_EXECUTE_INIT;
        break;

    statecasedefault(current_policy->state);
    }

cleanup:
    return r;

 error_cleanup:
    if (current_policy->flush_handle) {
         Esys_FlushContext(esys_ctx, current_policy->auth_handle);
    }
    SAFE_FREE(current_policy->nonceTPM);
    return r;
}

/** Execute a policy depending on the TPM timers.
 *
 * @param[in,out] *esys_ctx The ESAPI context which is needed to execute the
 *                policy command.
 * @param[in,out] policy The policy which defines the values for the comparision
 *                with the TPM timers and the comparision operation.
 *                The policy digest will be added to the policy.
 * @param[in]     current_hash_alg The hash algorithm wich will be used for
 *                policy computation.
 * @param[in,out] current_policy The policy context which stores the state
 *                of the policy execution.
 * @retval TSS2_RC_SUCCESS on success.
 * @retval TSS2_FAPI_RC_TRY_AGAIN if an I/O operation is not finished yet and
 *         this function needs to be called again.
 * @retval TSS2_FAPI_RC_BAD_SEQUENCE if the context has an asynchronous
 *         operation already pending.
 * @retval TSS2_ESYS_RC_* possible error codes of ESAPI.
 */
static TSS2_RC
execute_policy_counter_timer(
    ESYS_CONTEXT *esys_ctx,
    TPMS_POLICYCOUNTERTIMER *policy,
    IFAPI_POLICY_EXEC_CTX *current_policy)
{
    TSS2_RC r = TSS2_RC_SUCCESS;

    LOG_TRACE("call");

    switch (current_policy->state) {
    statecase(current_policy->state, POLICY_EXECUTE_INIT)
        /* Prepare the policy execution. */
        r = Esys_PolicyCounterTimer_Async(esys_ctx,
                                          current_policy->session,
                                          ESYS_TR_NONE, ESYS_TR_NONE,
                                          ESYS_TR_NONE,
                                          &policy->operandB,
                                          policy->offset,
                                          policy->operation);
        return_if_error(r, "Execute PolicyCounter.");
        fallthrough;

    statecase(current_policy->state, POLICY_EXECUTE_FINISH)
        /* Finalize the policy execution if possible. */
        r = Esys_PolicyCounterTimer_Finish(esys_ctx);
        try_again_or_error(r, "Execute PolicyCounterTImer_Finish.");

        current_policy->state = POLICY_EXECUTE_INIT;
        return r;

    statecasedefault(current_policy->state);
    }
    return r;
}

/** Execute a policy depending on physical presence.
 *
 * @param[in,out] *esys_ctx The ESAPI context which is needed to execute the
 *                policy command.
 * @param[in,out] current_policy The policy context which stores the state
 *                of the policy execution.
 * @retval TSS2_RC_SUCCESS on success.
 * @retval TSS2_FAPI_RC_TRY_AGAIN if an I/O operation is not finished yet and
 *         this function needs to be called again.
 * @retval TSS2_FAPI_RC_BAD_SEQUENCE if the context has an asynchronous
 *         operation already pending.
 * @retval TSS2_ESYS_RC_* possible error codes of ESAPI.
 */
static TSS2_RC
execute_policy_physical_presence(
    ESYS_CONTEXT *esys_ctx,
    IFAPI_POLICY_EXEC_CTX *current_policy)
{
    TSS2_RC r = TSS2_RC_SUCCESS;

    LOG_TRACE("call");

    switch (current_policy->state) {
    statecase(current_policy->state, POLICY_EXECUTE_INIT)
        /* Prepare the policy execution. */
        r = Esys_PolicyPhysicalPresence_Async(esys_ctx,
                                              current_policy->session,
                                              ESYS_TR_NONE, ESYS_TR_NONE,
                                              ESYS_TR_NONE);
        return_if_error(r, "Execute PolicyPhysicalpresence.");
        fallthrough;

    statecase(current_policy->state, POLICY_EXECUTE_FINISH)
        /* Finalize the policy execution if possible. */
        r = Esys_PolicyPhysicalPresence_Finish(esys_ctx);
        try_again_or_error(r, "Execute PolicyPhysicalPresence_Finish.");

        current_policy->state = POLICY_EXECUTE_INIT;
        return r;

    statecasedefault(current_policy->state);
    }
    return r;
}

/** Execute a policy for binding a authorization value of the authorized entity.
 *
 * The authValue will be included in hmacKey of the session.

 * @param[in,out] *esys_ctx The ESAPI context which is needed to execute the
 *                policy command.
 * @param[in,out] current_policy The policy context which stores the state
 *                of the policy execution.
 * @retval TSS2_RC_SUCCESS on success.
 * @retval TSS2_FAPI_RC_TRY_AGAIN if an I/O operation is not finished yet and
 *         this function needs to be called again.
 * @retval TSS2_FAPI_RC_BAD_SEQUENCE if the context has an asynchronous
 *         operation already pending.
 * @retval TSS2_ESYS_RC_* possible error codes of ESAPI.
 */
static TSS2_RC
execute_policy_auth_value(
    ESYS_CONTEXT *esys_ctx,
    IFAPI_POLICY_EXEC_CTX *current_policy)
{
    TSS2_RC r = TSS2_RC_SUCCESS;

    LOG_TRACE("call");

    switch (current_policy->state) {
    statecase(current_policy->state, POLICY_EXECUTE_INIT)
        /* Prepare the policy execution. */
        r = Esys_PolicyAuthValue_Async(esys_ctx,
                                       current_policy->session,
                                       ESYS_TR_NONE, ESYS_TR_NONE,
                                       ESYS_TR_NONE);
        return_if_error(r, "Execute PolicyAuthValue.");
        fallthrough;

    statecase(current_policy->state, POLICY_EXECUTE_FINISH)
        /* Finalize the policy execution if possible. */
        r = Esys_PolicyAuthValue_Finish(esys_ctx);
        try_again_or_error(r, "Execute PolicyAuthValue_Finish.");

        current_policy->state = POLICY_EXECUTE_INIT;
        return r;

    statecasedefault(current_policy->state);
    }
    return r;
}

/** Execute a policy for binding a authorization value of the authorized object.
 *
 * The authValue of the authorized object will be checked when the session is used
 * for authorization..
 *
 * @param[in,out] *esys_ctx The ESAPI context which is needed to execute the
 *                policy command.
 * @param[in,out] current_policy The policy context which stores the state
 *                of the policy execution.
 * @retval TSS2_RC_SUCCESS on success.
 * @retval TSS2_FAPI_RC_TRY_AGAIN if an I/O operation is not finished yet and
 *         this function needs to be called again.
 * @retval TSS2_FAPI_RC_BAD_SEQUENCE if the context has an asynchronous
 *         operation already pending.
 * @retval TSS2_ESYS_RC_* possible error codes of ESAPI.
 */
static TSS2_RC
execute_policy_password(
    ESYS_CONTEXT *esys_ctx,
    IFAPI_POLICY_EXEC_CTX *current_policy)
{
    TSS2_RC r = TSS2_RC_SUCCESS;

    LOG_TRACE("call");

    switch (current_policy->state) {
    statecase(current_policy->state, POLICY_EXECUTE_INIT)
        /* Prepare the policy execution. */
        r = Esys_PolicyPassword_Async(esys_ctx,
                                      current_policy->session,
                                      ESYS_TR_NONE, ESYS_TR_NONE,
                                      ESYS_TR_NONE);
        return_if_error(r, "Execute PolicyPassword.");
        fallthrough;

    statecase(current_policy->state, POLICY_EXECUTE_FINISH)
        /* Finalize the policy execution if possible. */
        r = Esys_PolicyPassword_Finish(esys_ctx);
        try_again_or_error(r, "Execute PolicyPassword_Finish.");

        current_policy->state = POLICY_EXECUTE_INIT;
        return r;

    statecasedefault(current_policy->state);
    }
    return r;
}

/** Execute a policy to limit an authorization to a specific command code.
 *
 * @param[in,out] *esys_ctx The ESAPI context which is needed to execute the
 *                policy command.
 * @param[in,out] policy The policy with the command code used fo limitting.
 * @param[in,out] current_policy The policy context which stores the state
 *                of the policy execution.
 * @retval TSS2_RC_SUCCESS on success.
 * @retval TSS2_FAPI_RC_TRY_AGAIN if an I/O operation is not finished yet and
 *         this function needs to be called again.
 * @retval TSS2_FAPI_RC_BAD_SEQUENCE if the context has an asynchronous
 *         operation already pending.
 * @retval TSS2_ESYS_RC_* possible error codes of ESAPI.
 */
static TSS2_RC
execute_policy_command_code(
    ESYS_CONTEXT *esys_ctx,
    TPMS_POLICYCOMMANDCODE *policy,
    IFAPI_POLICY_EXEC_CTX *current_policy)
{
    TSS2_RC r = TSS2_RC_SUCCESS;

    LOG_TRACE("call");

    switch (current_policy->state) {
    statecase(current_policy->state, POLICY_EXECUTE_INIT)
        /* Prepare the policy execution. */
        r = Esys_PolicyCommandCode_Async(esys_ctx,
                                         current_policy->session,
                                         ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE,
                                         policy->code);
        return_if_error(r, "Execute PolicyCommandCode.");
        fallthrough;

    statecase(current_policy->state, POLICY_EXECUTE_FINISH)
        /* Finalize the policy execution if possible. */
        r = Esys_PolicyCommandCode_Finish(esys_ctx);
        try_again_or_error(r, "Execute PolicyCommandCode_Finish.");

        current_policy->state = POLICY_EXECUTE_INIT;
        return r;

    statecasedefault(current_policy->state)
    }
    return r;
}

/** Execute a policy for binding the policy to a specific set of TPM entities.
 *
 * Up to three entity names can be defined in the policy.
 *
 * @param[in,out] *esys_ctx The ESAPI context which is needed to execute the
 *                policy command.
 * @param[in,out] policy The policy with the entity names.
 * @param[in,out] current_policy The policy context which stores the state
 *                of the policy execution.
 * @retval TSS2_RC_SUCCESS on success.
 * @retval TSS2_FAPI_RC_TRY_AGAIN if an I/O operation is not finished yet and
 *         this function needs to be called again.
 * @retval TSS2_FAPI_RC_BAD_SEQUENCE if the context has an asynchronous
 *         operation already pending.
 * @retval TSS2_ESYS_RC_* possible error codes of ESAPI.
 */
static TSS2_RC
execute_policy_name_hash(
    ESYS_CONTEXT *esys_ctx,
    TPMS_POLICYNAMEHASH *policy,
    IFAPI_POLICY_EXEC_CTX *current_policy)
{
    TSS2_RC r = TSS2_RC_SUCCESS;

    LOG_TRACE("call");

    switch (current_policy->state) {
    statecase(current_policy->state, POLICY_EXECUTE_INIT)
        /* Prepare the policy execution. */
        r = Esys_PolicyNameHash_Async(esys_ctx,
                                      current_policy->session,
                                      ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE,
                                      &policy->nameHash);
        return_if_error(r, "Execute PolicyNameH.");
        fallthrough;

    statecase(current_policy->state, POLICY_EXECUTE_FINISH)
        /* Finalize the policy execution if possible. */
        r = Esys_PolicyNameHash_Finish(esys_ctx);
        try_again_or_error(r, "Execute PolicyNameHash_Finish.");

        current_policy->state = POLICY_EXECUTE_INIT;
        return r;

    statecasedefault(current_policy->state)
    }
    return r;
}

/** Execute a policy for binding the policy to command parameters.
 *
 * @param[in,out] *esys_ctx The ESAPI context which is needed to execute the
 *                policy command.
 * @param[in,out] policy The policy with the cp hash.
 * @param[in,out] current_policy The policy context which stores the state
 *                of the policy execution.
 * @retval TSS2_RC_SUCCESS on success.
 * @retval TSS2_FAPI_RC_TRY_AGAIN if an I/O operation is not finished yet and
 *         this function needs to be called again.
 * @retval TSS2_FAPI_RC_BAD_SEQUENCE if the context has an asynchronous
 *         operation already pending.
 * @retval TSS2_ESYS_RC_* possible error codes of ESAPI.
 */
static TSS2_RC
execute_policy_cp_hash(
    ESYS_CONTEXT *esys_ctx,
    TPMS_POLICYCPHASH *policy,
    IFAPI_POLICY_EXEC_CTX *current_policy)
{
    TSS2_RC r = TSS2_RC_SUCCESS;

    LOG_TRACE("call");

    switch (current_policy->state) {
    statecase(current_policy->state, POLICY_EXECUTE_INIT)
        /* Prepare the policy execution. */
        r = Esys_PolicyCpHash_Async(esys_ctx,
                                    current_policy->session,
                                    ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE,
                                    &policy->cpHash);
        return_if_error(r, "Execute PolicyNameH.");

        fallthrough;

    statecase(current_policy->state, POLICY_EXECUTE_FINISH)
        /* Finalize the policy execution if possible. */
        r = Esys_PolicyCpHash_Finish(esys_ctx);
        try_again_or_error(r, "Execute PolicyCpHash_Finish.");

        current_policy->state = POLICY_EXECUTE_INIT;
        return r;

    statecasedefault(current_policy->state);
    }
    return r;
}

/** Execute a policy for binding the policy to a certain locality.
 *
 * @param[in,out] *esys_ctx The ESAPI context which is needed to execute the
 *                policy command.
 * @param[in,out] policy The policy with the locality.
 * @param[in,out] current_policy The policy context which stores the state
 *                of the policy execution.
 * @retval TSS2_RC_SUCCESS on success.
 * @retval TSS2_FAPI_RC_TRY_AGAIN if an I/O operation is not finished yet and
 *         this function needs to be called again.
 * @retval TSS2_FAPI_RC_BAD_SEQUENCE if the context has an asynchronous
 *         operation already pending.
 * @retval TSS2_ESYS_RC_* possible error codes of ESAPI.
 */
static TSS2_RC
execute_policy_locality(
    ESYS_CONTEXT *esys_ctx,
    TPMS_POLICYLOCALITY *policy,
    IFAPI_POLICY_EXEC_CTX *current_policy)
{
    TSS2_RC r = TSS2_RC_SUCCESS;

    LOG_TRACE("call");

    switch (current_policy->state) {
    statecase(current_policy->state, POLICY_EXECUTE_INIT)
        /* Prepare the policy execution. */
        r = Esys_PolicyLocality_Async(esys_ctx,
                                      current_policy->session,
                                      ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE,
                                      policy->locality);
        return_if_error(r, "Execute PolicyLocality.");
        fallthrough;

    statecase(current_policy->state, POLICY_EXECUTE_FINISH)
        /* Finalize the policy execution if possible. */
        r = Esys_PolicyLocality_Finish(esys_ctx);
        try_again_or_error(r, "Execute PolicyNV_Finish.");

        current_policy->state = POLICY_EXECUTE_INIT;
        return r;

    statecasedefault(current_policy->state);
    }
    return r;
}

/** Execute a policy for binding the policy to the NV written state.
 *
 * The state NV written yes or NV written no can be defined in the policy.
 *
 * @param[in,out] *esys_ctx The ESAPI context which is needed to execute the
 *                policy command.
 * @param[in,out] policy The policy with the NV written switch YES or NO.
 * @param[in,out] current_policy The policy context which stores the state
 *                of the policy execution.
 * @retval TSS2_RC_SUCCESS on success.
 * @retval TSS2_FAPI_RC_TRY_AGAIN if an I/O operation is not finished yet and
 *         this function needs to be called again.
 * @retval TSS2_FAPI_RC_BAD_SEQUENCE if the context has an asynchronous
 *         operation already pending.
 * @retval TSS2_ESYS_RC_* possible error codes of ESAPI.
 */
static TSS2_RC
execute_policy_nv_written(
    ESYS_CONTEXT *esys_ctx,
    TPMS_POLICYNVWRITTEN *policy,
    IFAPI_POLICY_EXEC_CTX *current_policy)
{
    TSS2_RC r = TSS2_RC_SUCCESS;

    LOG_TRACE("call");

    switch (current_policy->state) {
    statecase(current_policy->state, POLICY_EXECUTE_INIT)
        /* Prepare the policy execution. */
        r = Esys_PolicyNvWritten_Async(esys_ctx,
                                       current_policy->session,
                                       ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE,
                                       policy->writtenSet);
        return_if_error(r, "Execute PolicyNvWritten.");
        fallthrough;

    statecase(current_policy->state, POLICY_EXECUTE_FINISH)
        /* Finalize the policy execution if possible. */
        r = Esys_PolicyNvWritten_Finish(esys_ctx);
        try_again_or_error(r, "Execute PolicyNV_Finish.");

        current_policy->state = POLICY_EXECUTE_INIT;
        return r;

    statecasedefault(current_policy->state);
    }
    return r;
}

/** Execute a policy for binding the policy to the NV written state.
 *
 * The state NV written yes or NV written no can be defined in the policy.
 *
 * @param[in,out] *esys_ctx The ESAPI context which is needed to execute the
 *                policy command.
 * @param[in,out] policy The policy with the NV written switch YES or NO.
 * @param[in,out] current_policy The policy context which stores the state
 *                of the policy execution.
 * @retval TSS2_RC_SUCCESS on success.
 * @retval TSS2_FAPI_RC_TRY_AGAIN if an I/O operation is not finished yet and
 *         this function needs to be called again.
 * @retval TSS2_FAPI_RC_BAD_SEQUENCE if the context has an asynchronous
 *         operation already pending.
 * @retval TSS2_ESYS_RC_* possible error codes of ESAPI.
 * @retval TSS2_FAPI_RC_BAD_VALUE if an invalid value was passed into
 *         the function.
 */
static TSS2_RC
execute_policy_or(
    ESYS_CONTEXT *esys_ctx,
    TPMS_POLICYOR *policy,
    TPMI_ALG_HASH current_hash_alg,
    IFAPI_POLICY_EXEC_CTX *current_policy)
{
    TSS2_RC r = TSS2_RC_SUCCESS;

    LOG_TRACE("call");

    switch (current_policy->state) {
    statecase(current_policy->state, POLICY_EXECUTE_INIT)
        /* Prepare the policy execution. */
        r = compute_or_digest_list(policy->branches, current_hash_alg,
                                      &current_policy->digest_list);
        return_if_error(r, "Compute policy or digest list.");

        r = Esys_PolicyOR_Async(esys_ctx,
                                current_policy->session,
                                ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE,
                                &current_policy->digest_list);
        return_if_error(r, "Execute PolicyPCR.");
        fallthrough;
    statecase(current_policy->state, POLICY_EXECUTE_FINISH)
        /* Finalize the policy execution if possible. */
        r = Esys_PolicyOR_Finish(esys_ctx);
        try_again_or_error(r, "Execute PolicyPCR_Finish.");

        current_policy->state = POLICY_EXECUTE_INIT;
        return r;

    statecasedefault(current_policy->state);
    }
}

/** Execute a policy for executing a callback during policy execution.
 *
 * The action name stored in the policy name will be passed do the callback
 * function. The policy action callback has to be set with the function:
 * Fapi_SetPolicyActionCB().
 *
 * @param[in,out] *esys_ctx The ESAPI context which is needed to execute the
 *                policy command.
 * @param[in,out] policy The policy with action name.
 * @param[in,out] current_policy The policy context which stores the state
 *                of the policy execution.
 * @retval TSS2_RC_SUCCESS on success.
 * @retval TSS2_FAPI_RC_TRY_AGAIN if an I/O operation is not finished yet and
 *         this function needs to be called again.
 * @retval TSS2_FAPI_RC_BAD_SEQUENCE if the context has an asynchronous
 *         operation already pending.
 * @retval TSS2_FAPI_RC_BAD_REFERENCE a invalid null pointer is passed.
 * @retval TSS2_FAPI_RC_AUTHORIZATION_UNKNOWN if a required authorization callback
 *         is not set.
 */
static TSS2_RC
execute_policy_action(
    ESYS_CONTEXT *esys_ctx,
    TPMS_POLICYACTION *policy,
    IFAPI_POLICY_EXEC_CTX *current_policy)
{
    TSS2_RC r = TSS2_RC_SUCCESS;
    UNUSED(esys_ctx);
    LOG_TRACE("call");

    switch (current_policy->state) {
    statecase(current_policy->state, POLICY_EXECUTE_INIT);
        ifapi_policyeval_EXEC_CB *cb = &current_policy->callbacks;

        /* Execute the callback and try it again if the callback is not finished. */
        r = cb->cbaction(policy->action, cb->cbaction_userdata);
        try_again_or_error(r, "Execute policy action callback.");
        current_policy->state = POLICY_EXECUTE_INIT;
        return r;

    statecasedefault(current_policy->state);
    }
}

/** Execute a policy element depending on the type.
 *
 * @param[in,out] *esys_ctx The ESAPI context which is needed to execute the
 *                policy command.
 * @param[in,out] policy The policy element with the policy to be executed and
 *                the type of the policy.
 * @param[in,out] current_policy The policy context which stores the state
 *                of the policy execution.
 * @retval TSS2_RC_SUCCESS on success.
 * @retval TSS2_FAPI_RC_GENERAL_FAILURE if an internal error occured.
 * @retval TSS2_FAPI_RC_TRY_AGAIN if an I/O operation is not finished yet and
 *         this function needs to be called again.
 * @retval TSS2_FAPI_RC_BAD_SEQUENCE if the context has an asynchronous
 *         operation already pending.
 * @retval TSS2_FAPI_RC_BAD_REFERENCE a invalid null pointer is passed.
 * @retval TSS2_FAPI_RC_PATH_NOT_FOUND if a FAPI object path was not found
 *         during authorization.
 * @retval TSS2_FAPI_RC_MEMORY if not enough memory can be allocated.
 * @retval TSS2_FAPI_RC_KEY_NOT_FOUND if a key was not found.
 * @retval TSS2_FAPI_RC_BAD_PATH if the path is used in inappropriate context
 *         or contains illegal characters.
 * @retval TSS2_FAPI_RC_BAD_VALUE if an invalid value was passed into
 *         the function.
 * @retval TSS2_FAPI_RC_NOT_PROVISIONED FAPI was not provisioned.
 * @retval TSS2_FAPI_RC_IO_ERROR if an error occured while accessing the
 *         object store.
 * @retval TSS2_FAPI_RC_AUTHORIZATION_UNKNOWN if a required authorization callback
 *         is not set.
 * @retval TSS2_FAPI_RC_AUTHORIZATION_FAILED if the authorization attempt fails.
 * @retval TSS2_FAPI_RC_POLICY_UNKNOWN if policy search for a certain policy digest
 *         was not successful.
 */
static TSS2_RC
execute_policy_element(
    ESYS_CONTEXT *esys_ctx,
    TPMT_POLICYELEMENT *policy,
    TPMI_ALG_HASH hash_alg,
    IFAPI_POLICY_EXEC_CTX *current_policy)
{
    TSS2_RC r = TSS2_RC_SUCCESS;

    LOG_TRACE("call");

    switch (policy->type) {
    case POLICYSECRET:
        r = execute_policy_secret(esys_ctx,
                                  &policy->element.PolicySecret,
                                  current_policy);
        try_again_or_error_goto(r, "Execute policy authorize", error);
        break;
    case POLICYPCR:
        r = execute_policy_pcr(esys_ctx,
                               &policy->element.PolicyPCR,
                               hash_alg, current_policy);
        try_again_or_error_goto(r, "Execute policy pcr", error);
        break;
    case POLICYAUTHVALUE:
        r = execute_policy_auth_value(esys_ctx,
                                      current_policy);
        try_again_or_error_goto(r, "Execute policy auth value", error);
        break;
    case POLICYOR:
        r = execute_policy_or(esys_ctx,
                              &policy->element.PolicyOr,
                              hash_alg, current_policy);
        try_again_or_error_goto(r, "Execute policy or", error);
        break;
    case POLICYSIGNED:
        r = execute_policy_signed(esys_ctx,
                                  &policy->element.PolicySigned,
                                  current_policy);
        try_again_or_error_goto(r, "Execute policy signed", error);
        break;
    case POLICYAUTHORIZE:
        r = execute_policy_authorize(esys_ctx,
                                     &policy->element.PolicyAuthorize,
                                     hash_alg,
                                     current_policy);
        try_again_or_error_goto(r, "Execute policy authorize", error);
        break;
    case POLICYAUTHORIZENV:
        r = execute_policy_authorize_nv(esys_ctx,
                                        &policy->element.PolicyAuthorizeNv,
                                        hash_alg,
                                        current_policy);
        try_again_or_error_goto(r, "Execute policy authorize", error);
        break;
    case POLICYNV:
        r = execute_policy_nv(esys_ctx,
                              &policy->element.PolicyNV,
                              current_policy);
        try_again_or_error_goto(r, "Execute policy nv", error);
        break;
    case POLICYDUPLICATIONSELECT:
        r = execute_policy_duplicate(esys_ctx,
                                     &policy->element.PolicyDuplicationSelect,
                                     current_policy);
        try_again_or_error_goto(r, "Execute policy duplicate", error);
        break;
    case POLICYNVWRITTEN:
        r = execute_policy_nv_written(esys_ctx,
                                      &policy->element.PolicyNvWritten,
                                      current_policy);
        try_again_or_error_goto(r, "Execute policy nv written", error);
        break;
    case POLICYLOCALITY:
        r = execute_policy_locality(esys_ctx,
                                    &policy->element.PolicyLocality,
                                    current_policy);
        try_again_or_error_goto(r, "Execute policy locality", error);
        break;
    case POLICYCOMMANDCODE:
        r = execute_policy_command_code(esys_ctx,
                                        &policy->element.PolicyCommandCode,
                                        current_policy);
        try_again_or_error_goto(r, "Execute policy command code", error);
        break;
    case POLICYNAMEHASH:
        r = execute_policy_name_hash(esys_ctx,
                                     &policy->element.PolicyNameHash,
                                     current_policy);
            try_again_or_error_goto(r, "Execute policy name hash", error);
            break;
    case POLICYCPHASH:
        r = execute_policy_cp_hash(esys_ctx,
                                   &policy->element.PolicyCpHash,
                                   current_policy);
        try_again_or_error_goto(r, "Execute policy cp hash", error);
        break;
    case POLICYPHYSICALPRESENCE:
        r = execute_policy_physical_presence(esys_ctx,
                                             current_policy);
        try_again_or_error_goto(r, "Execute policy physical presence", error);
            break;
    case POLICYPASSWORD:
        r = execute_policy_password(esys_ctx,
                                    current_policy);
        try_again_or_error_goto(r, "Execute policy password", error);
        break;
    case POLICYCOUNTERTIMER:
        r = execute_policy_counter_timer(esys_ctx,
                                         &policy->element.PolicyCounterTimer,
                                         current_policy);
        try_again_or_error_goto(r, "Execute policy counter timer", error);
        break;
    case POLICYACTION:
        r = execute_policy_action(esys_ctx,
                                  &policy->element.PolicyAction,
                                  current_policy);
        try_again_or_error_goto(r, "Execute policy action", error);
        break;

    default:
        return_error(TSS2_FAPI_RC_GENERAL_FAILURE,
                     "Policy not implemented");
        }
    return r;
error:
    return r;

    /* All policies executed successfully */
    return r;
}

/** Compute execution order for policies based on branch selection.
 *
 * To simplify asynncronous policy executiion a linked list of the policy structures
 * needed for execution based on the result of the  branch selection callbacks
 * is computed.
 * @retval TSS2_FAPI_RC_MEMORY if not enough memory can be allocated.
 * @retval TSS2_FAPI_RC_BAD_REFERENCE a invalid null pointer is passed.
 * @retval TSS2_FAPI_RC_AUTHORIZATION_UNKNOWN if a required authorization callback
 *         is not set.
 * @retval TSS2_FAPI_RC_AUTHORIZATION_FAILED if the authorization attempt fails.
 */
static TSS2_RC
compute_policy_list(
    IFAPI_POLICY_EXEC_CTX *pol_ctx,
    TPML_POLICYELEMENTS *elements)
{
    TSS2_RC r = TSS2_RC_SUCCESS;
    TPML_POLICYBRANCHES *branches;
    TPML_POLICYELEMENTS *or_elements;
    size_t branch_idx, i;

    for (i = 0; i < elements->count; i++) {
        if (elements->elements[i].type == POLICYOR) {
            branches = elements->elements[i].element.PolicyOr.branches;
            r = pol_ctx->callbacks.cbpolsel(branches, &branch_idx,
                                            pol_ctx->callbacks.cbpolsel_userdata);
            return_if_error(r, "Select policy branch.");
            or_elements = branches->authorizations[branch_idx].policy;
            r = compute_policy_list(pol_ctx, or_elements);
            return_if_error(r, "Compute policy digest list for policy or.");
        }
        r = append_object_to_list(&elements->elements[i], &pol_ctx->policy_elements);
        return_if_error(r, "Extend policy list.");
    }
    return r;
}

/** Initialize policy element list to be executed and store policy in context.
 *
 * @param[in] pol_ctx Context for execution of a list of policy elements.
 * @param[in] hash_alg The hash algorithm used for the policy computation.
 * @param[in,out] policy The policy to be executed. Some policy elements will
 *                be used to store computed parameters needed for policy
 *                execution.
 * @retval TSS2_RC_SUCCESS on success.
 * @retval TSS2_FAPI_RC_AUTHORIZATION_UNKNOWN If the callback for branch selection is
 *         not defined. This callback will be needed of or policies have to be
 *         executed.
 * @retval TSS2_FAPI_RC_BAD_VALUE If the computed branch index deliverd by the
 *         callback does not identify a branch.
 * @retval TSS2_FAPI_RC_MEMORY if not enough memory can be allocated.
 * @retval TSS2_FAPI_RC_BAD_REFERENCE a invalid null pointer is passed.
 * @retval TSS2_FAPI_RC_AUTHORIZATION_FAILED if the authorization attempt fails.
 */
TSS2_RC
ifapi_policyeval_execute_prepare(
    IFAPI_POLICY_EXEC_CTX *pol_ctx,
    TPMI_ALG_HASH hash_alg,
    TPMS_POLICY *policy)
{
    TSS2_RC r;

    pol_ctx->policy = policy;
    pol_ctx->hash_alg = hash_alg;
    r = compute_policy_list(pol_ctx, policy->policy);
    return_if_error(r, "Compute list of policy elements to be executed.");

    return TSS2_RC_SUCCESS;
}

/** Execute all policy commands defined by a list of policy elements.
 *
 * @retval TSS2_RC_SUCCESS on success.
 * @retval TSS2_FAPI_RC_MEMORY: if not enough memory can be allocated.
 * @retval TSS2_FAPI_RC_BAD_VALUE If wrong values are detected during execution.
 * @retval TSS2_FAPI_RC_IO_ERROR If an error occurs during access to the policy
 *         store.
 * @retval TSS2_FAPI_RC_POLICY_UNKNOWN If policy search for a certain policy digest was
 *         not successful.
 * @retval TSS2_FAPI_RC_BAD_TEMPLATE In a invalid policy is loaded during execution.
 * @retval TSS2_FAPI_RC_TRY_AGAIN if an I/O operation is not finished yet and
 *         this function needs to be called again.
 * @retval TSS2_FAPI_RC_GENERAL_FAILURE if an internal error occurred.
 * @retval TSS2_FAPI_RC_BAD_SEQUENCE if the context has an asynchronous
 *         operation already pending.
 * @retval TSS2_FAPI_RC_BAD_REFERENCE a invalid null pointer is passed.
 * @retval TSS2_FAPI_RC_PATH_NOT_FOUND if a FAPI object path was not found
 *         during authorization.
 * @retval TSS2_FAPI_RC_KEY_NOT_FOUND if a key was not found.
 * @retval TSS2_FAPI_RC_AUTHORIZATION_UNKNOWN if a required authorization callback
 *         is not set.
 * @retval TSS2_FAPI_RC_AUTHORIZATION_FAILED if the authorization attempt fails.
 * @retval TSS2_ESYS_RC_* possible error codes of ESAPI.
 * @retval TSS2_FAPI_RC_BAD_PATH if the path is used in inappropriate context
 *         or contains illegal characters.
 * @retval TSS2_FAPI_RC_NOT_PROVISIONED FAPI was not provisioned.
 */
TSS2_RC
ifapi_policyeval_execute(
    ESYS_CONTEXT *esys_ctx,
    IFAPI_POLICY_EXEC_CTX *current_policy)

{
    TSS2_RC r = TSS2_RC_SUCCESS;
    NODE_OBJECT_T *current_policy_element;

    LOG_DEBUG("call");

    while (current_policy->policy_elements) {
        r = execute_policy_element(esys_ctx,
                                   (TPMT_POLICYELEMENT *)
                                   current_policy->policy_elements->object,
                                   current_policy->hash_alg,
                                   current_policy);
        return_try_again(r);

        if (r != TSS2_RC_SUCCESS) {
            Esys_FlushContext(esys_ctx, current_policy->session);
            current_policy->session = ESYS_TR_NONE;
            ifapi_free_node_list(current_policy->policy_elements);

        }
        return_if_error(r, "Execute policy.");

        current_policy_element = current_policy->policy_elements;
        current_policy->policy_elements = current_policy->policy_elements->next;
        SAFE_FREE(current_policy_element);
    }
    return r;

}
