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

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

#include "files.h"
#include "log.h"
#include "tool_rc.h"
#include "tpm2.h"
#include "tpm2_alg_util.h"
#include "tpm2_openssl.h"
#include "tpm2_policy.h"
#include "tpm2_tool.h"
#include "tpm2_util.h"

static bool evaluate_populate_pcr_digests(TPML_PCR_SELECTION *pcr_selections,
        const char *raw_pcrs_file, tpm2_pcrs *pcrs) {

    unsigned expected_pcr_input_file_size = 0;
    TPML_DIGEST *pcr_values = &pcrs->pcr_values[pcrs->count];
    // If pcr_selections is empty, this need to be reset to 0.
    pcrs->count++;

    //Iterating the number of pcr banks selected
    UINT32 i;
    for (i = 0; i < pcr_selections->count; i++) {
        //digest size returned per the hashAlg type
        unsigned dgst_size = tpm2_alg_util_get_hash_size(
                pcr_selections->pcrSelections[i].hash);
        if (!dgst_size) {
            return false;
        }

        UINT8 total_indices_for_this_alg = 0;

        //Looping to check total pcr select bits in the pcr-select-octets for a bank
        UINT32 pcr;
        for (pcr = 0;
             pcr < pcr_selections->pcrSelections[i].sizeofSelect * 8;
             pcr++) {
            if (!tpm2_util_is_pcr_select_bit_set(
                    &pcr_selections->pcrSelections[i], pcr))
                continue;

            pcr_values->digests[pcr_values->count].size = dgst_size;
            pcr_values->count++;
            total_indices_for_this_alg++;

            if (pcr_values->count == ARRAY_LEN(pcr_values->digests)) {
                pcrs->count++;
                if (pcrs->count == ARRAY_LEN(pcrs->pcr_values)) {
                    return false;
                }

                pcr_values = &pcrs->pcr_values[pcrs->count];
            }
        }

        expected_pcr_input_file_size +=
                (total_indices_for_this_alg * dgst_size);
    }

    // If the selection was totally empty, we reset to zero.
    if (expected_pcr_input_file_size == 0)
        pcrs->count = 0;

    //Check if the input pcrs file size is the same size as the pcr selection setlist
    if (raw_pcrs_file) {
        unsigned long filesize = 0;
        bool result = files_get_file_size_path(raw_pcrs_file, &filesize);
        if (!result) {
            LOG_ERR("Could not retrieve raw_pcrs_file size");
            return false;
        }
        if (filesize != expected_pcr_input_file_size) {
            LOG_ERR("pcr-input-file filesize does not match pcr set-list");
            return false;
        }
    }

    return true;
}

static bool tpm2_apply_forward_seals(
        TPML_PCR_SELECTION *pcr_selection,
        tpm2_pcrs *pcrs,
        tpm2_forwards *forwards) {
    TPML_DIGEST *pcr_values;
    unsigned int i;
    unsigned int idx = 0;

    if (pcr_selection->count != forwards->count) {
        LOG_ERR("mismatch between pcr count (%d) and forward count (%zu)",
                pcr_selection->count, forwards->count);

        return false;
    }

    for (i = 0 ; i < pcr_selection->count; i++) {
        TPMS_PCR_SELECTION *pcr_select =
            &pcr_selection->pcrSelections[i];
        tpm2_forward *forward = &forwards->bank[i];

        if (pcr_select->hash != forward->pcr_selection.hash) {
            LOG_ERR("mismatch between pcr hash (%x) and forward hash (%x)",
                    pcr_select->hash, forwards->bank[i].pcr_selection.hash);
            return false;
        }

        UINT16 dgst_size = tpm2_alg_util_get_hash_size(pcr_select->hash);

        for (int pcr = 0; pcr < pcr_select->sizeofSelect * 8; pcr++) {
            if (!tpm2_util_is_pcr_select_bit_set(pcr_select, pcr))
                continue;

            if (tpm2_util_is_pcr_select_bit_set(&forward->pcr_selection, pcr)) {
                const unsigned int lim = ARRAY_LEN(pcrs->pcr_values[0].digests);
                pcr_values = &pcrs->pcr_values[idx / lim];
                memcpy(pcr_values->digests[idx % lim].buffer,
                       forward->pcrs[pcr].sha512,
                       dgst_size);
            }
            idx++;
            if (idx == ARRAY_LEN(pcrs->pcr_values) *
                           ARRAY_LEN(pcrs->pcr_values[0].digests)) {
                LOG_ERR("Too many PCRs specified (%u > %zu max)",
                        idx, ARRAY_LEN(pcrs->pcr_values) *
                                ARRAY_LEN(pcrs->pcr_values[0].digests));
            }
        }
    }

    return true;
}

tool_rc tpm2_policy_build_pcr(ESYS_CONTEXT *ectx, tpm2_session *policy_session,
        const char *raw_pcrs_file, TPML_PCR_SELECTION *pcr_selections,
        TPM2B_DIGEST *raw_pcr_digest, tpm2_forwards *forwards) {

    tpm2_pcrs pcrs = { .count = 0 };

    if (!pcr_selections->count) {
        LOG_ERR("No pcr selection data specified!");
        return tool_rc_general_error;
    }


    TPM2B_DIGEST pcr_digest = TPM2B_TYPE_INIT(TPM2B_DIGEST, buffer);
    TPMI_ALG_HASH auth_hash = tpm2_session_get_authhash(policy_session);
    ESYS_TR handle = tpm2_session_get_handle(policy_session);

    /*
     * If digest of all PCRs is directly given, handle it here.
     */
    if (raw_pcr_digest &&
    raw_pcr_digest->size != tpm2_alg_util_get_hash_size(auth_hash)) {
        LOG_ERR("Specified PCR digest length not suitable with the policy session digest");
        return tool_rc_general_error;
    }
    // Call the PolicyPCR command
    if (raw_pcr_digest) {
        return tpm2_policy_pcr(ectx, handle, ESYS_TR_NONE, ESYS_TR_NONE,
            ESYS_TR_NONE, raw_pcr_digest, pcr_selections);
    }


    bool result = evaluate_populate_pcr_digests(pcr_selections, raw_pcrs_file,
            &pcrs);
    if (!result) {
        return tool_rc_general_error;
    }

    //If PCR input for policy is from raw pcrs file
    if (raw_pcrs_file) {
        FILE *fp = fopen(raw_pcrs_file, "rb");
        if (fp == NULL) {
            LOG_ERR("Cannot open pcr-input-file %s", raw_pcrs_file);
            return tool_rc_general_error;
        }
        // Bank hashAlg values dictates the order of the list of digests
        unsigned j;

        for (j = 0; j < pcrs.count; j++) {
            TPML_DIGEST *pcr_values = &pcrs.pcr_values[j];
            unsigned int i;

            for (i = 0; i < pcr_values->count; i++) {
                size_t sz = fread(&pcr_values->digests[i].buffer, 1,
                        pcr_values->digests[i].size, fp);
                if (ferror(fp)) {
                    LOG_ERR("fread error: %s\n", strerror(errno));
                    fclose(fp);
                    return tool_rc_general_error;
                }
                if (feof(fp)) {
                    LOG_ERR("Unexpected eof in pcr file.");
                    fclose(fp);
                    return tool_rc_general_error;
                }
                if (sz != pcr_values->digests[i].size) {
                    const char *msg =
                            ferror(fp) ? strerror(errno) : "end of file reached";
                    LOG_ERR("Reading from file \"%s\" failed: %s", raw_pcrs_file,
                            msg);
                    fclose(fp);
                    return tool_rc_general_error;
                }
            }
        }
        fclose(fp);
    } else {
        // Read PCRs
        tool_rc rc = pcr_read_pcr_values(ectx, pcr_selections, &pcrs,
                                         NULL, TPM2_ALG_ERROR, ESYS_TR_NONE,
                                         ESYS_TR_NONE, ESYS_TR_NONE);
        if (rc != tool_rc_success) {
            return rc;
        }
    }

    if (forwards) {
        if (!tpm2_apply_forward_seals(pcr_selections, &pcrs, forwards)) {
            LOG_ERR("Could not apply forward seal values");
            return tool_rc_general_error;
        }
    }

    // Calculate hashes
    result = tpm2_openssl_hash_pcr_banks(auth_hash, pcr_selections, &pcrs, &pcr_digest);
    if (!result) {
        LOG_ERR("Could not hash pcr values");
        return tool_rc_general_error;
    }

    // Call the PolicyPCR command
    return tpm2_policy_pcr(ectx, handle, ESYS_TR_NONE, ESYS_TR_NONE,
            ESYS_TR_NONE, &pcr_digest, pcr_selections);
}

tool_rc tpm2_policy_build_policyauthorize(ESYS_CONTEXT *ectx,
        tpm2_session *policy_session, const char *policy_digest_path,
        const char *qualifying_data,
        const char *verifying_pubkey_name_path, const char *ticket_path,
        TPM2B_DIGEST *cp_hash, TPMI_ALG_HASH parameter_hash_algorithm) {

    bool result = true;
    TPM2B_DIGEST approved_policy = { .size = 0 };
    if (policy_digest_path) {
        approved_policy.size = sizeof(TPMU_HA);
        result = files_load_bytes_from_path(policy_digest_path,
            approved_policy.buffer, &approved_policy.size);
    }
    if (!result) {
        return tool_rc_general_error;
    }

    /*
     * Qualifier data is optional. If not specified default to 0
     */

    TPM2B_NONCE policy_qualifier = { .size = 0 };

    if (qualifying_data) {
        policy_qualifier.size = sizeof(policy_qualifier.buffer);
        result = tpm2_util_bin_from_hex_or_file(qualifying_data,
                &policy_qualifier.size, policy_qualifier.buffer);
        if (!result) {
            return tool_rc_general_error;
        }
    }

    unsigned long file_size = 0;
    result = files_get_file_size_path(verifying_pubkey_name_path, &file_size);
    if (!result) {
        return tool_rc_general_error;
    }

    if (!file_size) {
        LOG_ERR("Verifying public key name file \"%s\", cannot be empty",
                verifying_pubkey_name_path);
        return tool_rc_general_error;
    }

    TPM2B_NAME key_sign = { .size = (uint16_t) file_size };

    result = files_load_bytes_from_path(verifying_pubkey_name_path,
            key_sign.name, &key_sign.size);
    if (!result) {
        return tool_rc_general_error;
    }

    TPMT_TK_VERIFIED check_ticket = { .tag = TPM2_ST_VERIFIED, .hierarchy =
            TPM2_RH_OWNER, .digest = { 0 } };
    result = tpm2_session_is_trial(policy_session);
    if (!result) {
        result = files_load_ticket(ticket_path, &check_ticket);
        if (!result) {
            LOG_ERR("Could not load verification ticket file");
            return tool_rc_general_error;
        }
    }

    ESYS_TR sess_handle = tpm2_session_get_handle(policy_session);
    return tpm2_policy_authorize(ectx, sess_handle, ESYS_TR_NONE, ESYS_TR_NONE,
            ESYS_TR_NONE, &approved_policy, &policy_qualifier, &key_sign,
            &check_ticket, cp_hash, parameter_hash_algorithm);
}

tool_rc tpm2_policy_build_policyor(ESYS_CONTEXT *ectx,
        tpm2_session *policy_session, TPML_DIGEST *policy_list) {

    ESYS_TR sess_handle = tpm2_session_get_handle(policy_session);
    return tpm2_policy_or(ectx, sess_handle, ESYS_TR_NONE, ESYS_TR_NONE,
            ESYS_TR_NONE, policy_list);
}

tool_rc tpm2_policy_build_policypassword(ESYS_CONTEXT *ectx,
        tpm2_session *session, TPM2B_DIGEST *cp_hash,
        TPMI_ALG_HASH parameter_hash_algorithm) {

    ESYS_TR policy_session_handle = tpm2_session_get_handle(session);

    return tpm2_policy_password(ectx, policy_session_handle, ESYS_TR_NONE,
            ESYS_TR_NONE, ESYS_TR_NONE, cp_hash, parameter_hash_algorithm);
}

tool_rc tpm2_policy_build_policynamehash(ESYS_CONTEXT *ectx,
    tpm2_session *session, const TPM2B_DIGEST *name_hash) {

    ESYS_TR policy_session_handle = tpm2_session_get_handle(session);

    return tpm2_policy_namehash(ectx, policy_session_handle, name_hash);
}

tool_rc tpm2_policy_build_policytemplate(ESYS_CONTEXT *ectx,
    tpm2_session *session, const TPM2B_DIGEST *template_hash) {

    ESYS_TR policy_session_handle = tpm2_session_get_handle(session);

    return tpm2_policy_template(ectx, policy_session_handle, template_hash);
}

tool_rc tpm2_policy_build_policycphash(ESYS_CONTEXT *ectx,
    tpm2_session *session, const TPM2B_DIGEST *cphash) {

    ESYS_TR policy_session_handle = tpm2_session_get_handle(session);

    return tpm2_policy_cphash(ectx, policy_session_handle, cphash);
}

tool_rc tpm2_policy_build_policyauthvalue(ESYS_CONTEXT *ectx,
        tpm2_session *session, TPM2B_DIGEST *cp_hash,
        TPMI_ALG_HASH parameter_hash_algorithm) {

    ESYS_TR policy_session_handle = tpm2_session_get_handle(session);

    return tpm2_policy_authvalue(ectx, policy_session_handle, ESYS_TR_NONE,
            ESYS_TR_NONE, ESYS_TR_NONE, cp_hash, parameter_hash_algorithm);
}

tool_rc tpm2_policy_build_policysecret(ESYS_CONTEXT *ectx,
        tpm2_session *policy_session, tpm2_loaded_object *auth_entity_obj,
        INT32 expiration, TPMT_TK_AUTH **policy_ticket,
        TPM2B_TIMEOUT **timeout, bool is_nonce_tpm,
        const char *policy_qualifier_data, TPM2B_DIGEST *cp_hash,
        TPMI_ALG_HASH parameter_hash_algorithm) {

    /*
     * Qualifier data is optional. If not specified default to 0
     */
    TPM2B_NONCE policy_qualifier = TPM2B_EMPTY_INIT;
    if (policy_qualifier_data) {
        policy_qualifier.size = sizeof(policy_qualifier.buffer);
        bool result = tpm2_util_bin_from_hex_or_file(policy_qualifier_data,
                &policy_qualifier.size,
                policy_qualifier.buffer);
        if (!result) {
            return tool_rc_general_error;
        }
    }

    ESYS_TR policy_session_handle = tpm2_session_get_handle(policy_session);

    TPM2B_NONCE *nonce_tpm = NULL;
    tool_rc rc = tool_rc_success;
    if (is_nonce_tpm) {
        rc = tpm2_sess_get_noncetpm(ectx, policy_session_handle, &nonce_tpm);
        if (rc != tool_rc_success) {
            goto tpm2_policy_build_policysecret_out;
        }
    }

    rc = tpm2_policy_secret(ectx, auth_entity_obj, policy_session_handle,
        expiration, policy_ticket, timeout, nonce_tpm, &policy_qualifier,
        cp_hash, parameter_hash_algorithm);

tpm2_policy_build_policysecret_out:
    Esys_Free(nonce_tpm);
    return rc;
}

tool_rc tpm2_policy_build_policyticket(ESYS_CONTEXT *ectx,
    tpm2_session *policy_session, char *policy_timeout_path,
    const char *qualifier_data, char *policy_ticket_path,
    const char *auth_name_path) {

    unsigned long file_size = 0;

    bool result = files_get_file_size_path(policy_timeout_path, &file_size);
    if (!result) {
        return tool_rc_general_error;
    }
    TPM2B_TIMEOUT policy_timeout = { .size = (uint16_t) file_size };
    if (policy_timeout.size) {
        result = files_load_bytes_from_path(policy_timeout_path,
                policy_timeout.buffer, &policy_timeout.size);
        if (!result) {
            return tool_rc_general_error;
        }
    }

    result = files_get_file_size_path(auth_name_path, &file_size);
    if (!result) {
        return tool_rc_general_error;
    }
    TPM2B_NAME auth_name = { .size = (uint16_t) file_size };
    if (auth_name.size) {
        result = files_load_bytes_from_path(auth_name_path,
                auth_name.name, &auth_name.size);
        if (!result) {
            return tool_rc_general_error;
        }
    }

    TPM2B_NONCE policyref = TPM2B_EMPTY_INIT;
    if (qualifier_data) {
        policyref.size = sizeof(policyref.buffer);
        result = tpm2_util_bin_from_hex_or_file(qualifier_data, &policyref.size,
                policyref.buffer);
        if (!result) {
            return tool_rc_general_error;
        }
    }

    TPMT_TK_AUTH ticket = { 0 };
    result = files_load_authorization_ticket(policy_ticket_path, &ticket);
    if (!result) {
        LOG_ERR("Failed loading authorization ticket.");
        return tool_rc_general_error;
    }

    ESYS_TR policy_session_handle = tpm2_session_get_handle(policy_session);

    return tpm2_policy_ticket(ectx, policy_session_handle, &policy_timeout,
        &policyref, &auth_name, &ticket);

    return tool_rc_success;
}

tool_rc tpm2_policy_build_policysigned(ESYS_CONTEXT *ectx,
        tpm2_session *policy_session, tpm2_loaded_object *auth_entity_obj,
        TPMT_SIGNATURE *signature, INT32 expiration, TPM2B_TIMEOUT **timeout,
        TPMT_TK_AUTH **policy_ticket, const char *policy_qualifier_data,
        bool is_nonce_tpm, const char *raw_data_path,
        const char *cphash_path) {

    bool result = true;

    /*
     * Qualifier data is optional. If not specified default to 0
     */
    TPM2B_NONCE policy_qualifier = TPM2B_EMPTY_INIT;

    if (policy_qualifier_data) {
        policy_qualifier.size = sizeof(policy_qualifier.buffer);
        result = tpm2_util_bin_from_hex_or_file(policy_qualifier_data,
                &policy_qualifier.size,
                policy_qualifier.buffer);
        if (!result) {
            return tool_rc_general_error;
        }
    }

    /*
     * CpHashA (digest of command parameters for approved command) optional.
     * If not specified default to NULL
     */
    TPM2B_DIGEST cphash = TPM2B_EMPTY_INIT;

    if (cphash_path) {
        bool result = files_load_digest(cphash_path, &cphash);
        if (!result) {
            return tool_rc_general_error;
        }
    }

    ESYS_TR policy_session_handle = tpm2_session_get_handle(policy_session);

    TPM2B_NONCE *nonce_tpm = NULL;
    tool_rc rc = tool_rc_success;
    if (is_nonce_tpm) {
        rc = tpm2_sess_get_noncetpm(ectx, policy_session_handle, &nonce_tpm);
        if (rc != tool_rc_success) {
            goto tpm2_policy_build_policysigned_out;
        }
    }

    /*
     * TPM-Rev-2.0-Part-3-Commands-01.38.pdf
     * aHash ≔ HauthAlg(nonceTPM || expiration || cpHashA || policyRef)
     */
    if (raw_data_path) {
        uint16_t raw_data_len = (nonce_tpm ? nonce_tpm->size : 0) +
            sizeof(INT32) + cphash.size + policy_qualifier.size;

        uint8_t *raw_data = malloc(raw_data_len);
        if (!raw_data) {
            LOG_ERR("oom");
            rc = tool_rc_general_error;
            goto tpm2_policy_build_policysigned_out;
        }
        /* nonceTPM */
        uint16_t offset = 0;
        if (nonce_tpm) {
            memcpy(raw_data, nonce_tpm->buffer, nonce_tpm->size);
            offset += nonce_tpm->size;
        }
        /* expiration */
        UINT32 endswap_data = tpm2_util_endian_swap_32(expiration);
        memcpy(raw_data + offset, (UINT8 *)&endswap_data, sizeof(INT32));
        offset += sizeof(INT32);
        /* cpHash */
        if (cphash_path) {
            memcpy(raw_data + offset, cphash.buffer, cphash.size);
            offset += cphash.size;
        }
        /* policyRef */
        memcpy(raw_data + offset, policy_qualifier.buffer,
            policy_qualifier.size);

        bool result = files_save_bytes_to_file(raw_data_path, raw_data,
            raw_data_len);
        free(raw_data);
        if (!result) {
            rc = tool_rc_general_error;
        }
        /*
         * We return since we only need to generate the raw signing data
         */
        goto tpm2_policy_build_policysigned_out;
    }

    rc = tpm2_policy_signed(ectx, auth_entity_obj, policy_session_handle,
        signature, expiration, timeout, policy_ticket, &policy_qualifier,
        nonce_tpm, &cphash);

tpm2_policy_build_policysigned_out:
    Esys_Free(nonce_tpm);
    return rc;
}

tool_rc tpm2_policy_get_digest(ESYS_CONTEXT *ectx, tpm2_session *session,
    TPM2B_DIGEST **policy_digest, TPM2B_DIGEST *cphash,
    TPMI_ALG_HASH parameter_hash_algorithm) {

    ESYS_TR handle = tpm2_session_get_handle(session);

    return tpm2_policy_getdigest(ectx, handle, ESYS_TR_NONE, ESYS_TR_NONE,
            ESYS_TR_NONE, policy_digest, cphash, parameter_hash_algorithm);
}

tool_rc tpm2_policy_build_policycommandcode(ESYS_CONTEXT *ectx,
        tpm2_session *session, uint32_t command_code, TPM2B_DIGEST *cp_hash,
        TPMI_ALG_HASH parameter_hash_algorithm) {

    ESYS_TR handle = tpm2_session_get_handle(session);

    return tpm2_policy_command_code(ectx, handle, ESYS_TR_NONE, ESYS_TR_NONE,
            ESYS_TR_NONE, command_code, cp_hash, parameter_hash_algorithm);
}

tool_rc  tpm2_policy_build_policynvwritten(ESYS_CONTEXT *ectx,
        tpm2_session *session, TPMI_YES_NO written_set, TPM2B_DIGEST *cp_hash,
        TPMI_ALG_HASH parameter_hash_algorithm) {

    ESYS_TR handle = tpm2_session_get_handle(session);

    return tpm2_policy_nv_written(ectx, handle, ESYS_TR_NONE, ESYS_TR_NONE,
            ESYS_TR_NONE, written_set, cp_hash, parameter_hash_algorithm);
}

tool_rc tpm2_policy_build_policylocality(ESYS_CONTEXT *ectx,
        tpm2_session *session, TPMA_LOCALITY locality, TPM2B_DIGEST *cp_hash,
        TPMI_ALG_HASH parameter_hash_algorithm) {

    ESYS_TR handle = tpm2_session_get_handle(session);

    return tpm2_policy_locality(ectx, handle, ESYS_TR_NONE, ESYS_TR_NONE,
            ESYS_TR_NONE, locality, cp_hash, parameter_hash_algorithm);
}

tool_rc tpm2_policy_build_policyduplicationselect(ESYS_CONTEXT *ectx,
        tpm2_session *session, const char *obj_name_path,
        const char *new_parent_name_path, TPMI_YES_NO is_include_obj) {

    TPM2B_NAME obj_name;
    bool result = true;

    if (obj_name_path) {
        obj_name.size = sizeof(obj_name.name);
        result = files_load_bytes_from_path(obj_name_path, obj_name.name,
                &obj_name.size);
    } else {
        obj_name.size = 0;
    }

    if (!result) {
        LOG_ERR("Failed to load duplicable object name.");
        return tool_rc_general_error;
    }

    TPM2B_NAME new_parent_name = { .size = sizeof(new_parent_name.name) };

    result = files_load_bytes_from_path(new_parent_name_path,
            new_parent_name.name, &new_parent_name.size);
    if (!result) {
        return tool_rc_general_error;
    }

    ESYS_TR handle = tpm2_session_get_handle(session);

    return tpm2_policy_duplication_select(ectx, handle, ESYS_TR_NONE,
            ESYS_TR_NONE, ESYS_TR_NONE, &obj_name, &new_parent_name,
            is_include_obj);
}

static bool tpm2_policy_populate_digest_list(char *buf,
        TPML_DIGEST *policy_list, TPMI_ALG_HASH hash) {

    uint8_t hash_len = tpm2_alg_util_get_hash_size(hash);
    if (!hash_len) {
        return false;
    }

    if (policy_list->count == ARRAY_LEN(policy_list->digests)) {
        return false;
    }

    unsigned long file_size;
    bool retval = files_get_file_size_path(buf, &file_size);
    if (!retval) {
        return false;
    }
    if (file_size != hash_len) {
        return false;
    }

    policy_list->digests[policy_list->count].size = hash_len;
    /* All policy digests are expected to be of same hash len */
    if (policy_list->count > 0
            && policy_list->digests[policy_list->count].size
                    != policy_list->digests[policy_list->count - 1].size) {
        return false;
    }

    uint16_t policy_digest_size = hash_len;
    retval = files_load_bytes_from_path(buf,
            policy_list->digests[policy_list->count].buffer,
            &policy_digest_size);
    if (!retval) {
        return false;
    }

    policy_list->count++;

    return true;
}

bool tpm2_policy_parse_policy_list(char *str, TPML_DIGEST *policy_list) {

    char *str1;
    char *str2;
    char *token;
    char *subtoken;
    char *saveptr1;
    char *saveptr2;
    const char *delimiter1 = ":";
    const char *delimiter2 = ",";

    unsigned int j;
    bool retval;
    TPMI_ALG_HASH hash = TPM2_ALG_ERROR;

    for (j = 1, str1 = str;; j++, str1 = NULL) {

        token = strtok_r(str1, delimiter1, &saveptr1);
        if (token == NULL) {
            break;
        }

        for (str2 = token;; str2 = NULL) {

            subtoken = strtok_r(str2, delimiter2, &saveptr2);
            if (subtoken == NULL) {
                break;
            }

            //Expecting one policy digest of same hash alg type for all policies
            if (j == 1) {
                hash = tpm2_alg_util_from_optarg(subtoken,
                        tpm2_alg_util_flags_hash);
                if (hash == TPM2_ALG_ERROR) {
                    LOG_ERR("Invalid/ Unspecified policy digest algorithm.");
                    return false;
                }
            }

            //Multiple valid policy files
            if (j > 1) {
                retval = tpm2_policy_populate_digest_list(subtoken, policy_list,
                        hash);
                if (!retval) {
                    return false;
                }
            }

        }
    }

    return true;
}

tool_rc tpm2_policy_tool_finish(ESYS_CONTEXT *ectx, tpm2_session *session,
        const char *save_path) {

    TPM2B_DIGEST *policy_digest = 0;
    tool_rc rc = tpm2_policy_get_digest(ectx, session, &policy_digest, 0,
        TPM2_ALG_ERROR);
    if (rc != tool_rc_success) {
        LOG_ERR("Could not build tpm policy");
        return rc;
    }

    tpm2_util_hexdump(policy_digest->buffer, policy_digest->size);
    tpm2_tool_output("\n");

    rc = tool_rc_general_error;

    if (save_path) {
        bool result = files_save_bytes_to_file(save_path, policy_digest->buffer,
                policy_digest->size);
        if (!result) {
            LOG_ERR("Failed to save policy digest into file \"%s\"", save_path);
            goto error;
        }
    }

    rc = tool_rc_success;

error:
    free(policy_digest);
    return rc;
}

tool_rc tpm2_policy_set_digest(const char *auth_policy, TPM2B_DIGEST *out_policy) {

    if (!auth_policy) {
        memset(out_policy, 0, sizeof(*out_policy));
        return tool_rc_success;
    }

    out_policy->size = sizeof(out_policy->buffer);
    bool res = tpm2_util_bin_from_hex_or_file(auth_policy, &out_policy->size, out_policy->buffer);
    return res ? tool_rc_success : tool_rc_general_error;
}
