/**
 * \file CryptoPlatformQSEE.c
 * \brief QSEE related high level crypto functions.
 * \author Dmytro Podgornyi (d.podgornyi@samsung.com)
 * \version 0.1
 * \date Created May 28, 2013
 * \par In Samsung Ukraine R&D Center (SURC) under a contract between
 * \par LLC "Samsung Electronics Ukraine Company" (Kiev, Ukraine) and
 * \par "Samsung Elecrtronics Co", Ltd (Seoul, Republic of Korea)
 * \par Copyright: (c) Samsung Electronics Co, Ltd 2012. All rights reserved.
 **/

#include <ctype.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "CryptoPlatform.h"
#include "CommLayerData.h"
#include "ServiceName.h"
#include "SfsFileOperations.h"
#include "log.h"
#include "com_dtypes.h"

#include "qsee_message.h"
#include "qsee_hash.h"
#include "qsee_prng.h"
#include "qsee_fuse.h"

#ifndef USE_QSEE_WRAP_WITH_SFS
#include "qsee_heap.h"
#include "qsee_kdf.h"
#include "qsee_cipher.h"

const char mldap_key_label[] = {"mldap"};
const char mldap_key_context[] = {"mldap"};

uint8_t mldap_iv[] =
{
	0x00, 0x0B, 0x18, 0x53, 0x52, 0x23, 0xA5, 0x92,
	0x54, 0x62, 0xA1, 0x95, 0x3F, 0xD0, 0xA3, 0x6A
};
#endif

#if ((SHA256_DIGEST_LEN) != (QSEE_SHA256_HASH_SZ))
#error SHA256_DIGEST_LEN != QSEE_SHA256_HASH_SZ
#endif

/* TODO: move to CommonLayerData.h */
#define MAX_FILE_PATH_LEN 256
#define HEADER_AND_MAC_LEN 116   // Must leave at least 144 extra bytes for the header and MAC, as per the API spec.

const char sfs_apcs_file[] = "/efs/prov_data/dev_root/dev_root.dat";
const char sfs_symm_file[] = "/efs/prov_data/dev_root/sym_key.dat";
const char sfs_mftr_file[] = "/efs/prov_data/dev_root/mftr_root.dat";

int32_t getSHA1Digest(uint8_t* data, uint32_t len, uint8_t* digest)
{
	int32_t res = PLATFORM_INTERNAL_ERROR;
	uint8_t tmp_digest[QSEE_SHA1_HASH_SZ] = {0};

	res = qsee_hash(QSEE_HASH_SHA1, data, len, tmp_digest, sizeof(tmp_digest));
	if (res != 0) {
		memset(tmp_digest, 0, sizeof(tmp_digest));
		return PLATFORM_INTERNAL_ERROR;
	}

	memcpy(digest, tmp_digest, QSEE_SHA1_HASH_SZ);
	memset(tmp_digest, 0, sizeof(tmp_digest));
	return NO_ERROR;
}

int32_t getSHA256Digest(uint8_t* data, uint32_t len, uint8_t* digest)
{
	int32_t res = PLATFORM_INTERNAL_ERROR;
	uint8_t tmp_digest[QSEE_SHA256_HASH_SZ] = {0};

	res = qsee_hash(QSEE_HASH_SHA256, data, len, tmp_digest, sizeof(tmp_digest));
	if (res != 0) {
		memset(tmp_digest, 0, sizeof(tmp_digest));
		return PLATFORM_INTERNAL_ERROR;
	}

	memcpy(digest, tmp_digest, QSEE_SHA256_HASH_SZ);
	memset(tmp_digest, 0, sizeof(tmp_digest));
	return NO_ERROR;
}

int32_t getRandBlock(uint8_t *buf, uint32_t len)
{
	if (qsee_prng_getdata(buf, len) != len) {
		return PLATFORM_INTERNAL_ERROR;
	}

	return NO_ERROR;
}

#ifndef USE_QSEE_WRAP_WITH_SFS

// Wrap Service Key without SFS
int32_t wrapWithoutSFS(void *inData, uint32_t inDataLen, void *outData, uint32_t *outDataLen){
	int32_t ret = NO_ERROR;
	uint8_t *ct_tmp = 0;
	uint32_t ct_len = MAX_TRANSFER_SIZE;
	uint32_t tmp_size;
	uint8_t mldap_enc_key[KEK_LENGTH];

	qsee_cipher_ctx *ctx = 0;
	QSEE_CIPHER_ALGO_ET  alg = QSEE_CIPHER_ALGO_AES_256;
	QSEE_CIPHER_MODE_ET  mode = QSEE_CIPHER_MODE_CBC;

	// Derive encryption key by KDF
	ret = qsee_kdf(NULL, 32, (void*)mldap_key_label, strlen(mldap_key_label), (void*)mldap_key_context, strlen(mldap_key_context), mldap_enc_key, KEK_LENGTH);
	if (ret != NO_ERROR) {
		LOGE("qdee_kdf failed. ret = %d", ret);
	}

	// Init cipher context
	ret = qsee_cipher_init(alg, &ctx);
	if (ret != NO_ERROR) {
		LOGE("qsee_cipher_init API failed. ret = %d", ret);
		goto handleError;
	}

	// Set key for encryption
	ret = qsee_cipher_set_param(ctx, QSEE_CIPHER_PARAM_KEY, mldap_enc_key, KEK_LENGTH);
	if (ret != NO_ERROR) {
		LOGE("qsee_cipher_set_param API failed. ret = %d", ret);
		goto handleError;
	}

	// Set AES mode
	ret = qsee_cipher_set_param(ctx, QSEE_CIPHER_PARAM_MODE, &mode, sizeof(mode));
	if (ret != NO_ERROR) {
		LOGE("qsee_cipher_set_param API failed. ret = %d", ret);
		goto handleError;
	}

	// Set IV
	ret = qsee_cipher_set_param(ctx, QSEE_CIPHER_PARAM_IV, mldap_iv, QSEE_AES256_IV_SIZE);
	if (ret != NO_ERROR) {
		LOGE("qsee_cipher_set_param API failed. ret = %d", ret);
		goto handleError;
	}

	tmp_size = (inDataLen/16+1)*16;
	// Allocate temp ct buffer
	ct_tmp = qsee_malloc(tmp_size);
	if (ct_tmp == NULL) {
		LOGE("Failed to allocate Ciphertext buffer.");
		goto handleError;
	}
	// Clear ciphettext buffer
	memset(ct_tmp, 0, tmp_size);

	// Padding 
	memset((uint8_t*)inData + inDataLen, 0, tmp_size - inDataLen);

	// encrypt key blob by encryption key
	ret = qsee_cipher_encrypt(ctx, inData, tmp_size, ct_tmp, (uint32*)&ct_len);
	if (ret != NO_ERROR) {
		LOGE("qsee_cipher_encrypt API failed. ret = %d", ret);
		goto handleError;
	}

	if (ct_len > (MAX_TRANSFER_SIZE - sizeof(inDataLen))) {
		LOGE("Wrapdata len is invalid\n");
		goto handleError;
	}
	memcpy(outData, &inDataLen, sizeof(inDataLen));
	memcpy((uint8_t*)outData + sizeof(inDataLen), ct_tmp, ct_len);
	*outDataLen = ct_len + sizeof(inDataLen);

handleError:
	// Free cipher context
	if (ctx) {
		if (qsee_cipher_free_ctx(ctx) != NO_ERROR) {
			LOGE("qsee_cipher_free_ctx API failed. ret = %d", ret);
		}
	}
	if (ct_tmp) {
		qsee_free(ct_tmp);
		ct_tmp = 0;
	}
	return ret;
}

// unwrap Service Key without SFS
int32_t unwrapWithoutSFS(void *inData, uint32_t inDataLen, void *outData, uint32_t *outDataLen)
{
	int32_t ret = NO_ERROR;
	uint8_t mldap_enc_key[KEK_LENGTH];
	uint32_t real_len = MAX_TRANSFER_SIZE;
	uint8_t pt_tmp[MAX_TRANSFER_SIZE] = {0};
	uint32_t pt_len = MAX_TRANSFER_SIZE;
	qsee_cipher_ctx *ctx = 0;
	QSEE_CIPHER_ALGO_ET  alg = QSEE_CIPHER_ALGO_AES_256;
	QSEE_CIPHER_MODE_ET  mode = QSEE_CIPHER_MODE_CBC;

	// Derive encryption key by KDF
	ret = qsee_kdf(NULL, 32, (void*)mldap_key_label, strlen(mldap_key_label), (void*)mldap_key_context, strlen(mldap_key_context), mldap_enc_key, KEK_LENGTH);
	if (ret != NO_ERROR) {
		LOGE("qdee_kdf failed. ret = %d", ret);
	}

	// Init cipher context
	ret = qsee_cipher_init(alg, &ctx);
	if (ret != NO_ERROR) {
		LOGE("qsee_cipher_init API failed. ret = %d", ret);
		goto handleError;
	}

	// Set key for encryption
	ret = qsee_cipher_set_param(ctx, QSEE_CIPHER_PARAM_KEY, mldap_enc_key, KEK_LENGTH);
	if (ret != NO_ERROR) {
		LOGE("qsee_cipher_set_param API failed. ret = %d", ret);
		goto handleError;
	}

	// Set AES mode
	ret = qsee_cipher_set_param(ctx, QSEE_CIPHER_PARAM_MODE, &mode, sizeof(mode));
	if (ret != NO_ERROR) {
		LOGE("qsee_cipher_set_param API failed. ret = %d", ret);
		goto handleError;
	}

	// Set IV
	ret = qsee_cipher_set_param(ctx, QSEE_CIPHER_PARAM_IV, mldap_iv, QSEE_AES256_IV_SIZE);
	if (ret != NO_ERROR) {
		LOGE("qsee_cipher_set_param API failed. ret = %d", ret);
		goto handleError;
	}
	memcpy(&real_len, inData, sizeof(real_len));
	// decrypt encrypted key
	ret = qsee_cipher_decrypt(ctx, (uint8*)inData + sizeof(real_len), inDataLen - sizeof(real_len), pt_tmp, (uint32*)&pt_len);
	if (ret != NO_ERROR) {
		LOGE("qsee_cipher_decrypt API failed. ret = %d", ret);
		goto handleError;
	}

	if (real_len > MAX_TRANSFER_SIZE) {
		LOGE("Unwrapdata len is invalid\n");
		goto handleError;
	}
	memcpy(outData, pt_tmp, real_len);
	*outDataLen = real_len;
handleError:
	// Free cipher context
	if (ctx) {
		if (qsee_cipher_free_ctx(ctx) != NO_ERROR) {
			LOGE("qsee_cipher_free_ctx API failed. ret = %d", ret);
		}
	}
	return ret;
}
#endif

int32_t loadMLDAPKeyBlob(uint8_t* wrapped, uint32_t wrappedLen, KeyInfo_t* keyInfo, uint8_t* keyBlob, uint32_t* keyBlobLen)
{
	int32_t res;
	const char *path = NULL;
	uint32_t fileSize = 0;

	LOGD("keyInfo->serviceName = %s", keyInfo->serviceName);
	path = serviceNameGetPath((const char*)keyInfo->serviceName);
	if (!path) {
		LOGE("wrong serviceName: %s", keyInfo->serviceName);
		return WRONG_DATA;
	}

#ifdef USE_QSEE_WRAP_WITH_SFS
	res = getKeySize(path, &fileSize);
	if (res != NO_ERROR) {
		LOGE("Can not get size of file");
		return res;
	}

	res = readKeyFromSFS(path, keyBlob, fileSize, 0);
#else
	res = unwrapWithoutSFS(wrapped, wrappedLen, keyBlob, keyBlobLen);
#endif
	if (res != NO_ERROR) {
		LOGE("Can not read file");
		return res;
	}
#ifdef USE_QSEE_WRAP_WITH_SFS
	*keyBlobLen = fileSize;
#endif
	return res;
}

int32_t loadSKMKeyBlob(uint8_t* wrapped, uint32_t wrappedLen, KeyInfo_t* keyInfo, uint8_t* keyBlob, uint32_t* keyBlobLen)
{
	int32_t res = PLATFORM_INTERNAL_ERROR;

	char tlName[] = MLDAP_TL_NAME;
	(void)keyInfo;

	if (wrappedLen > *keyBlobLen) {
		LOGE("Wrong size of data to unwrap");
		return WRONG_DATA;
	}

	LOGD("tlName = %s, wrappedLen = %d", tlName, wrappedLen);

	/* Make Decapsulation */
	res = qsee_decapsulate_inter_app_message(tlName, wrapped, wrappedLen, keyBlob, (uint32*)keyBlobLen);
	if (res != NO_ERROR) {
		LOGE("qsee_decapsulate_inter_app_message failed: %d", res);
		return res;
	}

	return res;
}

int32_t saveKeyBlob(uint8_t* keyBlob, uint32_t keyBlobLen, KeyInfo_t* keyInfo, uint8_t* wrappedOut, uint32_t* wrappedOutLen, uint8_t* TID, uint32_t TIDLen)
{
	const char *path = NULL;
	int32_t res = PLATFORM_INTERNAL_ERROR;

	(void)wrappedOut;
	*wrappedOutLen = 0;
	(void)TID;
	(void)TIDLen;

	LOGD("keyInfo->serviceName = %s", keyInfo->serviceName);
	path = serviceNameGetPath((const char*)keyInfo->serviceName);
	if (!path) {
		LOGE("wrong serviceName");
		return WRONG_DATA;
	}

#ifdef USE_QSEE_WRAP_WITH_SFS
	res = saveKeyToSFS(path, keyBlob, keyBlobLen, NULL, 0);
#else
	res = wrapWithoutSFS(keyBlob, keyBlobLen, wrappedOut, wrappedOutLen);
#endif
	if (NO_ERROR != res) {
		LOGE("Failed to save generated service key pair to SFS! Error code: %d", res);
		return res;
	}

	return NO_ERROR;
}

/* Get OEM flag for integrity check */
int32_t getOemFlag(void)
{
	int32_t  res = PLATFORM_INTERNAL_ERROR;
	int ret_dr = 0;
	boolean isblown = 0;

	//Integrity Check
	ret_dr = qsee_is_sw_fuse_blown(QSEE_HLOS_IMG_TAMPER_FUSE, &isblown, sizeof(boolean));
	if (ret_dr) {
		LOGE("sec_get_oem_flag error. [return value] = %d", ret_dr);
		res = GET_OEM_FLAG_ERROR;
		goto cleanup;
	}

	if (isblown == 1)
		res = INTEGRITY_ERROR;
	else
		res = NO_ERROR;

cleanup:
	return res;
}