/*
 * =====================================================================================
 *
 *       Filename:  qseeCryptoApi.c
 *
 *    Description:  APIs combined with QSEE APIs.
 *
 *        Version:  1.0
 *        Created:  04/27/2017 05:01:07 PM
 *       Compiler:  armcc
 *
 *         Author:  Dongwook Shim (), dw.shim@samsung.com
 *        Company:  Samsung Electronics
 *
 *        Copyright (c) 2017 by Samsung Electronics, All rights reserved.
 *
 * =====================================================================================
 */

#include "commonConfig.h"
#include "log.h"
#include "teeCryptoApi.h"

#include "qsee_message.h"
#include "qsee_prng.h"
#include "qsee_hash.h"
#include "qsee_cipher.h"

#if (defined USE_QSEE) && (defined USE_QSEE_SFS)
#include "systemConfig.h"
#include "qsee_fs.h"
#include "qsee_sfs.h"

#define DRK_KEY_PATH              "dev_root.dat"
#endif  // End of USE_QSEE && USE_QSEE_SFS

#define MAX_TID_SIZE            128
#define SO_HEADER_AND_MAC_LEN      144

int32_t createSecureObject(const uint8_t *inData, const uint32_t inDataLen,
        uint8_t *outData, uint32_t *outDataLen, const uint8_t *targetUid, const uint32_t targetUidLen)
{
    int32_t ret = NOT_ERROR;

    if(inData == NULL || inDataLen == 0 || outData == NULL || outDataLen == NULL || targetUid == NULL)
    {
        LOGE("%s : Invalid argument.", __func__);
        return ERR_TA_INVALID_ARGUMENT;
    }

    //SI-13985 : Possibility of Out-of-bounds write
    //Out-of-bounds write (or Integer overflow) is expected
    //Therefore outDataLen be checked before assign the result (headerLen[144] + inDataLen).
    if(*outDataLen <= SO_HEADER_AND_MAC_LEN || inDataLen > *outDataLen - SO_HEADER_AND_MAC_LEN) {
        return ERR_TA_BUFFER_OVERFLOW;
    }

    if((ret = qsee_encapsulate_inter_app_message((char *)targetUid, (uint8_t *)inData, inDataLen,
                    outData, (qc_uint32_t *)outDataLen)) != NOT_ERROR)
    {
        LOGE("Failed to create secure object with error 0x%X.", ret);
        ret = ERR_TA_QSEE_BASE - (ret & 0x0000FFF);
    }

    return ret;
}

int32_t openSecureObject(const uint8_t *inData, const uint32_t inDataLen,
        uint8_t *outData, uint32_t *outDataLen, const uint8_t *targetUid, const uint32_t targetUidLen)
{
    int32_t ret = NOT_ERROR;
    char srcTaName[MAX_TID_SIZE];

    if(inData == NULL || inDataLen == 0 || outData == NULL || outDataLen == NULL || targetUid == NULL)
    {
        LOGE("%s : Invalid argument.", __func__);
        return ERR_TA_INVALID_ARGUMENT;
    }

    memset(srcTaName, 0, sizeof(srcTaName));

    if((ret = qsee_decapsulate_inter_app_message(srcTaName, (uint8_t *)inData, inDataLen,
                    outData, (qc_uint32_t *)outDataLen)) != NOT_ERROR)
    {
        LOGE("Failed to open secure object with error 0x%X.", ret);
        return ERR_TA_QSEE_BASE - (ret & 0x0000FFF);
    }

    if(strncmp(srcTaName, (const char *)targetUid, (strlen(srcTaName) < targetUidLen) ? strlen(srcTaName) : targetUidLen))
    {
        LOGE("Unsupported source TA : %s.", srcTaName);
        ret = ERR_TA_INVALID_BLOB;
    }

    return ret;
}

int32_t getRandBlock(uint8_t *randomBuffer, uint32_t randomLen)
{
    int32_t ret = NOT_ERROR;

    if(randomBuffer == NULL || randomLen == 0)
    {
        LOGE("%s : Invalid argument.", __func__);
        return ERR_TA_INVALID_ARGUMENT;
    }

    if((ret = (int32_t)qsee_prng_getdata(randomBuffer, randomLen)) != randomLen)
    {
        LOGE("Request size(%d) and generated size(%d) is not matched.", randomLen, ret);
        return ERR_TA_GEN_RANDOM_FAILED;
    }

    return ret;
}

int32_t getShaDigest(const uint8_t *msg, size_t msgLen, uint8_t *digest, size_t digestLen, DigestAlgo_t algo)
{
    int32_t ret = NOT_ERROR;
    QSEE_HASH_ALGO_ET qseeHashAlgo;

    if(msg == NULL || msgLen == 0 || digest == NULL)
    {
        LOGE("%s : Invalid argument.", __func__);
        return ERR_TA_INVALID_ARGUMENT;
    }

    switch(algo)
    {
        case  ALGO_SHA1 :
            if(digestLen != QSEE_SHA1_HASH_SZ)
            {
                LOGE("Digest buffer is not matched - %zu.", digestLen);
                return ERR_TA_INVALID_ARGUMENT;
            }
            qseeHashAlgo = QSEE_HASH_SHA1;
            break;

        case ALGO_SHA256 :
            if(digestLen != QSEE_SHA256_HASH_SZ)
            {
                LOGE("Digest buffer is not matched - %zu.", digestLen);
                return ERR_TA_INVALID_ARGUMENT;
            }
            qseeHashAlgo = QSEE_HASH_SHA256;
            break;

        default :
            LOGE("Not supported algothim : %d.", algo);
            return ERR_TA_INVALID_ARGUMENT;
    }

    if((ret = qsee_hash(qseeHashAlgo, msg, msgLen, digest, digestLen)) != NOT_ERROR)
    {
        LOGE("Failed to make digest with error 0x%X.", ret);
        return ERR_TA_QSEE_BASE - (ret & 0x0000FFF);
    }

    return ret;
}

int32_t aes256CbcEncrypt(const uint8_t *inData, uint32_t inDataLen, uint8_t *iv,
    uint8_t *outData, uint32_t *outDataLen, const uint8_t *key, const uint32_t keyLen, AesEncryptMode_t mode)
{
    int32_t ret = NOT_ERROR;
    qsee_cipher_ctx *ctx = NULL;
    QSEE_CIPHER_MODE_ET cipherMode = QSEE_CIPHER_MODE_CBC;
    QSEE_CIPHER_PAD_ET cipherPad = QSEE_CIPHER_PAD_PKCS7;
    int (*pFunc)(const qsee_cipher_ctx*, const uint8_t *, uint32_t, uint8_t *, uint32_t *) = NULL;

    if(inData == NULL || iv == NULL || outData == NULL || outDataLen == NULL || key == NULL)
    {
        LOGE("%s : Invalid argument.", __func__);
        return ERR_TA_INVALID_ARGUMENT;
    }

    switch(mode)
    {
        case AES_MODE_ENCRYPT :
            pFunc = qsee_cipher_encrypt;
            break;

        case AES_MODE_DECRYPT :
            pFunc = qsee_cipher_decrypt;
            break;

        default :
            LOGE("Unsupported mode - %d.", mode);
            return ERR_TA_INVALID_ARGUMENT;
    }

    if((ret = qsee_cipher_init(QSEE_CIPHER_ALGO_AES_256, &ctx)) != NOT_ERROR)
    {
        LOGE("Failed to cipherInit with error 0x%X.", ret);
        ret =  ERR_TA_QSEE_BASE - (ret & 0x0000FFF);
        goto end;
    }

    if((ret = qsee_cipher_set_param(ctx, QSEE_CIPHER_PARAM_KEY, (const void *)key, keyLen)) != NOT_ERROR)
    {
        LOGE("Failed to set key param with error 0x%X.", ret);
        ret = ERR_TA_QSEE_BASE - (ret & 0x0000FFF);
        goto end;
    }

    if((ret = qsee_cipher_set_param(ctx, QSEE_CIPHER_PARAM_MODE, &cipherMode, sizeof(cipherMode))) != NOT_ERROR)
    {
        LOGE("Failed to set mode param with error 0x%X.", ret);
        ret = ERR_TA_QSEE_BASE - (ret & 0x0000FFF);
        goto end;
    }

    if((ret = qsee_cipher_set_param(ctx, QSEE_CIPHER_PARAM_IV, (const void *)iv, IV_SIZE)) != NOT_ERROR)
    {
        LOGE("Failed to set iv param with error 0x%X.", ret);
        ret =  ERR_TA_QSEE_BASE - (ret & 0x0000FFF);
        goto end;
    }

    if((ret = qsee_cipher_set_param(ctx, QSEE_CIPHER_PARAM_PAD, &cipherPad, sizeof(cipherPad))) != NOT_ERROR)
    {
        LOGE("Failed to set pad param with error 0x%X.", ret);
        ret = ERR_TA_QSEE_BASE - (ret & 0x0000FFF);
        goto end;
    }

    if((ret = pFunc(ctx, inData, inDataLen, outData, (qc_uint32_t *)outDataLen)) != NOT_ERROR)
    {
        LOGE("Failed to excute cipher operation(%d) with error 0x%X.", mode, ret);
        ret = ERR_TA_QSEE_BASE - (ret & 0x0000FFF);
    }
end:
    if (ctx != NULL) qsee_cipher_free_ctx(ctx);


    return ret;
}

#if (defined USE_QSEE) && (defined USE_QSEE_SFS)
int32_t createLocalSecureObject(const uint8_t *inData, const uint32_t inDataLen, uint8_t *outData, uint32_t *outDataLen)
{
    int32_t ret = NOT_ERROR;
    int32_t fd = 0;

    if(inData == NULL || inDataLen == 0)
    {
        LOGE("%s : Invalid argument.", __func__);
        return ERR_TA_INVALID_ARGUMENT;
    }

    if((fd = qsee_sfs_open(DRK_KEY_PATH, O_RDWR | O_CREAT | O_TRUNC)) == 0)
    {
        ret = qsee_sfs_error(fd);
        LOGE("Failed to open SFS file with error %d.", ret);
        return ERR_TA_QSEE_BASE - ret;
    }

    if(qsee_sfs_write(fd, (char *)inData, inDataLen) != inDataLen)
    {
        ret = qsee_sfs_error(fd);
        LOGE("Failed to write SFS file with error %d.", ret);
        qsee_sfs_close(fd);
        return ERR_TA_QSEE_BASE - ret;
    }

    if(qsee_sfs_close(fd) != SFS_NO_ERROR)
    {
        ret = qsee_sfs_error(fd);
        LOGE("Failed to close SFS file with error %d.", ret);
        return ERR_TA_QSEE_BASE - ret;
    }

    *outDataLen = 0;

    return ret;
}

int32_t openLocalSecureObject(const uint8_t *inData, const uint32_t inDataLen, uint8_t *outData, uint32_t *outDataLen)
{
    int32_t ret = NOT_ERROR;
    int32_t fd = 0;
    uint32_t sfsSize = 0;

    if(outData == NULL || outDataLen == NULL)
    {
        LOGE("%s : Invalid argument.", __func__);
        return ERR_TA_INVALID_ARGUMENT;
    }

    if((fd = qsee_sfs_open(DRK_KEY_PATH, O_RDONLY)) == 0)
    {
        ret = qsee_sfs_error(fd);
        LOGE("Failed to open SFS file with error %d.", ret);
        return ERR_TA_QSEE_BASE - ret;
    }

    if(qsee_sfs_getSize(fd, &sfsSize) != SFS_NO_ERROR)
    {
        ret = qsee_sfs_error(fd);
        LOGE("Failed to get SFS file size with error %d.", ret);
        qsee_sfs_close(fd);
        return ERR_TA_QSEE_BASE - ret;
    }

    if(*outDataLen < sfsSize)
    {
        LOGE("Too small buffer to save data - %d %d.", *outDataLen, sfsSize);
        qsee_sfs_close(fd);
        return ERR_TA_BUFFER_OVERFLOW;
    }

    if(qsee_sfs_read(fd, (char *)outData, sfsSize) != sfsSize)
    {
        ret = qsee_sfs_error(fd);
        LOGE("Failed to read SFS file with error %d.", qsee_sfs_error(fd));
        qsee_sfs_close(fd);
        return ERR_TA_QSEE_BASE - ret;
    }

    if(qsee_sfs_close(fd) != SFS_NO_ERROR)
    {
        ret = qsee_sfs_error(fd);
        LOGE("Failed to close SFS file with error %d.", ret);
        return ERR_TA_QSEE_BASE - ret;
    }

    *outDataLen = sfsSize;

    return ret;
}
#endif  // End of USE_QSEE && USE_QSEE_SFS
