Implement emulated RPMB backend

The backend uses a memory allocated buffer for storing data and it
emulates all the necessary data frame checks which makes it ideal for
testing in host environment.

Signed-off-by: Imre Kis <imre.kis@arm.com>
Change-Id: I18600ad6ccf969e75b43029a04daf8c3524aec59
diff --git a/components/service/rpmb/backend/emulated/component.cmake b/components/service/rpmb/backend/emulated/component.cmake
new file mode 100644
index 0000000..e4dfa16
--- /dev/null
+++ b/components/service/rpmb/backend/emulated/component.cmake
@@ -0,0 +1,14 @@
+#-------------------------------------------------------------------------------
+# Copyright (c) 2023, Arm Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+#-------------------------------------------------------------------------------
+if (NOT DEFINED TGT)
+	message(FATAL_ERROR "mandatory parameter TGT is not defined.")
+endif()
+
+target_sources(${TGT} PRIVATE
+	"${CMAKE_CURRENT_LIST_DIR}/rpmb_backend_emulated.c"
+)
+
diff --git a/components/service/rpmb/backend/emulated/rpmb_backend_emulated.c b/components/service/rpmb/backend/emulated/rpmb_backend_emulated.c
new file mode 100644
index 0000000..597c148
--- /dev/null
+++ b/components/service/rpmb/backend/emulated/rpmb_backend_emulated.c
@@ -0,0 +1,377 @@
+/*
+ * Copyright (c) 2023, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include "rpmb_backend_emulated.h"
+#include "util.h"
+#include "psa/crypto.h"
+#include <stdlib.h>
+#include <string.h>
+
+static void u32_to_rpmb_field(uint32_t u32, uint8_t *rpmb_field)
+{
+	rpmb_field[0] = (u32 >> 24) & 0xff;
+	rpmb_field[1] = (u32 >> 16) & 0xff;
+	rpmb_field[2] = (u32 >> 8) & 0xff;
+	rpmb_field[3] = u32 & 0xff;
+}
+
+static uint32_t u32_from_rpmb_field(const uint8_t *rpmb_field)
+{
+	return (rpmb_field[0] << 24) | (rpmb_field[1] << 16) | (rpmb_field[2] << 8) | rpmb_field[3];
+}
+
+static void u16_to_rpmb_field(uint16_t u16, uint8_t *rpmb_field)
+{
+	rpmb_field[0] = (u16 >> 8) & 0xff;
+	rpmb_field[1] = u16 & 0xff;
+}
+
+static uint16_t u16_from_rpmb_field(const uint8_t *rpmb_field)
+{
+	return (rpmb_field[0] << 8) | rpmb_field[1];
+}
+
+static psa_status_t rpmb_backend_emulated_get_dev_info(
+	void *context, uint32_t dev_id, struct rpmb_dev_info *dev_info)
+{
+	struct rpmb_backend_emulated *backend = (struct rpmb_backend_emulated *)context;
+	static const uint8_t test_cid[] = {
+		/* MID (Manufacturer ID): Micron */
+		0xfe,
+		/* CBX (Device/BGA): BGA */
+		0x01,
+		/* OID (OEM/Application ID) */
+		0x4e,
+		/* PNM (Product name) "MMC04G" */
+		0x4d, 0x4d, 0x43, 0x30, 0x34, 0x47,
+		/* PRV (Product revision): 4.2 */
+		0x42,
+		/* PSN (Product serial number) */
+		0xc8, 0xf6, 0x55, 0x2a,
+		/*
+		 * MDT (Manufacturing date):
+		 * June, 2014
+		 */
+		0x61,
+		/* (CRC7 (0xA) << 1) | 0x1 */
+		0x15
+	};
+
+	(void)dev_id;
+
+	memcpy(dev_info->cid, test_cid, sizeof(dev_info->cid));
+	dev_info->rpmb_size_mult = backend->buffer_size / RPMB_SIZE_MULT_UNIT;
+
+	return PSA_SUCCESS;
+}
+
+static void calculate_mac(struct rpmb_backend_emulated *backend,
+			  const struct rpmb_data_frame *frames, size_t frame_count, uint8_t *mac)
+{
+	const size_t frame_hash_length =
+		sizeof(struct rpmb_data_frame) - offsetof(struct rpmb_data_frame, data);
+	psa_hash_operation_t operation = PSA_HASH_OPERATION_INIT;
+	size_t hash_length = 0;
+	size_t i = 0;
+
+	psa_hash_setup(&operation, PSA_ALG_SHA_256);
+	psa_hash_update(&operation, backend->key, sizeof(backend->key));
+
+	for (i = 0; i < frame_count; i++) {
+		/* Hash data, nonce, write counter, address, block, result, req/resp fields */
+		psa_hash_update(&operation, (const uint8_t *)frames[i].data, frame_hash_length);
+	}
+
+	psa_hash_finish(&operation, mac, RPMB_KEY_MAC_SIZE, &hash_length);
+}
+
+static void rpmb_emulated_authentication_key_write(struct rpmb_backend_emulated *backend,
+						   const struct rpmb_data_frame *request,
+						   struct rpmb_data_frame *response)
+{
+	uint16_t result = RPMB_RES_GENERAL_FAILURE;
+
+	memset(response, 0x00, sizeof(*response));
+
+	if (!backend->key_programmed) {
+		memcpy(backend->key, request->key_mac, sizeof(backend->key));
+		backend->key_programmed = true;
+		result = RPMB_RES_OK;
+	}
+
+	u16_to_rpmb_field(result, response->op_result);
+	u16_to_rpmb_field(RPMB_RESP_TYPE_AUTHENTICATION_KEY_WRITE, response->msg_type);
+}
+
+static void rpmb_emulated_read_write_counter(struct rpmb_backend_emulated *backend,
+					     const struct rpmb_data_frame *request,
+					     struct rpmb_data_frame *response)
+{
+	uint8_t nonce[RPMB_NONCE_SIZE] = { 0 };
+
+	/*
+	 * It is not guarranteed by the RPC layer that the request and response has distinct
+	 * memory areas. Thus we need to create a copy of nonce because clearing the response might
+	 * clear the request as well.
+	 */
+	memcpy(nonce, request->nonce, sizeof(nonce));
+
+	memset(response, 0x00, sizeof(*response));
+	u16_to_rpmb_field(RPMB_RESP_TYPE_READ_WRITE_COUNTER, response->msg_type);
+	u16_to_rpmb_field(RPMB_RES_KEY_NOT_PROGRAMMED, response->op_result);
+
+	if (backend->key_programmed) {
+		memcpy(response->nonce, nonce, RPMB_NONCE_SIZE);
+		u32_to_rpmb_field(backend->write_counter, response->write_counter);
+		u16_to_rpmb_field(RPMB_RES_OK, response->op_result);
+		calculate_mac(backend, response, 1, response->key_mac);
+	}
+}
+
+static uint16_t check_write_request(struct rpmb_backend_emulated *backend,
+				    const struct rpmb_data_frame *request)
+{
+	size_t address = 0;
+	uint8_t mac[RPMB_KEY_MAC_SIZE] = { 0 };
+
+	/* Checking as specified in eMMC 6.6.22.4.3 */
+	if (backend->write_counter == 0xffffffff)
+		return RPMB_RES_COUNTER_EXPIRED | RPMB_RES_WRITE_FAILURE;
+
+	if (MUL_OVERFLOW(u16_from_rpmb_field(request->address), RPMB_DATA_SIZE, &address))
+		return RPMB_RES_ADDRESS_FAILURE;
+
+	if (address >= backend->buffer_size)
+		return RPMB_RES_ADDRESS_FAILURE;
+
+	/* Only single block write is supported for now */
+	if (u16_from_rpmb_field(request->block_count) != 1)
+		return RPMB_RES_GENERAL_FAILURE;
+
+	calculate_mac(backend, request, 1, mac);
+	if (memcmp(mac, request->key_mac, sizeof(mac)) != 0)
+		return RPMB_RES_AUTHENTICATION_FAILURE;
+
+	if (backend->write_counter != u32_from_rpmb_field(request->write_counter))
+		return RPMB_RES_COUNTER_FAILURE;
+
+	return RPMB_RES_OK;
+}
+
+static void rpmb_emulated_authenticated_data_write(struct rpmb_backend_emulated *backend,
+						   const struct rpmb_data_frame *request,
+						   struct rpmb_data_frame *response)
+{
+	uint16_t result = RPMB_RES_KEY_NOT_PROGRAMMED;
+
+	memset(response, 0x00, sizeof(*response));
+	u16_to_rpmb_field(RPMB_RESP_TYPE_AUTHENTICATED_DATA_WRITE, response->msg_type);
+
+	if (backend->key_programmed) {
+		result = check_write_request(backend, request);
+		if (result == RPMB_RES_OK) {
+			size_t address = u16_from_rpmb_field(request->address) * RPMB_DATA_SIZE;
+
+			memcpy(&backend->buffer[address], request->data, sizeof(request->data));
+			backend->write_counter++;
+		}
+
+		memcpy(response->address, request->address, sizeof(response->address));
+		u32_to_rpmb_field(backend->write_counter, response->write_counter);
+		calculate_mac(backend, response, 1, response->key_mac);
+	}
+
+	u16_to_rpmb_field(result, response->op_result);
+
+}
+
+static uint16_t check_read_request(struct rpmb_backend_emulated *backend,
+				   const struct rpmb_data_frame *request,
+				   size_t *address, size_t *length)
+{
+	uint16_t block_count = 0;
+	size_t end = 0;
+
+	/* Check if start address fits into range */
+	if (MUL_OVERFLOW(u16_from_rpmb_field(request->address), RPMB_DATA_SIZE, address))
+		return RPMB_RES_ADDRESS_FAILURE;
+
+	if (*address >= backend->buffer_size)
+		return RPMB_RES_ADDRESS_FAILURE;
+
+	block_count = u16_from_rpmb_field(request->block_count);
+	if (block_count == 0)
+		return RPMB_RES_GENERAL_FAILURE;
+
+	/* Check if end address fits into range */
+	if (MUL_OVERFLOW(block_count, RPMB_DATA_SIZE, length))
+		return RPMB_RES_ADDRESS_FAILURE;
+
+	if (ADD_OVERFLOW(*address, *length, &end))
+		return RPMB_RES_ADDRESS_FAILURE;
+
+	if (end > backend->buffer_size)
+		return RPMB_RES_ADDRESS_FAILURE;
+
+	return RPMB_RES_OK;
+}
+
+static psa_status_t rpmb_emulated_authenticated_data_read(struct rpmb_backend_emulated *backend,
+							  const struct rpmb_data_frame *request,
+							  struct rpmb_data_frame *response,
+							  size_t *response_count)
+{
+	size_t response_index = 0;
+
+	if (backend->key_programmed) {
+		uint16_t result = RPMB_RES_GENERAL_FAILURE;
+		size_t address = 0;
+		size_t length = 0;
+
+		result = check_read_request(backend, request, &address, &length);
+		if (result == RPMB_RES_OK) {
+			uint16_t block_count = 0;
+			uint16_t i = 0;
+			struct rpmb_data_frame temp = { 0 };
+
+			block_count = u16_from_rpmb_field(request->block_count);
+			if (block_count > *response_count)
+				/*
+				 * It is a service level error if available response dataframe count
+				 * is less than the requested block count.
+				 */
+				return PSA_ERROR_BUFFER_TOO_SMALL;
+
+			for (i = 0; i < block_count; i++) {
+				uint8_t *data = &backend->buffer[address + i * RPMB_DATA_SIZE];
+
+				memset(&temp, 0x00, sizeof(temp));
+				memcpy(temp.data, data, sizeof(temp.data));
+				memcpy(temp.nonce, request->nonce, sizeof(temp.nonce));
+				memcpy(temp.address, request->address, sizeof(temp.address));
+				memcpy(temp.block_count, request->block_count,
+				       sizeof(temp.block_count));
+				u16_to_rpmb_field(RPMB_RES_OK, temp.op_result);
+				u16_to_rpmb_field(RPMB_RESP_TYPE_AUTHENTICATED_DATA_READ,
+						  temp.msg_type);
+
+				memcpy(&response[i], &temp, sizeof(response[i]));
+			}
+
+			calculate_mac(backend, response, block_count,
+				      response[block_count - 1].key_mac);
+			response_index += block_count;
+		}
+	} else {
+		/* Return single data frame with the error code */
+		memset(response, 0x00, sizeof(*response));
+		u16_to_rpmb_field(RPMB_RES_KEY_NOT_PROGRAMMED, response->op_result);
+		u16_to_rpmb_field(RPMB_RESP_TYPE_AUTHENTICATED_DATA_READ, response->msg_type);
+		response_index++;
+	}
+
+	*response_count = response_index;
+
+	return PSA_SUCCESS;
+}
+
+static psa_status_t rpmb_backend_emulated_data_request(
+	void *context, uint32_t dev_id, const struct rpmb_data_frame *request_frames,
+	size_t request_frame_count, struct rpmb_data_frame *response_frames,
+	size_t *response_frame_count)
+{
+	struct rpmb_backend_emulated *backend = (struct rpmb_backend_emulated *)context;
+	size_t req_index = 0;
+	size_t resp_index = 0;
+
+	if (dev_id != 0)
+		return PSA_ERROR_INVALID_ARGUMENT;
+
+	for (req_index = 0; req_index < request_frame_count; req_index++) {
+		const struct rpmb_data_frame *request = &request_frames[req_index];
+
+		switch (u16_from_rpmb_field(request->msg_type)) {
+		case RPMB_REQ_TYPE_AUTHENTICATION_KEY_WRITE:
+			rpmb_emulated_authentication_key_write(backend, request, &backend->result);
+			break;
+
+		case RPMB_REQ_TYPE_READ_WRITE_COUNTER:
+			if (resp_index < *response_frame_count) {
+				struct rpmb_data_frame *response = &response_frames[resp_index++];
+
+				rpmb_emulated_read_write_counter(backend, request, response);
+			} else {
+				return PSA_ERROR_BUFFER_TOO_SMALL;
+			}
+			break;
+
+		case RPMB_REQ_TYPE_AUTHENTICATED_DATA_WRITE:
+			rpmb_emulated_authenticated_data_write(backend, request, &backend->result);
+			break;
+
+		case RPMB_REQ_TYPE_AUTHENTICATED_DATA_READ:
+			if (resp_index < *response_frame_count) {
+				struct rpmb_data_frame *response = &response_frames[resp_index];
+				size_t response_count = *response_frame_count - resp_index;
+				psa_status_t status = PSA_ERROR_GENERIC_ERROR;
+
+				status = rpmb_emulated_authenticated_data_read(
+					backend, request, response, &response_count);
+				if (status != PSA_SUCCESS)
+					return status;
+
+				resp_index += response_count;
+			} else {
+				return PSA_ERROR_BUFFER_TOO_SMALL;
+			}
+			break;
+
+		case RPMB_REQ_TYPE_RESULT_READ_REQUEST:
+			if (resp_index < *response_frame_count) {
+				struct rpmb_data_frame *response = &response_frames[resp_index++];
+
+				memcpy(response, &backend->result, sizeof(*response));
+			} else {
+				return PSA_ERROR_BUFFER_TOO_SMALL;
+			}
+			break;
+
+		default:
+			return PSA_ERROR_INVALID_ARGUMENT;
+		}
+	}
+
+	*response_frame_count = resp_index;
+
+	return PSA_SUCCESS;
+}
+
+struct rpmb_backend *rpmb_backend_emulated_init(struct rpmb_backend_emulated *context,
+						uint8_t size_mult)
+{
+	static const struct rpmb_backend_interface interface = {
+		rpmb_backend_emulated_get_dev_info,
+		rpmb_backend_emulated_data_request,
+	};
+
+	if (!context || !size_mult)
+		return NULL;
+
+	context->buffer_size = size_mult * RPMB_SIZE_MULT_UNIT;
+	context->buffer = calloc(1, context->buffer_size);
+	if (!context->buffer)
+		return NULL;
+
+	context->backend.context = context;
+	context->backend.interface = &interface;
+
+	return &context->backend;
+}
+
+void rpmb_backend_emulated_deinit(struct rpmb_backend_emulated *context)
+{
+	free(context->buffer);
+}
diff --git a/components/service/rpmb/backend/emulated/rpmb_backend_emulated.h b/components/service/rpmb/backend/emulated/rpmb_backend_emulated.h
new file mode 100644
index 0000000..c13a6dd
--- /dev/null
+++ b/components/service/rpmb/backend/emulated/rpmb_backend_emulated.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2023, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef RPMB_BACKEND_EMULATED
+#define RPMB_BACKEND_EMULATED
+
+#include "../rpmb_backend.h"
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \brief Emulated RPMB backend
+ *
+ * This backend uses a memory allocated buffer for storing data and it emulates
+ * all the necessary data frame checks.
+ */
+struct rpmb_backend_emulated {
+	struct rpmb_backend backend;
+	uint8_t *buffer;
+	size_t buffer_size;
+	uint8_t key[RPMB_KEY_MAC_SIZE];
+	bool key_programmed;
+	uint32_t write_counter;
+	struct rpmb_data_frame result;
+};
+
+/**
+ * \brief Initialize emulated RPMB backend
+ *
+ * \param context[in]	Backend context
+ * \param size_mult[in]	Size of the RPMB in 128kB units
+ * \return struct rpmb_backend*
+ */
+struct rpmb_backend *rpmb_backend_emulated_init(struct rpmb_backend_emulated *context,
+						uint8_t size_mult);
+
+/**
+ * \brief Deinitialize emulated RPMB backend
+ *
+ * \param context[in]	Backend context
+ */
+void rpmb_backend_emulated_deinit(struct rpmb_backend_emulated *context);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* RPMB_BACKEND_EMULATED */