/**
* \file CryptoPlatform.c
* \brief Platform independent high level crypto functions.
* \author Nikolay Oleschuk (n.oleschuk@samsung.com)
* \version 0.1
* \date Created March 03, 2014
* \par In Samsung Ukraine R&D Center (SRK) under a contract between
* \par LLC "Samsung Electronics Ukraine Company" (Kharkiv, Ukraine) and
* \par "Samsung Elecrtronics Co", Ltd (Seoul, Republic of Korea)
* \par Copyright: (c) Samsung Electronics Co, Ltd 2014. All rights reserved.
**/

#include <string.h>

#include "TAStartupCode.h"
#include "Synchronization.h"
#include "log.h"
#include "TLV.h"
#include "Utils.h"

int32_t kmLockAndLoadTA(void)
{
	int32_t res = PLATFORM_INTERNAL_ERROR;

#if (!defined(NWD_SYNC_DRIVER) || NWD_SYNC_DRIVER == NWD_SYNC_DRIVER_NONE)
	res = kmLoadTA();
	if (NO_ERROR != res) {
		LOGE("Loading Trustlet Application failed! Error code : %d", res);
	}
#else /* NWD_SYNC_DRIVER */
	res = kmLockTA();
	if (NO_ERROR != res) {
		LOGE("Locking failed! Error code: %d", res);
		return res;
	}

	res = kmLoadTA();
	if (NO_ERROR != res) {
		LOGE("Loading Trustlet Application failed! Error code : %d", res);
		if (kmUnlockTA() != NO_ERROR) {
			LOGE("Unlocking failed!");
		}
	}
#endif /* NWD_SYNC_DRIVER */

	return res;
}

int32_t kmUnloadAndUnlockTA(void)
{
	int32_t res = PLATFORM_INTERNAL_ERROR;

	res = kmUnloadTA();
	if (NO_ERROR != res) {
		LOGE("Unloading Trustlet Application failed! Error code : %d", res);
		if ( kmUnlockTA() != NO_ERROR) {
			LOGE("Unlocking failed");
		}
		return res;
	}

	res = kmUnlockTA();
	if (NO_ERROR != res) {
		LOGE("Unlocking failed! Error code : %d", res);
		return PLATFORM_INTERNAL_ERROR;
	}

	return NO_ERROR;
}

int32_t kmSendCmd(uint32_t cmdId, uint8_t* data,  uint32_t len)
{
	return kmSendCmdInOut(cmdId, data, len, NULL, 0);
}

int32_t kmSendCmdGetOtaCertWithKey(certType_t certType, uint8_t *out, uint32_t *outLen, uint8_t *wrappedKey, uint32_t wrappedKeyLen)
{
	uint8_t buf[MAX_TRANSFER_SIZE];
	uint32_t bufLen = MAX_TRANSFER_SIZE;//sizeof(buf);
	int cmdId;

	if (!out || !outLen) {
		LOGE("Some argument is NULL\n");
		return WRONG_DATA;
	}

	if (tlvInit(buf, bufLen) != NO_ERROR) {
		LOGE("tlvInit(ptr, tlvLen) error");
		return PLATFORM_INTERNAL_ERROR;
	}

	if (tlvAdd(buf, bufLen, TLV_WRAPPED_KEY, wrappedKey, wrappedKeyLen) != NO_ERROR) {
		LOGE("tlvAdd(tlv, tlvLen, TLV_WRAPPED_KEY, wrappedKey, wrappedKeyLen) error");
		return PLATFORM_INTERNAL_ERROR;
	}

	bufLen = tlvSize(buf, bufLen);

	switch (certType)
	{
		case CERT_DRK:
			cmdId = OTA_GET_DRK_CERT_CMD;
			break;
		case CERT_ML:
			cmdId = OTA_GET_ML_CERT_CMD;
			break;
		case CERT_SM0:
			cmdId = DAP_GET_SM0_CERT_CMD;
			break;
		case CERT_SM1:
			cmdId = DAP_GET_SM1_CERT_CMD;
			break;
		case CERT_SD:
			cmdId = DAP_GET_SD_CERT_CMD;
			break;
		default:
			LOGE("Unknown cert type, should be either ML or DRK\n");
			return PLATFORM_INTERNAL_ERROR;
	}

	return kmSendCmdInOut(cmdId, buf, bufLen, out, outLen);
}

int32_t kmVerifySDCert(uint8_t* wrappedKey, uint32_t wrappedKeyLen, uint8_t* localTime, uint32_t localTimeLen)
{
	uint8_t buf[MAX_TRANSFER_SIZE];
	uint32_t bufLen = MAX_TRANSFER_SIZE;//sizeof(buf);

	if (!wrappedKey || !localTime || localTimeLen == 0) {
		LOGE("Some argument is NULL\n");
		return WRONG_DATA;
	}

	if (tlvInit(buf, bufLen) != NO_ERROR) {
		LOGE("tlvInit(ptr, tlvLen) error");
		return PLATFORM_INTERNAL_ERROR;
	}

	if (tlvAdd(buf, bufLen, TLV_WRAPPED_KEY, wrappedKey, wrappedKeyLen) != NO_ERROR) {
		LOGE("tlvAdd(tlv, tlvLen, TLV_WRAPPED_KEY, wrappedKey, wrappedKeyLen) error");
		return PLATFORM_INTERNAL_ERROR;
	}

	if (tlvAdd(buf, bufLen, TLV_TIMESTAMP, localTime, localTimeLen) != NO_ERROR) {
		LOGE("tlvAdd(tlv, tlvLen, TLV_WRAPPED_KEY, wrappedKey, wrappedKeyLen) error");
		return PLATFORM_INTERNAL_ERROR;
	}

	bufLen = tlvSize(buf, bufLen);

	return kmSendCmdInOut(OTA_VERIFY_SD_CERT_CMD, buf, bufLen, NULL, 0);
}

int32_t kmSendCmdGetOtaSdPk(uint8_t *out, uint32_t *outLen)
{
	uint8_t buf[MAX_TRANSFER_SIZE];
	uint32_t bufLen = MAX_TRANSFER_SIZE;//sizeof(buf);

	uint8_t* wrappedKey = NULL;
	uint32_t wrappedKeyLen = 0;

	uint8_t TID[MAX_TID_SIZE];
	uint32_t TIDLen = MAX_TID_SIZE;//sizeof(TID);

	KeyInfo_t ki;
	memcpy(ki.serviceName, "SD", sizeof("SD"));
	ki.keyLen = 2048;

	if (!out || !outLen) {
		LOGE("Some argument is NULL\n");
		return WRONG_DATA;
	}

	if (kmGetTid(TID, &TIDLen) != NO_ERROR) {
		LOGE("kmGetTid(TID, &TIDLen)");
		return PLATFORM_INTERNAL_ERROR;   
	}

	if (tlvInit(buf, bufLen) != NO_ERROR) {
		LOGE("tlvInit(ptr, tlvLen) error");
		return PLATFORM_INTERNAL_ERROR;
	}

	if (tlvAdd(buf, bufLen, TLV_KEY_INFO, &ki, sizeof(ki)) != NO_ERROR) {
		LOGE("tlvAdd(buf, bufLen, TLV_KEY_INFO, &ki, sizeof(ki)) error");
		return PLATFORM_INTERNAL_ERROR;
	}

	if (tlvAdd(buf, bufLen, TLV_WRAPPED_KEY, wrappedKey, wrappedKeyLen) != NO_ERROR) {
		LOGE("tlvAdd(tlv, tlvLen, TLV_WRAPPED_KEY, wrappedKey, wrappedKeyLen) error");
		return PLATFORM_INTERNAL_ERROR;
	}

	if (tlvAdd(buf, bufLen, TLV_TID, TID, TIDLen) != NO_ERROR) {
		LOGE("tlvAdd(buf, bufLen, TLV_TID, TID, TIDLen) error");
		return PLATFORM_INTERNAL_ERROR;
	}

	bufLen = tlvSize(buf, bufLen);

	return kmSendCmdInOut(OTA_GET_SD_PUB_KEY_CMD, buf, bufLen, out, outLen);
}

int32_t kmSendCmdSaveOtaCerts(uint8_t *certMlSD, uint32_t certMlSDLen, uint8_t *certMlSM0, uint32_t certMlSM0Len, uint8_t *certMlSM1, uint32_t certMlSM1Len, uint8_t *wrappedKey, uint32_t wrappedKeyLen, uint8_t *keyBlob, uint32_t *keyBlobLen)
{
	uint8_t buf[MAX_TRANSFER_SIZE];
	uint32_t bufLen = MAX_TRANSFER_SIZE;//sizeof(buf);

	uint8_t innerBuf[MAX_TRANSFER_SIZE];
	uint32_t innerBufLen = MAX_TRANSFER_SIZE;//sizeof(innerBuf);

	uint8_t TID[MAX_TID_SIZE];
	uint32_t TIDLen = MAX_TID_SIZE;//sizeof(TID);

	KeyInfo_t ki;
	memcpy(ki.serviceName, "SD", sizeof("SD"));
	ki.keyLen = 2048;

	if (!certMlSD || !certMlSM0 || !certMlSM1) {
		LOGE("Some argument is NULL\n");
		return WRONG_DATA;
	}

	if (kmGetTid(TID, &TIDLen) != NO_ERROR) {
		LOGE("kmGetTid(TID, &TIDLen)");
		return PLATFORM_INTERNAL_ERROR;   
	}

	if (tlvInit(buf, bufLen) != NO_ERROR) {
		LOGE("tlvInit(ptr, tlvLen) error");
		return PLATFORM_INTERNAL_ERROR;
	}

	if (tlvAdd(buf, bufLen, TLV_KEY_INFO, &ki, sizeof(ki)) != NO_ERROR) {
		LOGE("tlvAdd(buf, bufLen, TLV_KEY_INFO, &ki, sizeof(ki)) error");
		return PLATFORM_INTERNAL_ERROR;
	}

	if (tlvAdd(buf, bufLen, TLV_WRAPPED_KEY, wrappedKey, wrappedKeyLen) != NO_ERROR) {
		LOGE("tlvAdd(tlv, tlvLen, TLV_WRAPPED_KEY, wrappedKey, wrappedKeyLen) error");
		return PLATFORM_INTERNAL_ERROR;
	}

	if (tlvAdd(buf, bufLen, TLV_TID, TID, TIDLen) != NO_ERROR) {
		LOGE("tlvAdd(buf, bufLen, TLV_TID, TID, TIDLen) error");
		return PLATFORM_INTERNAL_ERROR;
	}

	// Put TLV inside TLV
	// and we need to go deeper
	// =========================================================
	if (tlvInit(innerBuf, innerBufLen) != NO_ERROR) {
		LOGE("tlvInit(innerBuf, innerBufLen)");
		return PLATFORM_INTERNAL_ERROR;
	}

	if (tlvAdd(innerBuf, innerBufLen, TLV_CERT_SM0, certMlSM0, certMlSM0Len) != NO_ERROR ||
		tlvAdd(innerBuf, innerBufLen, TLV_CERT_SM1, certMlSM1, certMlSM1Len) != NO_ERROR ||
		tlvAdd(innerBuf, innerBufLen, TLV_CERT_SD, certMlSD, certMlSDLen) != NO_ERROR) {
			LOGE("tlvAdd(ptr, tlvLen, TLV_SIGN_DATA_BLOB, data, dataLen) error");
			return PLATFORM_INTERNAL_ERROR;   
	}

	innerBufLen = tlvSize(innerBuf, innerBufLen);
	// =========================================================

	if (tlvAdd(buf, bufLen, TLV_ATTRS, innerBuf, innerBufLen) != NO_ERROR) {
		LOGE("tlvAdd(buf, bufLen, TLV_ATTRS, innerBuf, innerBufLen) error");
		return PLATFORM_INTERNAL_ERROR;
	}

	bufLen = tlvSize(buf, bufLen);
	return kmSendCmdInOut(OTA_STORE_ML_AND_SD_CERT_CMD, buf, bufLen, keyBlob, keyBlobLen);
}

int32_t kmSendCmdSignData(certType_t certType, uint8_t* wrappedKey, uint32_t wrappedKeyLen, uint8_t* data, uint32_t dataLen, uint8_t* signature, uint32_t* signatureLen)
{
	uint8_t buf[MAX_TRANSFER_SIZE];
	uint32_t bufLen = MAX_TRANSFER_SIZE; //sizeof(buf);

	uint8_t innerBuf[MAX_TRANSFER_SIZE];
	uint32_t innerBufLen = MAX_TRANSFER_SIZE;// sizeof(innerBuf);

	uint8_t TID[MAX_TID_SIZE];
	uint32_t TIDLen = MAX_TID_SIZE ;//sizeof(TID);

	char SERVICE_NAME_ML[] = "MLDAP";
	char SERVICE_NAME_SD[] = "SD";

	KeyInfo_t ki;
	ki.crt = certType;
	ki.keyLen = 2048;

	if (!data || !signature || !signatureLen) {
		LOGE("Some argument is NULL\n");
		return WRONG_DATA;
	}

	/* Get appropriate service name */
	switch (certType)
	{
		case CERT_SD:
			strncpy((char*)ki.serviceName, SERVICE_NAME_SD, sizeof(ki.serviceName));
			break;
		case CERT_ML:
			strncpy((char*)ki.serviceName, SERVICE_NAME_ML, sizeof(ki.serviceName));
			break;
		default:

		LOGE("Unknown cert type, should be either MLDAP or SD");
		return PLATFORM_INTERNAL_ERROR;
	}

	if (kmGetTid(TID, &TIDLen) != NO_ERROR) {
		LOGE("kmGetTid(TID, &TIDLen)");
		return PLATFORM_INTERNAL_ERROR;   
	}

	if (tlvInit(buf, bufLen) != NO_ERROR) {
		LOGE("tlvInit(ptr, tlvLen) error");
		return PLATFORM_INTERNAL_ERROR;
	}

	if (tlvAdd(buf, bufLen, TLV_KEY_INFO, &ki, sizeof(ki)) != NO_ERROR) {
		LOGE("tlvAdd(buf, bufLen, TLV_KEY_INFO, &ki, sizeof(ki)) error");
		return PLATFORM_INTERNAL_ERROR;
	}

	if (tlvAdd(buf, bufLen, TLV_WRAPPED_KEY, wrappedKey, wrappedKeyLen) != NO_ERROR) {
		LOGE("tlvAdd(tlv, tlvLen, TLV_WRAPPED_KEY, wrappedKey, wrappedKeyLen) error");
		return PLATFORM_INTERNAL_ERROR;
	}

	if (tlvAdd(buf, bufLen, TLV_TID, TID, TIDLen) != NO_ERROR) {
		LOGE("tlvAdd(buf, bufLen, TLV_TID, TID, TIDLen) error");
		return PLATFORM_INTERNAL_ERROR;
	}

	// Put TLV inside TLV
	// =========================================================
	if (tlvInit(innerBuf, innerBufLen) != NO_ERROR) {
		LOGE("tlvInit(innerBuf, innerBufLen)");
		return PLATFORM_INTERNAL_ERROR;
	}

	if (tlvAdd(innerBuf, innerBufLen, TLV_SIGN_DATA_BLOB, data, dataLen) != NO_ERROR) {
		LOGE("tlvAdd(innerBuf, innerBufLen, TLV_SIGN_DATA_BLOB, data, dataLen) error");
		return PLATFORM_INTERNAL_ERROR;
	}

	innerBufLen = tlvSize(innerBuf, innerBufLen);
	// =========================================================

	if (tlvAdd(buf, bufLen, TLV_ATTRS, innerBuf, innerBufLen) != NO_ERROR) {
		LOGE("tlvAdd(buf, bufLen, TLV_ATTRS, innerBuf, innerBufLen) error");
		return PLATFORM_INTERNAL_ERROR;
	}

	bufLen = tlvSize(buf, bufLen);
	return kmSendCmdInOut(SIGN_DATA_CMD, buf, bufLen, signature, signatureLen);
}


int32_t kmSendCmdSignOtaData(uint8_t* wrappedKey, uint32_t wrappedKeyLen, uint8_t* data, uint32_t dataLen, uint8_t* signature, uint32_t* signatureLen)
{
	return kmSendCmdSignData(CERT_ML, wrappedKey, wrappedKeyLen, data, dataLen, signature, signatureLen);
}

int32_t kmSendCmdSignDapData(uint8_t* wrappedKey, uint32_t wrappedKeyLen, uint8_t* data, uint32_t dataLen, uint8_t* signature, uint32_t* signatureLen)
{
	return kmSendCmdSignData(CERT_SD, wrappedKey, wrappedKeyLen, data, dataLen, signature, signatureLen);
}

#ifdef USE_QSEE
// Handle Store Service Key
int32_t kmSendCmdStoreServiceKey(uint8_t* wrappedKey, uint32_t wrappedKeyLen, uint8_t* outData, uint32_t* outDataLen)
{
	uint8_t buf[MAX_TRANSFER_SIZE];
	uint32_t bufLen = MAX_TRANSFER_SIZE;

	if (!wrappedKey) {
		LOGE("Some argument is NULL\n");
		return WRONG_DATA;
	}

	if (tlvInit(buf, bufLen) != NO_ERROR) {
		LOGE("tlvInit(ptr, tlvLen) error");
		return PLATFORM_INTERNAL_ERROR;
	}

	if (tlvAdd(buf, bufLen, TLV_WRAPPED_KEY, wrappedKey, wrappedKeyLen) != NO_ERROR) {
		LOGE("tlvAdd(tlv, tlvLen, TLV_WRAPPED_KEY, wrappedKey, wrappedKeyLen) error");
		return PLATFORM_INTERNAL_ERROR;
	}

	bufLen = tlvSize(buf, bufLen);
	return kmSendCmdInOut(STORE_SERVICE_KEY_CMD, buf, bufLen, outData, outDataLen);
}
#endif

// Handle Verify Service Key
int32_t kmSendCmdVerifyServiceKey(uint8_t* wrappedKey, uint32_t wrappedKeyLen, uint8_t* localTime, uint32_t localTimeLen)
{
	uint8_t buf[MAX_TRANSFER_SIZE];
	uint32_t bufLen = MAX_TRANSFER_SIZE;//sizeof(buf);

	if (!wrappedKey || !localTime || localTimeLen == 0) {
		LOGE("Some argument is NULL\n");
		return WRONG_DATA;
	}

	if (tlvInit(buf, bufLen) != NO_ERROR) {
		LOGE("tlvInit(ptr, tlvLen) error");
		return PLATFORM_INTERNAL_ERROR;
	}
#if !defined(USE_QSEE_WRAP_WITH_SFS)
	if (tlvAdd(buf, bufLen, TLV_WRAPPED_KEY, wrappedKey, wrappedKeyLen) != NO_ERROR) {
		LOGE("tlvAdd(tlv, tlvLen, TLV_WRAPPED_KEY, wrappedKey, wrappedKeyLen) error");
		return PLATFORM_INTERNAL_ERROR;
	}
#endif
	if (tlvAdd(buf, bufLen, TLV_TIMESTAMP, localTime, localTimeLen) != NO_ERROR) {
		LOGE("tlvAdd(tlv, tlvLen, TLV_WRAPPED_KEY, wrappedKey, wrappedKeyLen) error");
		return PLATFORM_INTERNAL_ERROR;
	}

	bufLen = tlvSize(buf, bufLen);

	return kmSendCmdInOut(VERIFY_SERVICE_KEY_CMD, buf, bufLen, NULL, 0);
}