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

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

#include <inttypes.h>    // for PRIu32
#include <openssl/evp.h> // for EVP_get_digestbyname, EVP_MD, EVP_...
#include <stdbool.h>     // for bool, false, true
#include <stdio.h>       // for fread, fclose, FILE, fopen, sprintf
#include <stdlib.h>      // for calloc, malloc
#include <string.h>      // for memcpy, strlen, strcmp, memcmp
#include <strings.h>     // for strncasecmp

#include "ifapi_eventlog.h" // for CONTENT, CONTENT_TYPE
#include "ifapi_helpers.h"  // for ifapi_check_json_object_fields
#include "ifapi_ima_eventlog.h"
#include "ifapi_macros.h"         // for goto_if_null2, return_error2, goto...
#include "tpm_json_deserialize.h" // for ifapi_get_sub_object, ifapi_json_U...
#include "tss2_common.h"          // for UINT8, TSS2_RC, TSS2_FAPI_RC_BAD_V...
#include "tss2_tpm2_types.h"      // for TPM2_SHA1_DIGEST_SIZE, TPM2_SHA512...

#define LOGMODULE fapijson
#include "util/log.h" // for return_error, return_if_null, retu...

/* Defines from kernel ima.h */
#define IMA_TEMPLATE_FIELD_ID_MAX_LEN 16
#define IMA_TEMPLATE_NUM_FIELDS_MAX   15
/* Define from kernel crypt.h */
#define CRYPTO_MAX_ALG_NAME 128

static uint32_t
endian_swap_32(uint32_t data) {
    uint32_t converted;
    uint8_t *bytes = (uint8_t *)&data;
    uint8_t *tmp = (uint8_t *)&converted;
    size_t   i;
    for (i = 0; i < sizeof(uint32_t); i++) {
        tmp[i] = bytes[sizeof(uint32_t) - i - 1];
    }
    return converted;
}

static bool
big_endian_arch(void) {

    uint32_t test_word;
    uint8_t *test_byte;

    test_word = 0xFF000000;
    test_byte = (uint8_t *)(&test_word);
    return test_byte[0] == 0xFF;
}

static bool
little_endian_pcr(uint32_t pcr) {

    uint8_t *test_pcr;

    test_pcr = (uint8_t *)(&pcr);
    return test_pcr[0];
}

static bool
need_to_convert_to_big_endian(IFAPI_IMA_TEMPLATE *template) {
    return big_endian_arch() && little_endian_pcr(template->header.pcr);
}

static TSS2_RC
get_json_content(json_object *jso, json_object **jso_sub) {
    if (!ifapi_get_sub_object(jso, CONTENT, jso_sub)) {
        *jso_sub = json_object_new_object();
        return_if_null(*jso_sub, "Out of memory.", TSS2_FAPI_RC_MEMORY);
        if (json_object_object_add(jso, CONTENT, *jso_sub)) {
            return_error(TSS2_FAPI_RC_GENERAL_FAILURE, "Could not add json object.");
        }
    }
    return TSS2_RC_SUCCESS;
}

static TSS2_RC
add_uint8_ary_to_json(UINT8 *buffer, UINT32 size, json_object *jso, const char *jso_tag) {
    json_object *jso_byte_string = NULL;

    return_if_null(buffer, "Bad reference.", TSS2_FAPI_RC_BAD_VALUE);
    return_if_null(jso, "Bad reference.", TSS2_FAPI_RC_BAD_VALUE);

    char *hex_string = malloc((size) * 2 + 1);
    return_if_null(hex_string, "Out of memory.", TSS2_FAPI_RC_MEMORY);

    if (size > 0) {
        for (size_t i = 0, off = 0; i < size; i++, off += 2)
            sprintf(&hex_string[off], "%02x", buffer[i]);
    }
    hex_string[(size) * 2] = '\0';
    jso_byte_string = json_object_new_string(hex_string);
    SAFE_FREE(hex_string)
    return_if_null(jso_byte_string, "Out of memory", TSS2_FAPI_RC_MEMORY);

    if (json_object_object_add(jso, jso_tag, jso_byte_string)) {
        return_error(TSS2_FAPI_RC_GENERAL_FAILURE, "Could not add json object.");
    }
    return TSS2_RC_SUCCESS;
}

static TSS2_RC
add_string_to_json(const char *string, json_object *jso, const char *jso_tag) {
    json_object *jso_string = NULL;

    return_if_null(string, "Bad reference.", TSS2_FAPI_RC_BAD_VALUE);
    return_if_null(jso, "Bad reference.", TSS2_FAPI_RC_BAD_VALUE);

    jso_string = json_object_new_string(string);
    return_if_null(jso_string, "Out of memory", TSS2_FAPI_RC_MEMORY);

    if (json_object_object_add(jso, jso_tag, jso_string)) {
        return_error(TSS2_FAPI_RC_GENERAL_FAILURE, "Could not add json object.");
    }
    return TSS2_RC_SUCCESS;
}

static TSS2_RC
add_number_to_json(UINT32 number, json_object *jso, const char *jso_tag) {
    json_object *jso_number = NULL;

    return_if_null(jso, "Bad reference.", TSS2_FAPI_RC_BAD_VALUE);

    jso_number = json_object_new_int64(number);
    return_if_null(jso_number, "Out of memory", TSS2_FAPI_RC_MEMORY);

    if (json_object_object_add(jso, jso_tag, jso_number)) {
        return_error(TSS2_FAPI_RC_GENERAL_FAILURE, "Could not add json object.");
    }
    return TSS2_RC_SUCCESS;
}

static bool
zero_digest(UINT8 *buffer, size_t size) {
    if (buffer[0] == 0 && memcmp(&buffer[0], &buffer[1], size - 1) == 0) {
        return true;
    } else {
        return false;
    }
}

static UINT8 digest_ff[TPM2_SHA512_DIGEST_SIZE] = {
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
};

/* Replace current sha1 digest with digest of 0xff values. */
static TSS2_RC
set_ff_digest(json_object *jso) {
    TSS2_RC      r;
    json_object *jso_digest = NULL, *jso_ary = NULL, *jso_digest_type = NULL;

    jso_digest = json_object_new_object();
    return_if_null(jso_digest, "Out of memory.", TSS2_FAPI_RC_MEMORY);

    r = add_uint8_ary_to_json(digest_ff, TPM2_SHA1_DIGEST_SIZE, jso_digest, "digest");
    goto_if_error2(r, "Add digest to json", error);

    jso_digest_type = NULL;
    jso_digest_type = json_object_new_string("sha1");
    goto_if_null(jso_digest_type, "Out of memory.", TSS2_FAPI_RC_MEMORY, error);

    if (json_object_object_add(jso_digest, "hashAlg", jso_digest_type)) {
        goto_error(r, TSS2_FAPI_RC_GENERAL_FAILURE, "Could not add json object.", error);
    }

    jso_ary = json_object_new_array();
    goto_if_null(jso_ary, "Out of memory.", TSS2_FAPI_RC_MEMORY, error);

    if (json_object_array_add(jso_ary, jso_digest)) {
        goto_error(r, TSS2_FAPI_RC_GENERAL_FAILURE, "Could not add json object.", error);
    }
    json_object_object_del(jso, "digests");
    if (json_object_object_add(jso, "digests", jso_ary)) {
        goto_error(r, TSS2_FAPI_RC_GENERAL_FAILURE, "Could not add json object.", error);
    }
    return TSS2_RC_SUCCESS;

error:
    if (jso_digest)
        json_object_put(jso_digest);
    if (jso_digest_type)
        json_object_put(jso_digest_type);
    if (jso_ary)
        json_object_put(jso_ary);
    return r;
}

/** Callback for digest of old IMA format.
 */
static TSS2_RC
sha_digest_json_cb(UINT8       *digest,
                   UINT8       *buffer,
                   size_t      *offset,
                   json_object *jso,
                   IFAPI_IMA_TEMPLATE *template) {
    TSS2_RC r;
    UNUSED(template);

    LOGBLOB_TRACE(&buffer[*offset], TPM2_SHA1_DIGEST_SIZE, "IMA buffer");
    LOGBLOB_TRACE(digest, TPM2_SHA1_DIGEST_SIZE, "IMA digest");

    if (jso && zero_digest(digest, TPM2_SHA1_DIGEST_SIZE)
        && zero_digest(&buffer[*offset], TPM2_SHA1_DIGEST_SIZE)) {
        r = set_ff_digest(jso);
        return_if_error(r, "Set 0xff in digest.");
    }

    *offset += TPM2_SHA1_DIGEST_SIZE;
    return TSS2_RC_SUCCESS;
}

/** Get UINT32 size value from buffer and increase offset.
 */
UINT32
get_size_from_buffer(UINT8 *buffer, size_t *offset, IFAPI_IMA_TEMPLATE *template) {
    UINT32 size;
    memcpy(&size, &buffer[*offset], sizeof(UINT32));
    *offset += sizeof(UINT32);
    if (template->convert_to_big_endian) {
        size = endian_swap_32(size);
    }
    return size;
}

/** Callback for digest with name of used hash algorithm,
 */
static TSS2_RC
digest_with_hash_name_cb(UINT8       *digest,
                         UINT8       *buffer,
                         size_t      *offset,
                         json_object *jso,
                         IFAPI_IMA_TEMPLATE *template) {
    TSS2_RC       r;
    char          hash_alg[CRYPTO_MAX_ALG_NAME + 1] = { 0 };
    size_t        alg_name_size;
    const EVP_MD *md;
    int           digest_size;
    UINT32        digest_buffer_size;

    digest_buffer_size = get_size_from_buffer(buffer, offset, template);
    alg_name_size = strlen((char *)&buffer[*offset]) - 1; /**< strip : */
    if (alg_name_size > CRYPTO_MAX_ALG_NAME) {
        return_error(TSS2_FAPI_RC_BAD_VALUE, "Invalid hash name.");
    }
    memcpy(hash_alg, &buffer[*offset], alg_name_size);
    md = EVP_get_digestbyname(hash_alg);
    if (md == NULL) {
        return_error(TSS2_FAPI_RC_BAD_VALUE, "Invalid hash name.");
    }
    *offset += alg_name_size + 2; /**< skip : and '\0' */
    digest_size = EVP_MD_size(md);
    if (alg_name_size + 2 + digest_size != digest_buffer_size) {
        digest_buffer_size = endian_swap_32(digest_buffer_size);
        /* Try with endian swap */
        if (alg_name_size + 2 + digest_size != digest_buffer_size) {
            return_error(TSS2_FAPI_RC_BAD_VALUE, "Invalid IMA binary format.");
        }
    }
    LOGBLOB_TRACE(&buffer[*offset], digest_size, "IMA data_hash");

    if (jso && zero_digest(digest, template->hash_size)
        && zero_digest(&buffer[*offset], digest_size)) {
        r = set_ff_digest(jso);
        return_if_error(r, "Set 0xff in digest.");
    }
    *offset += digest_size;
    return TSS2_RC_SUCCESS;
}

/** Callback to get digest with size field (UINT32).
 */
static TSS2_RC
signature_cb(UINT8       *digest,
             UINT8       *buffer,
             size_t      *offset,
             json_object *jso,
             IFAPI_IMA_TEMPLATE *template) {
    UNUSED(digest);
    UNUSED(jso);
    UINT32 digest_size;
    UNUSED(template);

    digest_size = get_size_from_buffer(buffer, offset, template);
    LOGBLOB_TRACE(&buffer[*offset], digest_size, "IMA Signature:");
    *offset += digest_size;
    return TSS2_RC_SUCCESS;
}

/** Callback to get null terminated name with size field (n-ng).
 */
static TSS2_RC
eventname_ng_json_cb(UINT8       *digest,
                     UINT8       *buffer,
                     size_t      *offset,
                     json_object *jso,
                     IFAPI_IMA_TEMPLATE *template) {
    size_t size;
    UINT32 size_from_buffer;
    UNUSED(digest);
    UNUSED(jso);

    /* Get size from buffer with 0 Terminator. */
    size_from_buffer = get_size_from_buffer(buffer, offset, template);
    size = strlen((const char *)&buffer[*offset]);
    if (size != size_from_buffer - 1) {
        size_from_buffer = endian_swap_32(size_from_buffer);
        /* Try with endian swap */
        if (size != size_from_buffer - 1) {
            return_error2(TSS2_FAPI_RC_BAD_VALUE,
                          "Invalid digest size, string length: %zu size from buffer: %" PRIu32,
                          size, size_from_buffer);
        }
    }
    LOG_TRACE("IMA name: %s", (const char *)&buffer[*offset]);
    template->name = (char *)&buffer[*offset];
    *offset += size + 1; /**< with 0 terminator */
    return TSS2_RC_SUCCESS;
}

/** Callback to get null terminated name (n).
 */
static TSS2_RC
eventname_cb(UINT8       *digest,
             UINT8       *buffer,
             size_t      *offset,
             json_object *jso,
             IFAPI_IMA_TEMPLATE *template) {
    size_t size;
    UNUSED(digest);
    UNUSED(jso);

    size = strlen((const char *)&buffer[*offset]); // TODO check
    if (size > TCG_EVENT_NAME_LEN_MAX + 1) {
        return_error(TSS2_FAPI_RC_BAD_VALUE, "Too long event name.");
    }
    LOG_TRACE("IMA name: %s", (const char *)&buffer[*offset]);
    template->name = (char *)&buffer[*offset];
    *offset += size + 1; /**< with 0 terminator */
    return TSS2_RC_SUCCESS;
}

/** Callback to initialize the json event list.
 */
TSS2_RC
init_event_list_json_cb(json_object **jso) {
    return_if_null(jso, "Bad reference.", TSS2_FAPI_RC_BAD_VALUE);
    if (!*jso) {
        *jso = json_object_new_array();
        return_if_null(*jso, "Out of memory", TSS2_FAPI_RC_MEMORY);
    }
    return TSS2_RC_SUCCESS;
}

/** Callback to convert header of IMA template to JSON.
 */
TSS2_RC
event_header_json_cb(size_t        recnum,
                     UINT32        pcr,
                     const char   *ima_type,
                     UINT8        *digest,
                     size_t        digest_size,
                     json_object  *jso_list,
                     json_object **jso) {
    TSS2_RC      r;
    json_object *jso_digest, *jso_digest_type, *jso_ary, *jso_content;
    char        *hash_name;

    *jso = json_object_new_object();
    return_if_null(*jso, "Out of memory.", TSS2_FAPI_RC_MEMORY);

    r = add_number_to_json(recnum, *jso, "recnum");
    return_if_error(r, "Add number to json object.");

    r = add_number_to_json(pcr, *jso, "pcr");
    return_if_error(r, "Add number to json object.");

    jso_digest = json_object_new_object();
    return_if_null(*jso, "Out of memory.", TSS2_FAPI_RC_MEMORY);

    jso_digest_type = NULL;
    switch (digest_size) {
    case TPM2_SHA1_DIGEST_SIZE:
        hash_name = "sha1";
        break;
    case TPM2_SHA256_DIGEST_SIZE:
        hash_name = "sha256";
        break;
    case TPM2_SHA384_DIGEST_SIZE:
        hash_name = "sha384";
        break;
    case TPM2_SHA512_DIGEST_SIZE:
        hash_name = "sha512";
        break;
    default:
        return_error2(TSS2_FAPI_RC_BAD_VALUE, "Invalid hash size %zu", digest_size);
    }

    jso_digest_type = json_object_new_string(hash_name);
    return_if_null(jso_digest_type, "Out of memory.", TSS2_FAPI_RC_MEMORY);

    if (json_object_object_add(jso_digest, "hashAlg", jso_digest_type)) {
        return_error(TSS2_FAPI_RC_GENERAL_FAILURE, "Could not add json object.");
    }

    jso_ary = json_object_new_array();
    return_if_null(jso_ary, "Out of memory.", TSS2_FAPI_RC_MEMORY);

    if (json_object_array_add(jso_ary, jso_digest)) {
        return_error(TSS2_FAPI_RC_GENERAL_FAILURE, "Could not add json object.");
    }
    if (json_object_object_add(*jso, "digests", jso_ary)) {
        return_error(TSS2_FAPI_RC_GENERAL_FAILURE, "Could not add json object.");
    }

    r = add_uint8_ary_to_json(digest, digest_size, jso_digest, "digest");
    return_if_error(r, "Add digest to json");

    r = add_string_to_json("ima_template", *jso, CONTENT_TYPE);
    return_if_error(r, "Add number to json object.");

    r = get_json_content(*jso, &jso_content);
    return_if_error(r, "Get sub event");

    r = add_string_to_json(ima_type, jso_content, "template_name");
    return_if_error(r, "Add number to json object.");

    if (json_object_array_add(jso_list, *jso)) {
        return_error(TSS2_FAPI_RC_GENERAL_FAILURE, "Could not add json object.");
    }
    return TSS2_RC_SUCCESS;
}

/* Type to store field data and the field callback */
struct template_field {
    const char *field_id;
    TSS2_RC(*field_cb)
    (UINT8 *digest, UINT8 *buffer, size_t *offset, json_object *jso, IFAPI_IMA_TEMPLATE *template);
};

/* Type for storing the IMA template descriptor */
struct template_description {
    char *ima_type;
    char *format;
};

/* Callbacks to initialize result list and add events to the list */
struct event_callbacks {
    TSS2_RC (*init_list_cb)(json_object **jso);
    TSS2_RC(*add_header_cb)
    (size_t        recnum,
     UINT32        pcr,
     const char   *ima_type,
     UINT8        *digest,
     size_t        digest_size,
     json_object  *jso_list,
     json_object **jso_current_event);
};

static struct event_callbacks event_callbacks
    = { .init_list_cb = init_event_list_json_cb, .add_header_cb = event_header_json_cb };

/*
 * Supported Descriptors and Template Fields.
 */
static struct template_description template_tab[]
    = { { .ima_type = "ima", .format = "d|n" },
        { .ima_type = "ima-ng", .format = "d-ng|n-ng" },
        { .ima_type = "ima-sig", .format = "d-ng|n-ng|sig" } };

static struct template_field field_tab[] = {
    { .field_id = "d", .field_cb = sha_digest_json_cb },
    { .field_id = "n", .field_cb = eventname_cb },
    { .field_id = "d-ng", .field_cb = digest_with_hash_name_cb },
    { .field_id = "n-ng", .field_cb = eventname_ng_json_cb },
    { .field_id = "sig", .field_cb = signature_cb },
};

/**  Convert the IMA event to output format.
 *
 * Based on the event description in the ima template the appropriate callbacks are
 * determined and executed to generate the ouput.
 *
 * @param[in] template The IMA event.
 * @param[out] jso The output object.
 * @param[out] name The name of the event object.
 * @retval TSS2_RC_SUCCESS if the function call was a success.
 * @retval TSS2_FAPI_RC_BAD_VALUE If the current template foramt can't be processed.
 * @retval TSS2_FAPI_RC_MEMORY: if the FAPI cannot allocate enough memory.
 */
static TSS2_RC
convert_ima_event_buffer(IFAPI_IMA_TEMPLATE *template, json_object *jso, char **name) {
    TSS2_RC                      r;
    size_t                       offset = 0;
    size_t                       i, j;
    char                        *copy_of_template_format = NULL;
    char                        *copy_of_template_format_orig = NULL;
    char                        *current_field;
    struct template_description *template_desc = NULL;
    size_t       size_of_template_tab = sizeof(template_tab) / sizeof(template_tab[0]);
    size_t       size_of_field_tab = sizeof(field_tab) / sizeof(field_tab[0]);
    json_object *jso_content;

    /* Search template decription corresponding to the template type. */
    for (i = 0; i < size_of_template_tab; i++) {
        if (strcmp(template->ima_type, template_tab[i].ima_type) == 0) {
            template_desc = template_tab + i;
            break;
        }
    }

    if (i == size_of_template_tab) {
        return_error(TSS2_FAPI_RC_BAD_VALUE, "Ima template type not supported.");
    }

    copy_of_template_format = strdup(template_desc->format);
    /* Save pointer for cleanup. */
    copy_of_template_format_orig = copy_of_template_format;
    goto_if_null(copy_of_template_format, "Out of memory.", TSS2_FAPI_RC_MEMORY, error);
    /* Loop over template fields separated by | */
    for (i = 0; (current_field = strsep(&copy_of_template_format, "|")) != NULL; i++) {
        struct template_field *field = NULL;

        for (j = 0; j < size_of_field_tab; j++) {
            if (!strcmp(current_field, field_tab[j].field_id)) {
                field = field_tab + j;
                break;
            }
        }

        goto_if_null2(field, "Unknown field %s", r, TSS2_FAPI_RC_BAD_VALUE, error, current_field);

        /* Convert the IMA data with the found callback. */
        r = field->field_cb(&template->header.digest[0], template->event_buffer, &offset, jso,
                            template);
        *name = template->name;
        goto_if_error(r, "Get field", error);
    }
    if (jso) {
        r = get_json_content(jso, &jso_content);
        goto_if_error(r, "Get sub event", error);

        r = add_uint8_ary_to_json(template->event_buffer, template->event_size, jso_content,
                                  "template_data");
        goto_if_error(r, "Create data to be hashed", error);
    }

    SAFE_FREE(copy_of_template_format_orig);
    return TSS2_RC_SUCCESS;
error:
    SAFE_FREE(copy_of_template_format_orig);
    return r;
}

/** Get one event from the event file.
 *
 * Retrieve the part of the event behind the fixed event header from the
 * event file.
 *
 * @param[in,out] template The IMA event with the already read event header.
 *                the storage for the rest of the event will be allocated
 *                and stored in the template.
 * @param[in] fp The event stream.
 *
 * @retval TSS2_RC_SUCCESS if the function call was a success.
 * @retval TSS2_FAPI_RC_MEMORY: if the FAPI cannot allocate enough memory.
 */
static TSS2_RC
read_event_buffer(IFAPI_IMA_TEMPLATE *template, FILE *fp) {
    bool   old_ima_format;
    size_t size, rsize;

    /* Check IMA  legacy format. */
    if (strcmp(template->ima_type, "ima") == 0) {
        old_ima_format = true;
        /* Size is fixed for old IMA format. */
        template->event_size = TPM2_SHA1_DIGEST_SIZE + TCG_EVENT_NAME_LEN_MAX + 1;
        size = TPM2_SHA1_DIGEST_SIZE;
    } else {
        old_ima_format = false;
        /* For the new IMA format get size from event. */
        rsize = fread(&template->event_size, sizeof(UINT32), 1, fp);
        if (rsize != 1) {
            return_error(TSS2_FAPI_RC_BAD_VALUE, "Invalid ima data");
        }
        if (template->convert_to_big_endian) {
            template->event_size = endian_swap_32(template->event_size);
        }
        size = template->event_size;
    }

    template->event_buffer = calloc(template->event_size, sizeof(UINT8));
    return_if_null(template->event_buffer, "Out of memory", TSS2_FAPI_RC_MEMORY);

    rsize = fread(template->event_buffer, size, 1, fp);
    if (rsize != 1) {
        return_error(TSS2_FAPI_RC_BAD_VALUE, "Invalid ima data");
    }
    if (old_ima_format) {
        UINT32 field_len;

        rsize = fread(&field_len, sizeof(UINT32), 1, fp);
        if (rsize != 1) {
            return_error(TSS2_FAPI_RC_BAD_VALUE, "Invalid ima data");
        }
        if (template->convert_to_big_endian) {
            field_len = endian_swap_32(field_len);
        }
        if (field_len > template->event_size - TPM2_SHA1_DIGEST_SIZE) {
            return_error(TSS2_FAPI_RC_BAD_VALUE, "Invalid ima data");
        }
        rsize = fread(template->event_buffer + TPM2_SHA1_DIGEST_SIZE, field_len, 1, fp);
        if (rsize != 1) {
            return_error(TSS2_FAPI_RC_BAD_VALUE, "Invalid ima data");
        }
    }
    return TSS2_RC_SUCCESS;
}

size_t
read_ima_header(IFAPI_IMA_TEMPLATE *template, FILE *fp, TSS2_RC *rc) {
    /* header: pcr register, sha1 digest, size field ima type string, start of ima type */
    size_t header_size = sizeof(UINT32) + TPM2_SHA1_DIGEST_SIZE + sizeof(UINT32) + 3;
    size_t size, rsize;
    size_t pos_ima_type = header_size - 3 - sizeof(UINT32);

    *rc = TSS2_RC_SUCCESS;

    size = fread(&template->header, 1, header_size, fp);
    if (size == 0) {
        return size;
    }

    if (size != header_size) {
        *rc = TSS2_FAPI_RC_BAD_VALUE;
        LOG_ERROR("Invalid ima data");
        return 0;
    }

    template->convert_to_big_endian = need_to_convert_to_big_endian(template);

    if (memcmp(&template->header.digest[pos_ima_type], "ima", 3) == 0) {
        /* Start of IMA type string found. */
        memcpy(&template->ima_type_size, &template->header.digest[pos_ima_type - sizeof(UINT32)],
               sizeof(UINT32));
        if (template->convert_to_big_endian) {
            template->ima_type_size = endian_swap_32(template->ima_type_size);
        }
        memcpy(&template->ima_type[0], "ima", 3);
        /* Get the description of the IMA event. */
        if (template->ima_type_size < 3 || template->ima_type_size >= TCG_EVENT_NAME_LEN_MAX) {
            LOG_ERROR("Invalid ima data");
            *rc = TSS2_FAPI_RC_BAD_VALUE;
            return 0;
        }
        size = template->ima_type_size - 3;
        if (size > 0) {
            rsize = fread(&template->ima_type[3], size, 1, fp);
            if (rsize != 1) {
                LOG_ERROR("Invalid ima data");
                *rc = TSS2_FAPI_RC_BAD_VALUE;
                return 0;
            }
            template->ima_type[template->ima_type_size] = '\0';
        } else {
            template->ima_type[3] = '\0';
        }
        template->hash_alg = TPM2_ALG_SHA1;
        template->hash_size = TPM2_SHA1_DIGEST_SIZE;
        return header_size;
    }
    return 0;
}

/** Read ima eventlog and create JSON list of events.
 *
 * @param[in] filename The filname of the IMA event file.
 * @param[in] pcrList The list of PCRs that are to be quoted
 * @param[in] pcrListSize The size of pcrList in bytes
 * @param[out] The event_list in JSON format.
 * @retval TSS2_RC_SUCCESS if the function call was a success.
 * @retval TSS2_FAPI_RC_IO_ERROR If a event cannot be read.
 * @retval TSS2_FAPI_RC_MEMORY: if the FAPI cannot allocate enough memory for
 *         internal operations or return parameters.
 */
TSS2_RC
ifapi_read_ima_event_log(const char     *filename,
                         const uint32_t *pcrList,
                         size_t          pcrListSize,
                         json_object   **jso_list) {
    TSS2_RC r;
    FILE   *fp = NULL;
    IFAPI_IMA_TEMPLATE template = { 0 };
    size_t       recnum = 0, i;
    json_object *jso_current_event = NULL;
    ;
    bool     add_event;
    uint32_t pcr = 0;

    return_if_null(jso_list, "Bad reference.", TSS2_FAPI_RC_BAD_VALUE);
    template.event_buffer = NULL;

    OpenSSL_add_all_digests();
    fp = fopen(filename, "r");
    goto_if_null2(fp, "Could not open: %s", r, TSS2_FAPI_RC_IO_ERROR, error, filename);
    LOG_INFO("IMA read file: %s", filename);

    r = event_callbacks.init_list_cb(jso_list);
    goto_if_error(r, "Initialize event list.", error);

    /* While the event header with fixed size can be read. */
    while (read_ima_header(&template, fp, &r)) {
        if (r) {
            return_error(r, "Invalid ima data.")
        }
        add_event = true;
        if (!template.ima_type_size || template.ima_type_size > TCG_EVENT_NAME_LEN_MAX) {
            goto_error(r, TSS2_FAPI_RC_BAD_VALUE, "Invalid ima type size", error);
        }

        /* Check whether IMA PCR is member of pcrList. */
        for (i = 0; i < pcrListSize; i++) {
            pcr = template.header.pcr;
            if (template.convert_to_big_endian) {
                pcr = endian_swap_32(pcr);
            }
            if (pcrList[i] == pcr)
                break;
        }
        if (i == pcrListSize) {
            /* PCR  is not used. */
            add_event = false;
        }

        if (add_event) {
            r = event_callbacks.add_header_cb(recnum, pcr, template.ima_type,
                                              template.header.digest, template.hash_size, *jso_list,
                                              &jso_current_event);
            goto_if_error(r, "Add header to event list.", error);

            recnum += 1;
        }

        /* Read the rest of the event. */
        r = read_event_buffer(&template, fp);
        goto_if_error(r, "Read event buffer.", error);

        if (add_event) {
            char *name;
            r = convert_ima_event_buffer(&template, jso_current_event, &name);
            goto_if_error(r, "Create json event.", error);
        }
        SAFE_FREE(template.event_buffer);
    }
    fclose(fp);
    return TSS2_RC_SUCCESS;

error:
    SAFE_FREE(template.event_buffer);
    if (fp)
        fclose(fp);
    if (*jso_list)
        json_object_put(*jso_list);
    return r;
}

static char *field_IFAPI_IMA_EVENT_tab[] = { "template_value", "template_data", "template_name" };

/** Deserialize a IFAPI_IMA_EVENT_TYPE json object.
 *
 * @param[in]  jso the json object to be deserialized.
 * @param[out] out the deserialzed binary object.
 * @retval TSS2_RC_SUCCESS if the function call was a success.
 * @retval TSS2_FAPI_RC_BAD_VALUE if the json object can't be deserialized.
 */
TSS2_RC
ifapi_json_IFAPI_IMA_EVENT_TYPE_deserialize(json_object *jso, IFAPI_IMA_EVENT_TYPE *out) {
    LOG_TRACE("call");
    return ifapi_json_IFAPI_IMA_EVENT_TYPE_deserialize_txt(jso, out);
}

typedef struct {
    IFAPI_IMA_EVENT_TYPE in;
    char                *name;
} IFAPI_IFAPI_IMA_EVENT_TYPE_ASSIGN;

static IFAPI_IFAPI_IMA_EVENT_TYPE_ASSIGN deserialize_IFAPI_IMA_EVENT_TYPE_tab[] = {
    { IFAPI_IMA_EVENT_TAG_IMA, "ima" },
    { IFAPI_IMA_EVENT_TAG_NG, "ima-ng" },
    { IFAPI_IMA_EVENT_TAG_SIG, "ima-sig" },
};

/**  Deserialize a json object of type IFAPI_IMA_EVENT_TYPE.
 *
 * @param[in]  jso the json object to be deserialized.
 * @param[out] out the deserialzed binary object.
 * @retval TSS2_RC_SUCCESS if the function call was a success.
 * @retval TSS2_FAPI_RC_BAD_VALUE if the json object can't be deserialized.
 */
TSS2_RC
ifapi_json_IFAPI_IMA_EVENT_TYPE_deserialize_txt(json_object *jso, IFAPI_IMA_EVENT_TYPE *out) {
    LOG_TRACE("call");
    const char *token = json_object_get_string(jso);
    size_t      i;
    size_t      n = sizeof(deserialize_IFAPI_IMA_EVENT_TYPE_tab)
               / sizeof(deserialize_IFAPI_IMA_EVENT_TYPE_tab[0]);
    size_t size = strlen(token);
    for (i = 0; i < n; i++) {
        if (strncasecmp(&token[0], &deserialize_IFAPI_IMA_EVENT_TYPE_tab[i].name[0], size) == 0) {
            *out = deserialize_IFAPI_IMA_EVENT_TYPE_tab[i].in;
            return TSS2_RC_SUCCESS;
        }
    }
    return_error(TSS2_FAPI_RC_BAD_VALUE, "Undefined constant.");
}

/** Deserialize a IFAPI_IMA_EVENT json object.
 *
 * @param[in]  jso the json object to be deserialized.
 * @param[out] out the deserialzed binary object.
 * @retval TSS2_RC_SUCCESS if the function call was a success.
 * @retval TSS2_FAPI_RC_BAD_VALUE if the json object can't be deserialized.
 * @retval TSS2_FAPI_RC_BAD_REFERENCE a invalid null pointer is passed.
 * @retval TSS2_FAPI_RC_MEMORY if not enough memory can be allocated.
 */
TSS2_RC
ifapi_json_IFAPI_IMA_EVENT_deserialize(json_object *jso, IFAPI_IMA_EVENT *out) {
    json_object *jso2;
    TSS2_RC      r;

    LOG_TRACE("call");
    return_if_null(out, "Bad reference.", TSS2_FAPI_RC_BAD_REFERENCE);

    ifapi_check_json_object_fields(jso, &field_IFAPI_IMA_EVENT_tab[0],
                                   SIZE_OF_ARY(field_IFAPI_IMA_EVENT_tab));

    if (!ifapi_get_sub_object(jso, "template_name", &jso2)) {
        LOG_ERROR("Field \"template_name\" not found.");
        return TSS2_FAPI_RC_BAD_VALUE;
    }
    r = ifapi_json_IFAPI_IMA_EVENT_TYPE_deserialize(jso2, &out->template_name);
    return_if_error(r, "Bad value for field \"template_name\".");

    if (!ifapi_get_sub_object(jso, "template_data", &jso2)) {
        if (!ifapi_get_sub_object(jso, "template_value", &jso2)) {
            LOG_ERROR("Field \"template_data\" not found.");
            return TSS2_FAPI_RC_BAD_VALUE;
        }
    }
    r = ifapi_json_UINT8_ARY_deserialize(jso2, &out->template_value);
    return_if_error(r, "Bad value for field \"template_data \".");

    LOG_TRACE("true");
    return TSS2_RC_SUCCESS;
}

TSS2_RC
ifapi_get_ima_eventname(IFAPI_IMA_EVENT *ima_event, char **name) {
    TSS2_RC r;
    IFAPI_IMA_TEMPLATE template;
    size_t i;
    size_t n = SIZE_OF_ARY(deserialize_IFAPI_IMA_EVENT_TYPE_tab);

    memset(&template, 0, sizeof(template));
    for (i = 0; i < n; i++) {
        if (deserialize_IFAPI_IMA_EVENT_TYPE_tab[i].in == ima_event->template_name) {
            char *tab_name = deserialize_IFAPI_IMA_EVENT_TYPE_tab[i].name;
            memcpy(&template.ima_type, tab_name, strlen(tab_name) + 1);
            break;
        }
    }
    if (i >= n) {
        return_error(TSS2_FAPI_RC_BAD_VALUE, "Undefined constant.");
    }
    template.event_size = ima_event->template_value.size;
    template.event_buffer = &ima_event->template_value.buffer[0];
    r = convert_ima_event_buffer(&template, NULL, name);
    return_if_error(r, "Parsing of IMA template failed.");

    return TSS2_RC_SUCCESS;
}
