
/*
 * =====================================================================================
 *
 *       Filename:  hdm_hash.c
 *
 *    Description:  HDM hash manipulation
 *
 *        Version:  1.0
 *        Created:  09/16/2019 15:26:11 PM
 *       Revision:  none
 *       Compiler:  gcc
 *
 *        Company:  Samsung Electronics
 *        Copyright (c) 2015 by Samsung Electronics, All rights reserved.
 *
 * =====================================================================================
 */

/** Includes */
#include "hdm_hash.h"

/**
 * @brief
 * compute_hash
 * Computes hash of given type for the given input string.
 *
 * @param[in]  msg     - message to hash
 * @param[in]  msg_len - length of the message
 * @param[out] out     - hashed message
 * @param[out] out_len - length of the hashed message
 * @param[in]  evp_md  - hash function to use
 *
 * @return HDM status code
 */
hdm_return_code_t compute_hash(uint8_t *msg, uint32_t msg_len, uint8_t *out, uint32_t *out_len, const EVP_MD *evp_md) {
        HDM_LOG_DEBUG("compute_hash()");

        hdm_return_code_t ret = HDM_COMPUTE_HASH_FAIL;
        EVP_MD_CTX *evp_md_ctx;
        evp_md_ctx = EVP_MD_CTX_new();

        if (evp_md_ctx == NULL) { // SI-16863
            HDM_LOG("FAILED to new evp_md_ctx");
            return HDM_COMPUTE_HASH_FAIL;
        }

        if (!EVP_DigestInit_ex(evp_md_ctx, evp_md, NULL)) {
                HDM_LOG("FAILED to initialize context to alg");
                ret = HDM_COMPUTE_HASH_FAIL;
                goto error;
        }

        if (!EVP_DigestUpdate(evp_md_ctx, msg, msg_len)) {
                HDM_LOG("FAILED to compute hash");
                ret = HDM_COMPUTE_HASH_FAIL;
                goto error;
        }

        if (!EVP_DigestFinal_ex(evp_md_ctx, out, out_len)) {
                HDM_LOG("FAILED to copy hashed value to output");
                ret = HDM_COMPUTE_HASH_FAIL;
                goto error;
        }

        ret = HDM_STATUS_SUCCESS;
error:
        EVP_MD_CTX_free(evp_md_ctx);
        return ret;
}

/**
 * @brief
 * gen_rpmb_hash
 * Receives an empty buffer and fills it with the SHA256 of the concatenation of MAGIC, POLICY counter and DEVICE block.
 *
 * @param[in] payload    - payload with device_block and policy_version info
 * @param[out] rpmb_hash - buffer to be filled with the RPMB hash
 * @param[out] len       - rpmb_hash lenght
 *
 * @return HDM status code
 */
hdm_return_code_t gen_rpmb_hash(void *payload, uint8_t *rpmb_hash, uint32_t *len, uint32_t version) {
        HDM_LOG_DEBUG("gen_rpmb_hash()");

        uint32_t ret = HDM_GEN_RPMB_HASH_FAIL;
        uint32_t magic = HDM_MAGIC;

        if (version == HDM_SINGLE_POLICY_VERSION) {
            tz_hdm_rpmb_0000_t *pVer0000_data = (tz_hdm_rpmb_0000_t *)payload;
            uint8_t input[HDM_RPMB_BLOCK_SIZE] = {0};

            TEE_MemMove(&input[0*sizeof(uint32_t)], &magic, sizeof(uint32_t));
            TEE_MemMove(&input[1*sizeof(uint32_t)], &pVer0000_data->device_block, sizeof(uint32_t));
            TEE_MemMove(&input[2*sizeof(uint32_t)], &pVer0000_data->compromise_block, sizeof(uint32_t));
            TEE_MemMove(&input[3*sizeof(uint32_t)], &pVer0000_data->policy_version, sizeof(uint32_t));
            TEE_MemMove(&input[4*sizeof(uint32_t)], &pVer0000_data->device_id, JWS_PAYLOAD_DEVICE_ID_LEN);

            if (compute_hash(input, 4*sizeof(uint32_t)+JWS_PAYLOAD_DEVICE_ID_LEN, rpmb_hash, len, EVP_sha256()) == HDM_STATUS_SUCCESS) {
                    return HDM_STATUS_SUCCESS;
            }
        }
        else if (version == HDM_CURRENT_RPMB_VERSION) {
            HDM_LOG_DEBUG("SIZE OF tz_hdm_rpmb_t : %d", sizeof(tz_hdm_rpmb_t));
            if (compute_hash(payload, sizeof(tz_hdm_rpmb_t)-SHA256_DIGEST_LENGTH , rpmb_hash, len, EVP_sha256()) == HDM_STATUS_SUCCESS) {
                return HDM_STATUS_SUCCESS;
            }
        }

        HDM_LOG("Fail to compute RPMB Hash");
        return HDM_GEN_RPMB_HASH_FAIL;
}

/**
 * @brief
 * compare_rpmb_hash
 * Generates the hash for the stored payload values and compares it against the hash value stored.
 *
 * @param[in] rpmb_data  - data from the rpmb
 * @return HDM status code
 */
hdm_return_code_t compare_rpmb_hash(void *rpmb_data, uint32_t version) {
        HDM_LOG_DEBUG("compare_rpmb_hash()");

        hdm_return_code_t ret = HDM_STATUS_SUCCESS;
        uint8_t hash[SHA256_DIGEST_LENGTH] = {0};
        uint8_t *stored_hash;
        uint32_t len = 0;

        if (version == HDM_CURRENT_RPMB_VERSION)
            stored_hash = ((tz_hdm_rpmb_t*)rpmb_data)->hash_value;
        else if (version == HDM_SINGLE_POLICY_VERSION)
            stored_hash = ((tz_hdm_rpmb_0000_t*)rpmb_data)->hash_value;
        else {
            HDM_LOG("Fail to generate RPMB Hash");
            ret = HDM_GEN_RPMB_HASH_FAIL;
            goto exit;
        }

        if (gen_rpmb_hash(rpmb_data, hash, &len, version) != HDM_STATUS_SUCCESS) {
                HDM_LOG("Fail to generate RPMB Hash");
                ret = HDM_GEN_RPMB_HASH_FAIL;
                goto exit;
        }

        if (TEE_MemCompare(hash, stored_hash, SHA256_DIGEST_LENGTH) != 0) {
                HDM_LOG("RPMB integrity FAIL");
                ret = HDM_RPMB_INTEGRITY_FAIL;
                goto exit;
        }

        ret = HDM_STATUS_SUCCESS;
exit:
        return ret;
}

/**
 * @brief
 * gen_device_id
 * Compare and generates hash value of device ID
 *
 * @param[in]   *nwd_device_id      - Device ID acquired from normal world
 * @param[in]   *rpmb_device_id     - Device ID acquired from RPMB
 * @param[out]  *device_id_hash     - B64( H(device_id) )
 * @param[out]  *device_id_hash_len - B64 device_id length is fixed value 44 (DEVICE_ID_B64_LEN)
 *
 * @return HDM status code
 */
hdm_return_code_t gen_device_id(uint8_t *nwd_device_id, uint8_t *rpmb_device_id, uint8_t *device_id_hash, uint32_t *device_id_hash_len) {
        HDM_LOG_DEBUG("gen_device_id()");

        hdm_return_code_t ret = HDM_DEVICE_ID_CHECK_FAIL;
        uint8_t hash_computed[SHA256_DIGEST_LENGTH + 1] = {0,};
        uint32_t hash_computed_len = SHA256_DIGEST_LENGTH;
        uint8_t b64_sha256_device_id[(DEVICE_ID_B64_LEN * 2) +1] = {0,};
        uint32_t b64_sha256_device_id_len = DEVICE_ID_B64_LEN * 2;
        

        if (strlen((char *) nwd_device_id) > NWS_INFO_LEN) {
                HDM_LOG_DEBUG("nwd device id invalid length");
                ret = HDM_GEN_DEVICE_ID_FAIL;
                goto exit;
        }
        

        if (rpmb_device_id != NULL && strlen((char *) rpmb_device_id) > JWS_PAYLOAD_DEVICE_ID_LEN) {
                HDM_LOG_DEBUG("rpmb device id invalid length");
                ret = HDM_GEN_DEVICE_ID_FAIL;
                goto exit;
        }

        ret = compute_hash(nwd_device_id, strlen((char *) nwd_device_id), hash_computed, &hash_computed_len, EVP_sha256());
        if (ret != HDM_STATUS_SUCCESS) {
                HDM_LOG("Fail to compute device id hash");
                HDM_LOG_DEBUG("Fail to compute device id hash ret = %d", ret);
                goto exit;
        }

        ret = base64url_encode(hash_computed, hash_computed_len, b64_sha256_device_id, &b64_sha256_device_id_len);
        if (b64_sha256_device_id_len != DEVICE_ID_B64_LEN || ret != 0) {
                HDM_LOG("Fail to encode hash_computed");
                HDM_LOG_DEBUG("hash_computed %s", hash_computed);
                HDM_LOG_DEBUG("b64_sha256_device_id %s", b64_sha256_device_id);
                ret = HDM_GEN_DEVICE_ID_FAIL;
                goto exit;
        }

        if (rpmb_device_id != NULL) {
                ret = TEE_MemCompare(b64_sha256_device_id, rpmb_device_id, DEVICE_ID_B64_LEN);
                if(ret != 0) {
                        HDM_LOG("Device id mismatch");
                        HDM_LOG_DEBUG("RPMB device id mismathces with nwd generated device ID");
                        ret = HDM_GEN_DEVICE_ID_FAIL;
                        goto exit;
                }
        }

        TEE_MemMove(device_id_hash, b64_sha256_device_id, DEVICE_ID_B64_LEN);
        *device_id_hash_len = b64_sha256_device_id_len;
        device_id_hash[*device_id_hash_len] = '\0';

exit:
        return ret;
}

/**
 * @brief
 * gen_drk_device_id
 * Generates Device ID to match with DRKv2 Certificate
 *
 * @param[in]     hash_imei     - Hashed IMEI/MAC Address
 * @param[in]     serial_number - Serial Number
 * @param[out]    device_id     - B64(H(H(ID) | H(SN))), ID = ((IMEI_1 | IMEI_2) or MAC_ADDR)
 * @param[in|out] device_id_len - B64 device_id length is fixed value 44 (DEVICE_ID_B64_LEN)
 *
 * @return HDM status code
 */
hdm_return_code_t gen_drk_device_id(uint8_t *hash_imei, uint8_t *serial_number, uint8_t *device_id, uint32_t *device_id_len) {

        HDM_LOG_DEBUG("gen_drk_device_id()");

        hdm_return_code_t ret = HDM_STATUS_FAIL;
        uint32_t device_id_b64_len = DEVICE_ID_B64_LEN * 2;
        uint32_t sha256_id_len = SHA256_DIGEST_LENGTH;
        uint32_t sha256_serial_number_len = SHA256_DIGEST_LENGTH;

        SHA256_CTX sha256;
        uint8_t imei_concat[(2 * IMEI_LEN) + 1] = {0,};
        uint8_t sha256_id[SHA256_DIGEST_LENGTH + 1] = {0,};
        uint8_t sha256_serial_number[SHA256_DIGEST_LENGTH + 1] = {0,};
        uint8_t sha256_device_id[SHA256_DIGEST_LENGTH + 1] = {0,};
        uint8_t b64_sha256_device_id[DEVICE_ID_B64_LEN + 1] = {0,};

        uint8_t *sn = serial_number;

        /* H(ID) */
        TEE_MemMove(sha256_id, hash_imei, SHA256_DIGEST_LENGTH);
        HDM_LOG("hash_imei[%d] : %s", strlen((char*)hash_imei), (char *)hash_imei);

#if 0
        /* H(SN) */
        ret = compute_hash(serial_number, strlen((char*)serial_number), sha256_serial_number2, &sha256_serial_number_len, EVP_sha256());
        if (ret != HDM_STATUS_SUCCESS || sha256_serial_number_len != SHA256_DIGEST_LENGTH) {
                HDM_LOG("Fail to compute H(SN)");
                HDM_LOG_DEBUG("Fail sha256_serial_number_len = %d", sha256_serial_number_len);
                ret = HDM_GEN_DEVICE_ID_FAIL;
                goto exit;
        }
#else   
        TEE_MemMove(sha256_serial_number, serial_number, SHA256_DIGEST_LENGTH);
#endif
        /* H(H(ID) | H(SN)) */
        SHA256_Init(&sha256);
        SHA256_Update(&sha256, sha256_id, sha256_id_len);
        SHA256_Update(&sha256, sha256_serial_number, sha256_serial_number_len);
        ret = SHA256_Final(sha256_device_id, &sha256);
        if (ret == 0) {
                HDM_LOG("Fail to compute H(H(ID), H(SN))");
                HDM_LOG_DEBUG("Fail SHA256_Final ret = %d", ret);
                ret = HDM_GEN_DEVICE_ID_FAIL;
                goto exit;
        }

        /* B64(H(H(ID), H(SN))) */
        ret = base64url_encode(sha256_device_id, SHA256_DIGEST_LENGTH, b64_sha256_device_id, &device_id_b64_len);
        if (device_id_b64_len != DEVICE_ID_B64_LEN || ret != 0) {
                HDM_LOG("Fail to compute B64(H(H(ID), H(SN)))");
                HDM_LOG_DEBUG("device_id_b64_len = %d", device_id_b64_len);
                ret = HDM_GEN_DEVICE_ID_FAIL;
                goto exit;
        }

        /* Check external device_id allocation size */
        if (*device_id_len < DEVICE_ID_B64_LEN) {
                HDM_LOG("device_id Out of Memory");
                HDM_LOG_DEBUG("device_id_len = %d", *device_id_len);
                ret = HDM_COMPUTE_HASH_FAIL;
                goto exit;
        }

        TEE_MemMove(device_id, b64_sha256_device_id, device_id_b64_len);
        *device_id_len = device_id_b64_len;

        HDM_LOG_DEBUG("calculed device_id = %s", device_id);
        ret = HDM_STATUS_SUCCESS;
exit:
        return ret;
}

