/*
 * =====================================================================================
 *
 *       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"
#include "PlatformConfig.h"

#define SO_HEADER_AND_MAC_LEN      144

#if (defined USE_QSEE_SFS)
#include "qsee_fs.h"
#include "qsee_sfs.h"
#else
#include "qsee_kdf.h"
#include "qsee_uf_aes.h"

#define AES_GCM_IV_SIZE            12
#define AES_GCM_TAG_SIZE           16
#endif // End of USE_QSEE_SFS

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 to %s with error 0x%X.", targetUid, 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_SFS)
static int32_t getDerivedKey(void *key, uint32_t keyLen)
{
    int32_t ret = NOT_ERROR;
    const char keyLabel[] = "SKM TZ KDF FUNCTION";
    const char salt[] = "This is salt of skm KDF function for QSEECOM";

    if(key == NULL || keyLen == 0)
    {
        LOGE("Invalid parameter.");
        return ERR_TA_INVALID_ARGUMENT;
    }

    if((ret = qsee_kdf(NULL, keyLen, (void *)keyLabel, strlen(keyLabel), (void *)salt, strlen(salt),
                    key, keyLen)) != NOT_ERROR)
    {
        LOGE("QSEE_KDF returned error (%d).", ret);
        ret = ERR_TA_QSEE_BASE + ret;
    }

    return ret;
}

static int32_t aes256GcmEncrypt(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, cleanRet = NOT_ERROR;
    const uint8_t aesGcmAAD[] = {0x42, 0xF6, 0x7E, 0x3F, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10};
    uint8_t *pGcmTag = NULL;
    CryptoCntxHandle *cntx;
    IovecListType ioVecIn;
    IovecListType ioVecOut;
    IovecType IovecIn;
    IovecType IovecOut;
    SW_CipherEncryptDir dir;
    SW_CipherModeType cipherMode = SW_CIPHER_MODE_GCM;

    if(inData == NULL || iv == NULL || outData == NULL || outDataLen == NULL || key == NULL || keyLen != SW_AES256_KEY_SIZE)
    {
        LOGE("%s : Invalid argument.", __func__);
        return ERR_TA_INVALID_ARGUMENT;
    }

    // Input IOVEC.
    ioVecIn.size = 1;
    ioVecIn.iov = &IovecIn;
    ioVecIn.iov[0].pvBase = (void *)inData;

    // Output IOVEC.
    ioVecOut.size = 1;
    ioVecOut.iov = &IovecOut;
    ioVecOut.iov[0].pvBase = (void *)outData;

    switch(mode)
    {
        case AES_MODE_ENCRYPT :
            if((*outDataLen > AES_GCM_TAG_SIZE) && (*outDataLen - AES_GCM_TAG_SIZE < inDataLen ))
            {
                LOGE("too small buffer size - %d %d.", inDataLen, *outDataLen);
                return ERR_TA_BUFFER_OVERFLOW;
            }

            ioVecIn.iov[0].dwLen = inDataLen;
            ioVecOut.iov[0].dwLen = inDataLen;
            dir = SW_CIPHER_ENCRYPT;
            pGcmTag = outData + inDataLen;
            *outDataLen = inDataLen + AES_GCM_TAG_SIZE;
            break;

        case AES_MODE_DECRYPT :
            if((inDataLen > AES_GCM_TAG_SIZE) && (*outDataLen < inDataLen - AES_GCM_TAG_SIZE))
            {
                LOGE("too small buffer size - %d %d.", inDataLen, *outDataLen);
                return ERR_TA_BUFFER_OVERFLOW;
            }

            ioVecIn.iov[0].dwLen = inDataLen - AES_GCM_TAG_SIZE;
            ioVecOut.iov[0].dwLen = inDataLen - AES_GCM_TAG_SIZE;
            dir = SW_CIPHER_DECRYPT;
            pGcmTag = (uint8_t *)inData + inDataLen - AES_GCM_TAG_SIZE;
            *outDataLen = inDataLen - AES_GCM_TAG_SIZE;
            break;

        default :
            LOGE("Unsupported mode - %d.", mode);
            return ERR_TA_INVALID_ARGUMENT;
    }

    if((ret = qsee_SW_Cipher_Init(&cntx, SW_CIPHER_ALG_AES256)) != NOT_ERROR)
    {
        LOGE("Failed to qsee cipher init with error %d.", ret);
        return ERR_TA_QSEE_BASE - ret;
    }

    if((ret = qsee_SW_Cipher_SetParam(cntx, SW_CIPHER_PARAM_DIRECTION, &dir, sizeof(dir))) != NOT_ERROR)
    {
        LOGE("Failed to qsee set param of direction with error %d.", ret);
        ret = ERR_TA_QSEE_BASE - ret;
        goto cleanup;
    }

    if((ret = qsee_SW_Cipher_SetParam(cntx, SW_CIPHER_PARAM_MODE, &cipherMode, sizeof(cipherMode))) != NOT_ERROR)
    {
        LOGE("Failed to qsee set param of mode with error %d.", ret);
        ret = ERR_TA_QSEE_BASE - ret;
        goto cleanup;
    }

    if((ret = qsee_SW_Cipher_SetParam(cntx, SW_CIPHER_PARAM_KEY, key, keyLen)) != NOT_ERROR)
    {
        LOGE("Failed to qsee set param of key with error %d.", ret);
        ret = ERR_TA_QSEE_BASE - ret;
        goto cleanup;
    }

    if((ret = qsee_SW_Cipher_SetParam(cntx, SW_CIPHER_PARAM_IV, iv, AES_GCM_IV_SIZE)) != NOT_ERROR)
    {
        LOGE("Failed to qsee set param of iv with error %d.", ret);
        ret = ERR_TA_QSEE_BASE - ret;
        goto cleanup;
    }

    if((ret = qsee_SW_Cipher_SetParam(cntx, SW_CIPHER_PARAM_AAD, aesGcmAAD, sizeof(aesGcmAAD))) != NOT_ERROR)
    {
        LOGE("Failed to qsee set param of aad with error %d.", ret);
        ret = ERR_TA_QSEE_BASE - ret;
        goto cleanup;
    }

    if(dir == SW_CIPHER_DECRYPT)
    {
        if((ret = qsee_SW_Cipher_SetParam(cntx, SW_CIPHER_PARAM_TAG, pGcmTag, AES_GCM_TAG_SIZE)) != NOT_ERROR)
        {
            LOGE("Failed to qsee set param of tag with error %d.", ret);
            ret = ERR_TA_QSEE_BASE - ret;
            goto cleanup;
        }
    }

    if((ret = qsee_SW_CipherData(cntx, ioVecIn, &ioVecOut)) != NOT_ERROR)
    {
        LOGE("Failed to qsee cipherData with error %d.", ret);
        ret = ERR_TA_QSEE_BASE - ret;
        goto cleanup;
    }

    if(dir == SW_CIPHER_ENCRYPT)
    {
        if((ret = qsee_SW_Cipher_GetParam(cntx, SW_CIPHER_PARAM_TAG, pGcmTag, AES_GCM_TAG_SIZE)) != NOT_ERROR)
        {
            LOGE("Failed to qsee get param of tag with error %d.", ret);
            ret = ERR_TA_QSEE_BASE - ret;
        }
    }

cleanup :
    if(cntx)
    {
        if((cleanRet = qsee_SW_Cipher_DeInit(&cntx, SW_CIPHER_ALG_AES256)) != NOT_ERROR)
            LOGE("Failed to qsee cipher deInit with error %d.", cleanRet);

        cntx = NULL;
    }

    return ret;
}
#endif // End of !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;
#if (defined USE_QSEE_SFS)
    int32_t fd = 0;
    char fileDir[MAX_FILE_PATH_LEN];
#else
    uint8_t key[SW_AES256_KEY_SIZE], iv[SW_AES_IV_SIZE];
#endif // End of USE_QSEE_SFS

    if(inData == NULL || inDataLen == 0 || outData == NULL || outDataLen == NULL)
    {
        LOGE("%s : Invalid argument.", __func__);
        return ERR_TA_INVALID_ARGUMENT;
    }

#if (defined USE_QSEE_SFS)
    // qsee_sfs_mkdir has been deprecated at latest QSEE. This code is for old model.
    memset(fileDir, 0, sizeof(fileDir));
    memcpy(fileDir, outData, *outDataLen);

    while(fileDir[--*outDataLen] != '/' && *outDataLen > 0)
        fileDir[*outDataLen] = 0;

    fileDir[*outDataLen] = 0;

    if(*outDataLen != 0)
    {
        if(qsee_sfs_mkdir(fileDir) != SFS_NO_ERROR)
        {
            ret = qsee_sfs_error(fd);
            LOGE("Failed to make SFS dir with error %d.", ret);
            return ERR_TA_QSEE_BASE - ret;
        }
    }

    if((fd = qsee_sfs_open((char *)outData, 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;
    }

#else

    if(*outDataLen < inDataLen + sizeof(iv))
    {
        LOGE("Too small buffer - %d %d.", inDataLen, *outDataLen);
        return ERR_TA_BUFFER_OVERFLOW;
    }

    memset(iv, 0, sizeof(iv));
    memset(key, 0, sizeof(key));
    memset(outData, 0, *outDataLen);

    if((ret = getRandBlock(iv, sizeof(iv))) != sizeof(iv))
    {
        LOGE("Failed to get iv with error %d.", ret);
        return ret;
    }

    if((ret = getDerivedKey((void *)key, sizeof(key))) != NOT_ERROR)
    {
        LOGE("Failed to derive key with error %d.", ret);
        return ret;
    }

    if((ret = aes256GcmEncrypt(inData, inDataLen, iv, outData, outDataLen, key, sizeof(key), AES_MODE_ENCRYPT)) == NOT_ERROR)
    {
        memcpy(outData + *outDataLen, iv, sizeof(iv));
        *outDataLen += sizeof(iv);
    }
    else
    {
        LOGE("Failed to make local secure object with error %d.", ret);
    }

    memset(key, 0, sizeof(key));

#endif  // End of USE_QSEE_SFS
    return ret;
}

int32_t openLocalSecureObject(const uint8_t *inData, const uint32_t inDataLen, uint8_t *outData, uint32_t *outDataLen)
{
    int32_t ret = NOT_ERROR;
#if (defined USE_QSEE_SFS)
    int32_t fd = 0;
    uint32_t sfsSize = 0;
#else
    uint8_t key[SW_AES256_KEY_SIZE];
    uint8_t *pIv = (uint8_t *)inData + inDataLen - SW_AES_IV_SIZE;
#endif  // End of USE_QSEE_SFS

    if(inData == NULL || inDataLen == 0 || outData == NULL || outDataLen == NULL)
    {
        LOGE("%s : Invalid argument.", __func__);
        return ERR_TA_INVALID_ARGUMENT;
    }

#if (defined USE_QSEE_SFS)
    if((fd = qsee_sfs_open((char *)inData, 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;

#else
    if(*outDataLen < inDataLen - SW_AES_IV_SIZE)
    {
        LOGE("too small buffer - %d %d.", inDataLen, *outDataLen);
        return ERR_TA_BUFFER_OVERFLOW;
    }

    memset(key, 0, sizeof(key));
    memset(outData, 0, *outDataLen);

    if((ret = getDerivedKey((void *)key, sizeof(key))) != NOT_ERROR)
    {
        LOGE("Failed to derive key with error %d.", ret);
        return ret;
    }

    if((ret = aes256GcmEncrypt(inData, inDataLen - SW_AES_IV_SIZE, pIv, outData, outDataLen, key, sizeof(key), AES_MODE_DECRYPT)) != NOT_ERROR)
    {
        LOGE("Failed to make local secure object with error %d.", ret);
    }

    memset(key, 0, sizeof(key));
#endif  // End of USE_QSEE_SFS

    return ret;
}
