/*
 * =====================================================================================
 *
 *       Filename:  bfCryptoApi.c
 *
 *    Description:  APIs combined with blowfish APIs.
 *
 *        Version:  1.0
 *        Created:  05/29/2017 04:53:59 PM
 *       Compiler:  armcc
 *
 *         Author:  Dongwook Shim (), dw.shim@samsung.com
 *        Company:  Samsung Electronics
 *
 *        Copyright (c) 2017 by Samsung Electronics, All rights reserved.
 *
 * =====================================================================================
 */

#include <string.h>

#include "commonConfig.h"
#include "log.h"
#include "teeCryptoApi.h"
#include "sha/sha.h"
#include "PlatformConfig.h"

#include <tee_internal_api.h>
#include <tees_secure_object.h>

#if (TEEGRIS_SDK_VER == 3)
#define SO_HEADER_LEN       SO_HEADER_SIZE(1)
#elif (TEEGRIS_SDK_VER >= 4)
#define SO_HEADER_LEN       SO_HEADER_SIZE_STATIC
#else
#define SO_HEADER_LEN       SO_HEADER_SIZE(1,1)
#endif

#define STR_AUTH_ID_SAMSUNG_TA   "samsung_ta"
#define STR_AUTH_ID_SAMSUNG_DRV  "samsung_drv"
#define STR_AUTH_ID_TMF_DRV      "tmf_drv"

#ifndef SO_TA_ID_LEN
#define SO_TA_ID_LEN 16
#endif

#if (TEEGRIS_SDK_VER >= 4)

struct AUTH_ID
{
    uint8_t TID[SO_TA_ID_LEN];
    char auth_name[AUTHORITY_NAME_LEN];
};

static void setAuthId(char * acInfo, const uint8_t *uid, const uint32_t uidLen){
    int index = 0;
	const struct AUTH_ID authIdTbl[] =
	{
	      {TID_ICCC,STR_AUTH_ID_SAMSUNG_DRV},
	      {TID_TMFS,STR_AUTH_ID_TMF_DRV},
	};

    if(acInfo == NULL || uid == NULL)
    {
        LOGE("%s : Invalid case.", __func__);
        return;
    }

    for(index = 0; index < ARRAY_SIZE(authIdTbl); index++) {
        if(memcmp(uid, authIdTbl[index].TID, (uidLen < SO_TA_ID_LEN) ? uidLen : SO_TA_ID_LEN) == 0){
	      memset(acInfo, 0, AUTHORITY_NAME_LEN);
	      memcpy(acInfo, authIdTbl[index].auth_name, strlen(authIdTbl[index].auth_name)<AUTHORITY_NAME_LEN ?strlen(STR_AUTH_ID_SAMSUNG_DRV): AUTHORITY_NAME_LEN);
	      return;
        }
    }
    return;
}
#endif

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;
    SO_AccessControlInfoType aci = { DELEGATED_TA_ID_AC, {0,}, STR_AUTH_ID_SAMSUNG_TA };

    if(inData == NULL || outData == NULL || outDataLen == 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[72] + inDataLen).
    if(*outDataLen <= SO_HEADER_LEN || inDataLen > *outDataLen - SO_HEADER_LEN) {
        return ERR_TA_BUFFER_OVERFLOW;
    }

    if(targetUid != NULL && targetUidLen != 0)
    {
        memcpy(&aci.ta_id, targetUid, (targetUidLen < SO_TA_ID_LEN) ? targetUidLen : SO_TA_ID_LEN);
#if (TEEGRIS_SDK_VER >= 4)
        setAuthId((char *)&aci.auth_id.id,targetUid, targetUidLen);
#endif
    }
    else
    {
        LOGE("Invalid targetUid.");
        return ERR_TA_INVALID_ARGUMENT;
    }

    if((ret = TEES_WrapSecureObject(inData, inDataLen, outData, outDataLen, &aci)) != TEE_SUCCESS)
    {
        LOGE("Failed to create secure object with error 0x%X.", ret);
        ret = ERR_TA_BF_BASE - (ret & 0xFFFF);
    }

    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;
#if (TEEGRIS_SDK_VER >= 3)
    SO_AccessControlInfoType ac = { DELEGATED_TA_ID_AC, {0,}, STR_AUTH_ID_SAMSUNG_TA };
    const uint8_t tid[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x4b, 0x4d};
#endif

    if(inData == NULL || outData == NULL || outDataLen == NULL || targetUid == NULL)
    {
        LOGE("%s : Invalid argument.", __func__);
        return ERR_TA_INVALID_ARGUMENT;
    }

#if (TEEGRIS_SDK_VER >= 3)
    memcpy(&ac.ta_id, targetUid, (targetUidLen > SO_TA_ID_LEN)? SO_TA_ID_LEN : targetUidLen);
    if((ret = TEES_CheckSecureObjectCreator((char *)inData, inDataLen, &ac) != TEE_SUCCESS))
    {
        memset(&ac.ta_id, 0, sizeof(ac.ta_id));
        memcpy(&ac.ta_id, tid, (sizeof(tid) > SO_TA_ID_LEN)? SO_TA_ID_LEN : sizeof(tid));
        if((ret = TEES_CheckSecureObjectCreator((char *)inData, inDataLen, &ac) != TEE_SUCCESS))
        {
            LOGE("Invalid secure object creator with error 0x%X", ret);
            return ERR_TA_BF_BASE - (ret & 0xFFFF);
        }
    }
#endif

    if((ret = TEES_UnwrapSecureObject((void *)inData, inDataLen, outData, outDataLen)) != TEE_SUCCESS)
    {
        LOGE("Failed to open secure object with error 0x%X.", ret);
        ret = ERR_TA_BF_BASE - (ret & 0xFFFF);
    }

    return ret;
}

int32_t getRandBlock(uint8_t *randomBuffer, uint32_t randomLen)
{
    if(randomBuffer == NULL || randomLen == 0)
    {
        LOGE("%s : Invalid argument.", __func__);
        return ERR_TA_INVALID_ARGUMENT;
    }

    TEE_GenerateRandom(randomBuffer, randomLen);

    return randomLen;
}

int32_t getShaDigest(const uint8_t *msg, size_t msgLen, uint8_t *digest, size_t digestLen, DigestAlgo_t algo)
{
    int32_t ret = NOT_ERROR;
    size_t reqDigestLen = digestLen;
    TEE_OperationHandle teeOpHandle;
    TEE_OperationMode hashAlgo;

    if(msg == NULL || msgLen == 0 || digest == NULL)
    {
        LOGE("%s : Invalid argument.", __func__);
        return ERR_TA_INVALID_ARGUMENT;
    }

    switch(algo)
    {
        case  ALGO_SHA1 :
            if(digestLen != SHA_DIGEST_LENGTH)
            {
                LOGE("Digest buffer is not matched - %zu.", digestLen);
                return ERR_TA_INVALID_ARGUMENT;
            }
            hashAlgo = TEE_ALG_SHA1;
            break;

        case ALGO_SHA256 :
            if(digestLen != SHA256_DIGEST_LENGTH)
            {
                LOGE("Digest buffer is not matched - %zu.", digestLen);
                return ERR_TA_INVALID_ARGUMENT;
            }
            hashAlgo = TEE_ALG_SHA256;
            break;

        default :
            LOGE("Not supported algothim : %d.", algo);
            return ERR_TA_INVALID_ARGUMENT;
    }

    if((ret = TEE_AllocateOperation(&teeOpHandle, hashAlgo, TEE_MODE_DIGEST, 0)) != TEE_SUCCESS)
    {
        LOGE("Failed to allocate operation with error 0x%X.", ret);
        return ERR_TA_BF_BASE - (ret & 0xFFFF);
    }

    if((ret = TEE_DigestDoFinal(teeOpHandle, msg, msgLen, digest, &digestLen)) != TEE_SUCCESS)
    {
        LOGE("Failed to make digest with error 0x%X.", ret);
        ret = ERR_TA_BF_BASE - (ret & 0xFFFF);
    } else {
        if(reqDigestLen != digestLen)
        {
            LOGE("Digest size is not matched. %zu.", reqDigestLen);
            ret =  ERR_TA_GEN_HASH_FAILED;
        }
    }

    TEE_FreeOperation(teeOpHandle);

    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;
    TEE_ObjectHandle teeObjHandle;
    TEE_OperationHandle teeOpHandle;
    TEE_Attribute teeAttrs;
    TEE_OperationMode cipherMode;

    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 :
            cipherMode = TEE_MODE_ENCRYPT;
            break;

        case AES_MODE_DECRYPT :
            cipherMode = TEE_MODE_DECRYPT;
            break;

        default :
            LOGE("Unsupported mode - %d.", mode);
            return ERR_TA_INVALID_ARGUMENT;
    }

    if((ret = TEE_AllocateTransientObject(TEE_TYPE_AES, keyLen * 8, &teeObjHandle)) != TEE_SUCCESS)
    {
        LOGE("Failed to allocate object with error 0x%X.", ret);
        return ERR_TA_BF_BASE - (ret & 0xFFFF);
    }

    TEE_InitRefAttribute(&teeAttrs, TEE_ATTR_SECRET_VALUE, key, keyLen);

    if((ret = TEE_PopulateTransientObject(teeObjHandle, &teeAttrs, 1)) != TEE_SUCCESS)
    {
        LOGE("Failed to populate object with error 0x%X.", ret);
        TEE_FreeTransientObject(teeObjHandle);
        return ERR_TA_BF_BASE - (ret & 0xFFFF);
    }

    if((ret = TEE_AllocateOperation(&teeOpHandle, TEE_ALG_AES_CBC_PKCS7, cipherMode, keyLen * 8)) != TEE_SUCCESS)
    {
        LOGE("Failed to allocate operation with error 0x%X.", ret);
        ret = ERR_TA_BF_BASE - (ret & 0xFFFF);
        goto cleanup;
    }

    if((ret = TEE_SetOperationKey(teeOpHandle, teeObjHandle)) != TEE_SUCCESS)
    {
        LOGE("Failed to set op key with error 0x%X.", ret);
        ret = ERR_TA_BF_BASE - (ret & 0xFFFF);
        goto cleanup;
    }

    TEE_CipherInit(teeOpHandle, iv, IV_SIZE);

    if((ret = TEE_CipherDoFinal(teeOpHandle, inData, inDataLen, outData, outDataLen)) != TEE_SUCCESS)
    {
        LOGE("Failed to do final operation with error 0x%X.", ret);
        ret = ERR_TA_BF_BASE - (ret & 0xFFFF);
    }

cleanup :
    TEE_FreeTransientObject(teeObjHandle);
    TEE_FreeOperation(teeOpHandle);

    return ret;
}
