// SPDX-License-Identifier: BSD-2-Clause
/*
 * Copyright (C) 2019-2020, Linaro Limited
 */

#include <assert.h>
#include <pta_system.h>
#include <string.h>
#include <string_ext.h>
#include <tee_internal_api.h>
#include <tee_internal_api_extensions.h>
#include <trusted_keys.h>
#include <util.h>

#define IV_SIZE			16
#define TAG_SIZE		16
#define MAX_BUF_SIZE		512

/*
 * Acronym:
 *
 * TK - Trusted Key
 */

struct tk_blob_hdr {
	uint8_t reserved;
	uint8_t iv[IV_SIZE];
	uint8_t tag[TAG_SIZE];
	uint8_t enc_key[];
};

static TEE_Result get_random(uint32_t types, TEE_Param params[TEE_NUM_PARAMS])
{
	uint8_t *rng_buf = NULL;

	DMSG("Invoked TA_CMD_GET_RANDOM");

	if (types != TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_OUTPUT,
				     TEE_PARAM_TYPE_NONE,
				     TEE_PARAM_TYPE_NONE,
				     TEE_PARAM_TYPE_NONE))
		return TEE_ERROR_BAD_PARAMETERS;

	if (!params[0].memref.buffer || !params[0].memref.size)
		return TEE_ERROR_BAD_PARAMETERS;

	rng_buf = TEE_Malloc(params[0].memref.size, TEE_MALLOC_FILL_ZERO);
	if (!rng_buf)
		return TEE_ERROR_OUT_OF_MEMORY;

	TEE_GenerateRandom(rng_buf, params[0].memref.size);
	memcpy(params[0].memref.buffer, rng_buf, params[0].memref.size);
	memzero_explicit(rng_buf, params[0].memref.size);

	TEE_Free(rng_buf);

	return TEE_SUCCESS;
}

static TEE_Result derive_unique_key(uint8_t *key, uint16_t key_size,
				    uint8_t *extra, uint16_t extra_size)
{
	TEE_TASessionHandle sess = TEE_HANDLE_NULL;
	TEE_Param params[TEE_NUM_PARAMS] = { };
	TEE_Result res = TEE_ERROR_GENERIC;
	uint32_t ret_orig = 0;
	uint32_t param_types = TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_INPUT,
					       TEE_PARAM_TYPE_MEMREF_OUTPUT,
					       TEE_PARAM_TYPE_NONE,
					       TEE_PARAM_TYPE_NONE);

	res = TEE_OpenTASession(&(const TEE_UUID)PTA_SYSTEM_UUID,
				TEE_TIMEOUT_INFINITE, 0, NULL, &sess,
				&ret_orig);
	if (res)
		return res;

	if (extra && extra_size) {
		params[0].memref.buffer = extra;
		params[0].memref.size = extra_size;
	}

	params[1].memref.buffer = key;
	params[1].memref.size = key_size;

	res = TEE_InvokeTACommand(sess, TEE_TIMEOUT_INFINITE,
				  PTA_SYSTEM_DERIVE_TA_UNIQUE_KEY,
				  param_types, params, &ret_orig);

	TEE_CloseTASession(sess);

	return res;
}

static TEE_Result huk_ae_encrypt(TEE_OperationHandle crypto_op, uint8_t *in,
				 uint32_t in_sz, uint8_t *out, uint32_t *out_sz)
{
	TEE_Result res = TEE_ERROR_GENERIC;
	struct tk_blob_hdr *hdr = (struct tk_blob_hdr *)out;
	uint8_t iv[IV_SIZE] = { 0 };
	uint32_t enc_key_len = in_sz;
	uint32_t tag_len = TAG_SIZE;

	hdr->reserved = 0;
	TEE_GenerateRandom(iv, IV_SIZE);
	memcpy(hdr->iv, iv, IV_SIZE);

	res = TEE_AEInit(crypto_op, hdr->iv, IV_SIZE, TAG_SIZE * 8, 0, 0);
	if (res)
		return res;

	res = TEE_AEEncryptFinal(crypto_op, in, in_sz, hdr->enc_key,
				 &enc_key_len, hdr->tag, &tag_len);
	if (res || tag_len != TAG_SIZE)
		return TEE_ERROR_SECURITY;

	if (ADD_OVERFLOW(enc_key_len, sizeof(*hdr), out_sz))
		return TEE_ERROR_SECURITY;

	return res;
}

static TEE_Result huk_ae_decrypt(TEE_OperationHandle crypto_op, uint8_t *in,
				 uint32_t in_sz, uint8_t *out, uint32_t *out_sz)
{
	TEE_Result res = TEE_ERROR_GENERIC;
	struct tk_blob_hdr *hdr = (struct tk_blob_hdr *)in;
	uint8_t tag[TAG_SIZE] = { 0 };
	uint32_t enc_key_len = 0;

	if (SUB_OVERFLOW(in_sz, sizeof(*hdr), &enc_key_len))
		return TEE_ERROR_SECURITY;

	res = TEE_AEInit(crypto_op, hdr->iv, IV_SIZE, TAG_SIZE * 8, 0, 0);
	if (res)
		return res;

	memcpy(tag, hdr->tag, TAG_SIZE);
	res = TEE_AEDecryptFinal(crypto_op, hdr->enc_key, enc_key_len, out,
				 out_sz, tag, TAG_SIZE);
	if (res)
		res = TEE_ERROR_SECURITY;

	return res;
}

static TEE_Result huk_crypt(TEE_OperationMode mode, uint8_t *in, uint32_t in_sz,
			    uint8_t *out, uint32_t *out_sz)
{
	TEE_Result res = TEE_ERROR_GENERIC;
	TEE_OperationHandle crypto_op = TEE_HANDLE_NULL;
	TEE_ObjectHandle hkey = TEE_HANDLE_NULL;
	uint8_t huk_key[TA_DERIVED_KEY_MAX_SIZE] = { };
	TEE_Attribute attr = { };

	res = TEE_AllocateOperation(&crypto_op, TEE_ALG_AES_GCM, mode,
				    sizeof(huk_key) * 8);
	if (res)
		return res;

	res = derive_unique_key(huk_key, sizeof(huk_key), NULL, 0);
	if (res) {
		EMSG("derive_unique_key failed: returned %#"PRIx32, res);
		goto out_op;
	}

	res = TEE_AllocateTransientObject(TEE_TYPE_AES, sizeof(huk_key) * 8,
					  &hkey);
	if (res)
		goto out_op;

	attr.attributeID = TEE_ATTR_SECRET_VALUE;
	attr.content.ref.buffer = huk_key;
	attr.content.ref.length = sizeof(huk_key);

	res = TEE_PopulateTransientObject(hkey, &attr, 1);
	if (res)
		goto out_key;

	res = TEE_SetOperationKey(crypto_op, hkey);
	if (res)
		goto out_key;

	if (mode == TEE_MODE_ENCRYPT) {
		res = huk_ae_encrypt(crypto_op, in, in_sz, out, out_sz);
		if (res)
			EMSG("huk_AE_encrypt failed: returned %#"PRIx32, res);
	} else if (mode == TEE_MODE_DECRYPT) {
		res = huk_ae_decrypt(crypto_op, in, in_sz, out, out_sz);
		if (res)
			EMSG("huk_AE_decrypt failed: returned %#"PRIx32, res);
	} else {
		TEE_Panic(0);
	}

out_key:
	TEE_FreeTransientObject(hkey);
out_op:
	TEE_FreeOperation(crypto_op);
	memzero_explicit(huk_key, sizeof(huk_key));
	return res;
}

static TEE_Result seal_trusted_key(uint32_t types,
				   TEE_Param params[TEE_NUM_PARAMS])
{
	TEE_Result res = TEE_SUCCESS;
	uint8_t *in = NULL;
	uint32_t in_sz = 0;
	uint8_t *out = NULL;
	uint32_t out_sz = 0;

	DMSG("Invoked TA_CMD_SEAL");

	if (types != TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_INPUT,
				     TEE_PARAM_TYPE_MEMREF_OUTPUT,
				     TEE_PARAM_TYPE_NONE,
				     TEE_PARAM_TYPE_NONE))
		return TEE_ERROR_BAD_PARAMETERS;

	in = params[0].memref.buffer;
	in_sz = params[0].memref.size;
	out = params[1].memref.buffer;
	out_sz = params[1].memref.size;

	if (!in || !in_sz || in_sz > MAX_BUF_SIZE)
		return TEE_ERROR_BAD_PARAMETERS;
	if ((!out && out_sz) ||
	    (out && !ALIGNMENT_IS_OK(out, struct tk_blob_hdr)) ||
	    out_sz > MAX_BUF_SIZE)
		return TEE_ERROR_BAD_PARAMETERS;

	if ((in_sz + sizeof(struct tk_blob_hdr)) > out_sz) {
		params[1].memref.size = in_sz + sizeof(struct tk_blob_hdr);
		return TEE_ERROR_SHORT_BUFFER;
	}

	res = huk_crypt(TEE_MODE_ENCRYPT, in, in_sz, out, &out_sz);
	if (res == TEE_SUCCESS) {
		assert(out_sz == in_sz + sizeof(struct tk_blob_hdr));
		params[1].memref.size = out_sz;
	}

	return res;
}

static TEE_Result unseal_trusted_key(uint32_t types,
				     TEE_Param params[TEE_NUM_PARAMS])
{
	TEE_Result res = TEE_SUCCESS;
	uint8_t *in = NULL;
	uint32_t in_sz = 0;
	uint8_t *out = NULL;
	uint32_t out_sz = 0;

	DMSG("Invoked TA_CMD_UNSEAL");

	if (types != TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_INPUT,
				     TEE_PARAM_TYPE_MEMREF_OUTPUT,
				     TEE_PARAM_TYPE_NONE,
				     TEE_PARAM_TYPE_NONE))
		return TEE_ERROR_BAD_PARAMETERS;

	in = params[0].memref.buffer;
	in_sz = params[0].memref.size;
	out = params[1].memref.buffer;
	out_sz = params[1].memref.size;

	if (!in || !ALIGNMENT_IS_OK(in, struct tk_blob_hdr) ||
	    in_sz <= sizeof(struct tk_blob_hdr) || in_sz > MAX_BUF_SIZE)
		return TEE_ERROR_BAD_PARAMETERS;
	if ((!out && out_sz) || out_sz > MAX_BUF_SIZE)
		return TEE_ERROR_BAD_PARAMETERS;

	if (in_sz > (out_sz + sizeof(struct tk_blob_hdr))) {
		params[1].memref.size = in_sz - sizeof(struct tk_blob_hdr);
		return TEE_ERROR_SHORT_BUFFER;
	}

	res = huk_crypt(TEE_MODE_DECRYPT, in, in_sz, out, &out_sz);
	if (res == TEE_SUCCESS) {
		assert(out_sz == in_sz - sizeof(struct tk_blob_hdr));
		params[1].memref.size = out_sz;
	}

	return res;
}

TEE_Result TA_CreateEntryPoint(void)
{
	return TEE_SUCCESS;
}

void TA_DestroyEntryPoint(void)
{
}

TEE_Result TA_OpenSessionEntryPoint(uint32_t pt __unused,
				    TEE_Param params[TEE_NUM_PARAMS] __unused,
				    void **session __unused)
{
	TEE_Result res = TEE_ERROR_GENERIC;
	TEE_PropSetHandle h = TEE_HANDLE_NULL;
	TEE_Identity id = { };

	res = TEE_AllocatePropertyEnumerator(&h);
	if (res)
		goto out;

	TEE_StartPropertyEnumerator(h, TEE_PROPSET_CURRENT_CLIENT);

	res = TEE_GetPropertyAsIdentity(h, NULL, &id);
	if (res)
		goto out;

	if (id.login != TEE_LOGIN_REE_KERNEL)
		res = TEE_ERROR_ACCESS_DENIED;

out:
	if (h)
		TEE_FreePropertyEnumerator(h);
	return res;
}

void TA_CloseSessionEntryPoint(void *sess __unused)
{
}

TEE_Result TA_InvokeCommandEntryPoint(void *sess __unused, uint32_t cmd,
				      uint32_t pt,
				      TEE_Param params[TEE_NUM_PARAMS])
{
	switch (cmd) {
	case TA_CMD_GET_RANDOM:
		return get_random(pt, params);
	case TA_CMD_SEAL:
		return seal_trusted_key(pt, params);
	case TA_CMD_UNSEAL:
		return unseal_trusted_key(pt, params);
	default:
		EMSG("Command ID %#"PRIx32" is not supported", cmd);
		return TEE_ERROR_NOT_SUPPORTED;
	}
}
