diff options
Diffstat (limited to 'drivers/tee/optee/rpmb_emu.c')
-rw-r--r-- | drivers/tee/optee/rpmb_emu.c | 563 |
1 files changed, 563 insertions, 0 deletions
diff --git a/drivers/tee/optee/rpmb_emu.c b/drivers/tee/optee/rpmb_emu.c new file mode 100644 index 0000000000..7f4f688573 --- /dev/null +++ b/drivers/tee/optee/rpmb_emu.c @@ -0,0 +1,563 @@ +// SPDX-License-Identifier: BSD-2-Clause +/* + * Copyright (c) 2020 Linaro Limited + */ + +#include <common.h> +#include <dm.h> +#include <hexdump.h> +#include <log.h> +#include <tee.h> +#include <mmc.h> +#include <dm/device_compat.h> + +#include "optee_msg.h" +#include "optee_private.h" +#include "sha2.h" +#include "hmac_sha2.h" +#include "rpmb_emu.h" + +static struct rpmb_emu rpmb_emu = { + .size = EMU_RPMB_SIZE_BYTES +}; + +static struct rpmb_emu *mem_for_fd(int fd) +{ + static int sfd = -1; + + if (sfd == -1) + sfd = fd; + if (sfd != fd) { + printf("Emulating more than 1 RPMB partition is not supported\n"); + return NULL; + } + + return &rpmb_emu; +} + +#if (DEBUGLEVEL >= TRACE_FLOW) +static void dump_blocks(size_t startblk, size_t numblk, uint8_t *ptr, + bool to_mmc) +{ + char msg[100] = { 0 }; + size_t i = 0; + + for (i = 0; i < numblk; i++) { + snprintf(msg, sizeof(msg), "%s MMC block %zu", + to_mmc ? "Write" : "Read", startblk + i); + //print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, ptr, 256); + ptr += 256; + } +} +#else +static void dump_blocks(size_t startblk, size_t numblk, uint8_t *ptr, + bool to_mmc) +{ + (void)startblk; + (void)numblk; + (void)ptr; + (void)to_mmc; +} +#endif + +#define CUC(x) ((const unsigned char *)(x)) +static void hmac_update_frm(hmac_sha256_ctx *ctx, struct rpmb_data_frame *frm) +{ + hmac_sha256_update(ctx, CUC(frm->data), 256); + hmac_sha256_update(ctx, CUC(frm->nonce), 16); + hmac_sha256_update(ctx, CUC(&frm->write_counter), 4); + hmac_sha256_update(ctx, CUC(&frm->address), 2); + hmac_sha256_update(ctx, CUC(&frm->block_count), 2); + hmac_sha256_update(ctx, CUC(&frm->op_result), 2); + hmac_sha256_update(ctx, CUC(&frm->msg_type), 2); +} + +static bool is_hmac_valid(struct rpmb_emu *mem, struct rpmb_data_frame *frm, + size_t nfrm) +{ + uint8_t mac[32] = { 0 }; + size_t i = 0; + hmac_sha256_ctx ctx; + + memset(&ctx, 0, sizeof(ctx)); + + if (!mem->key_set) { + printf("Cannot check MAC (key not set)\n"); + return false; + } + + hmac_sha256_init(&ctx, mem->key, sizeof(mem->key)); + for (i = 0; i < nfrm; i++, frm++) + hmac_update_frm(&ctx, frm); + frm--; + hmac_sha256_final(&ctx, mac, 32); + + if (memcmp(mac, frm->key_mac, 32)) { + printf("Invalid MAC\n"); + return false; + } + return true; +} + +static uint16_t gen_msb1st_result(uint8_t byte) +{ + return (uint16_t)byte << 8; +} + +static uint16_t compute_hmac(struct rpmb_emu *mem, struct rpmb_data_frame *frm, + size_t nfrm) +{ + size_t i = 0; + hmac_sha256_ctx ctx; + + memset(&ctx, 0, sizeof(ctx)); + + if (!mem->key_set) { + printf("Cannot compute MAC (key not set)\n"); + return gen_msb1st_result(RPMB_RESULT_AUTH_KEY_NOT_PROGRAMMED); + } + + hmac_sha256_init(&ctx, mem->key, sizeof(mem->key)); + for (i = 0; i < nfrm; i++, frm++) + hmac_update_frm(&ctx, frm); + frm--; + hmac_sha256_final(&ctx, frm->key_mac, 32); + + return gen_msb1st_result(RPMB_RESULT_OK); +} + +static uint16_t ioctl_emu_mem_transfer(struct rpmb_emu *mem, + struct rpmb_data_frame *frm, + size_t nfrm, int to_mmc) +{ + size_t start = mem->last_op.address * 256; + size_t size = nfrm * 256; + size_t i = 0; + uint8_t *memptr = NULL; + + if (start > mem->size || start + size > mem->size) { + printf("Transfer bounds exceeed emulated memory\n"); + return gen_msb1st_result(RPMB_RESULT_ADDRESS_FAILURE); + } + if (to_mmc && !is_hmac_valid(mem, frm, nfrm)) + return gen_msb1st_result(RPMB_RESULT_AUTH_FAILURE); + + //printf("Transferring %zu 256-byte data block%s %s MMC (block offset=%zu)", + //nfrm, (nfrm > 1) ? "s" : "", to_mmc ? "to" : "from", start / 256); + for (i = 0; i < nfrm; i++) { + memptr = mem->buf + start + i * 256; + if (to_mmc) { + memcpy(memptr, frm[i].data, 256); + mem->write_counter++; + frm[i].write_counter = htonl(mem->write_counter); + frm[i].msg_type = + htons(RPMB_MSG_TYPE_RESP_AUTH_DATA_WRITE); + } else { + memcpy(frm[i].data, memptr, 256); + frm[i].msg_type = + htons(RPMB_MSG_TYPE_RESP_AUTH_DATA_READ); + frm[i].address = htons(mem->last_op.address); + frm[i].block_count = nfrm; + memcpy(frm[i].nonce, mem->nonce, 16); + } + frm[i].op_result = gen_msb1st_result(RPMB_RESULT_OK); + } + dump_blocks(mem->last_op.address, nfrm, mem->buf + start, to_mmc); + + if (!to_mmc) + compute_hmac(mem, frm, nfrm); + + return gen_msb1st_result(RPMB_RESULT_OK); +} + +static void ioctl_emu_get_write_result(struct rpmb_emu *mem, + struct rpmb_data_frame *frm) +{ + frm->msg_type = htons(RPMB_MSG_TYPE_RESP_AUTH_DATA_WRITE); + frm->op_result = mem->last_op.op_result; + frm->address = htons(mem->last_op.address); + frm->write_counter = htonl(mem->write_counter); + compute_hmac(mem, frm, 1); +} + +static uint16_t ioctl_emu_setkey(struct rpmb_emu *mem, + struct rpmb_data_frame *frm) +{ + if (mem->key_set) { + printf("Key already set\n"); + return gen_msb1st_result(RPMB_RESULT_GENERAL_FAILURE); + } + print_hex_dump_bytes("Setting Key:", DUMP_PREFIX_OFFSET, frm->key_mac, + 32); + memcpy(mem->key, frm->key_mac, 32); + mem->key_set = true; + + return gen_msb1st_result(RPMB_RESULT_OK); +} + +static void ioctl_emu_get_keyprog_result(struct rpmb_emu *mem, + struct rpmb_data_frame *frm) +{ + frm->msg_type = + htons(RPMB_MSG_TYPE_RESP_AUTH_KEY_PROGRAM); + frm->op_result = mem->last_op.op_result; +} + +static void ioctl_emu_read_ctr(struct rpmb_emu *mem, + struct rpmb_data_frame *frm) +{ + printf("Reading counter\n"); + frm->msg_type = htons(RPMB_MSG_TYPE_RESP_WRITE_COUNTER_VAL_READ); + frm->write_counter = htonl(mem->write_counter); + memcpy(frm->nonce, mem->nonce, 16); + frm->op_result = compute_hmac(mem, frm, 1); +} + +static uint32_t read_cid(uint16_t dev_id, uint8_t *cid) +{ + /* Taken from an actual eMMC chip */ + 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(cid, test_cid, sizeof(test_cid)); + + return TEE_SUCCESS; +} + +static void ioctl_emu_set_ext_csd(uint8_t *ext_csd) +{ + ext_csd[168] = EMU_RPMB_SIZE_MULT; + ext_csd[222] = EMU_RPMB_REL_WR_SEC_C; +} + +/* A crude emulation of the MMC ioctls we need for RPMB */ +static int ioctl_emu(int fd, unsigned long request, ...) +{ + struct mmc_ioc_cmd *cmd = NULL; + struct rpmb_data_frame *frm = NULL; + uint16_t msg_type = 0; + struct rpmb_emu *mem = mem_for_fd(fd); + va_list ap; + + if (request != MMC_IOC_CMD) { + printf("Unsupported ioctl: 0x%lx\n", request); + return -1; + } + if (!mem) + return -1; + + va_start(ap, request); + cmd = va_arg(ap, struct mmc_ioc_cmd *); + va_end(ap); + + switch (cmd->opcode) { + case MMC_SEND_EXT_CSD: + ioctl_emu_set_ext_csd((uint8_t *)(uintptr_t)cmd->data_ptr); + break; + + case MMC_WRITE_MULTIPLE_BLOCK: + frm = (struct rpmb_data_frame *)(uintptr_t)cmd->data_ptr; + msg_type = ntohs(frm->msg_type); + + switch (msg_type) { + case RPMB_MSG_TYPE_REQ_AUTH_KEY_PROGRAM: + mem->last_op.msg_type = msg_type; + mem->last_op.op_result = ioctl_emu_setkey(mem, frm); + break; + + case RPMB_MSG_TYPE_REQ_AUTH_DATA_WRITE: + mem->last_op.msg_type = msg_type; + mem->last_op.address = ntohs(frm->address); + mem->last_op.op_result = + ioctl_emu_mem_transfer(mem, frm, + cmd->blocks, 1); + break; + + case RPMB_MSG_TYPE_REQ_WRITE_COUNTER_VAL_READ: + case RPMB_MSG_TYPE_REQ_AUTH_DATA_READ: + memcpy(mem->nonce, frm->nonce, 16); + mem->last_op.msg_type = msg_type; + mem->last_op.address = ntohs(frm->address); + break; + default: + break; + } + break; + + case MMC_READ_MULTIPLE_BLOCK: + frm = (struct rpmb_data_frame *)(uintptr_t)cmd->data_ptr; + msg_type = ntohs(frm->msg_type); + + switch (mem->last_op.msg_type) { + case RPMB_MSG_TYPE_REQ_AUTH_KEY_PROGRAM: + ioctl_emu_get_keyprog_result(mem, frm); + break; + + case RPMB_MSG_TYPE_REQ_AUTH_DATA_WRITE: + ioctl_emu_get_write_result(mem, frm); + break; + + case RPMB_MSG_TYPE_REQ_WRITE_COUNTER_VAL_READ: + ioctl_emu_read_ctr(mem, frm); + break; + + case RPMB_MSG_TYPE_REQ_AUTH_DATA_READ: + ioctl_emu_mem_transfer(mem, frm, cmd->blocks, 0); + break; + + default: + printf("Unexpected\n"); + break; + } + break; + + default: + printf("Unsupported ioctl opcode 0x%08x\n", cmd->opcode); + return -1; + } + + return 0; +} + +static int mmc_rpmb_fd(uint16_t dev_id) +{ + (void)dev_id; + + /* Any value != -1 will do in test mode */ + return 0; +} + +static int mmc_fd(uint16_t dev_id) +{ + (void)dev_id; + + return 0; +} + +static void close_mmc_fd(int fd) +{ + (void)fd; +} + +/* + * Extended CSD Register is 512 bytes and defines device properties + * and selected modes. + */ +static uint32_t read_ext_csd(int fd, uint8_t *ext_csd) +{ + int st = 0; + struct mmc_ioc_cmd cmd = { + .blksz = 512, + .blocks = 1, + .flags = MMC_RSP_R1 | MMC_CMD_ADTC, + .opcode = MMC_SEND_EXT_CSD, + }; + + mmc_ioc_cmd_set_data(cmd, ext_csd); + + st = IOCTL(fd, MMC_IOC_CMD, &cmd); + if (st < 0) + return TEE_ERROR_GENERIC; + + return TEE_SUCCESS; +} + +static uint32_t rpmb_data_req(int fd, struct rpmb_data_frame *req_frm, + size_t req_nfrm, struct rpmb_data_frame *rsp_frm, + size_t rsp_nfrm) +{ + int st = 0; + size_t i = 0; + uint16_t msg_type = ntohs(req_frm->msg_type); + struct mmc_ioc_cmd cmd = { + .blksz = 512, + .blocks = req_nfrm, + .data_ptr = (uintptr_t)req_frm, + .flags = MMC_RSP_R1 | MMC_CMD_ADTC, + .opcode = MMC_WRITE_MULTIPLE_BLOCK, + .write_flag = 1, + }; + + for (i = 1; i < req_nfrm; i++) { + if (req_frm[i].msg_type != msg_type) { + printf("All request frames shall be of the same type\n"); + return TEE_ERROR_BAD_PARAMETERS; + } + } + + //printf("Req: %zu frame(s) of type 0x%04x", req_nfrm, msg_type); + //printf("Rsp: %zu frame(s)", rsp_nfrm); + + switch(msg_type) { + case RPMB_MSG_TYPE_REQ_AUTH_KEY_PROGRAM: + case RPMB_MSG_TYPE_REQ_AUTH_DATA_WRITE: + if (rsp_nfrm != 1) { + printf("Expected only one response frame\n"); + return TEE_ERROR_BAD_PARAMETERS; + } + + /* Send write request frame(s) */ + cmd.write_flag |= MMC_CMD23_ARG_REL_WR; + /* + * Black magic: tested on a HiKey board with a HardKernel eMMC + * module. When postsleep values are zero, the kernel logs + * random errors: "mmc_blk_ioctl_cmd: Card Status=0x00000E00" + * and ioctl() fails. + */ + cmd.postsleep_min_us = 20000; + cmd.postsleep_max_us = 50000; + st = IOCTL(fd, MMC_IOC_CMD, &cmd); + if (st < 0) + return TEE_ERROR_GENERIC; + cmd.postsleep_min_us = 0; + cmd.postsleep_max_us = 0; + + /* Send result request frame */ + memset(rsp_frm, 0, 1); + rsp_frm->msg_type = htons(RPMB_MSG_TYPE_REQ_RESULT_READ); + cmd.data_ptr = (uintptr_t)rsp_frm; + cmd.write_flag &= ~MMC_CMD23_ARG_REL_WR; + st = IOCTL(fd, MMC_IOC_CMD, &cmd); + if (st < 0) + return TEE_ERROR_GENERIC; + + /* Read response frame */ + cmd.opcode = MMC_READ_MULTIPLE_BLOCK; + cmd.write_flag = 0; + cmd.blocks = rsp_nfrm; + st = IOCTL(fd, MMC_IOC_CMD, &cmd); + if (st < 0) + return TEE_ERROR_GENERIC; + break; + + case RPMB_MSG_TYPE_REQ_WRITE_COUNTER_VAL_READ: + if (rsp_nfrm != 1) { + printf("Expected only one response frame\n"); + return TEE_ERROR_BAD_PARAMETERS; + } +#if __GNUC__ > 6 + __attribute__((fallthrough)); +#endif + + case RPMB_MSG_TYPE_REQ_AUTH_DATA_READ: + if (req_nfrm != 1) { + printf("Expected only one request frame\n"); + return TEE_ERROR_BAD_PARAMETERS; + } + + /* Send request frame */ + st = IOCTL(fd, MMC_IOC_CMD, &cmd); + if (st < 0) + return TEE_ERROR_GENERIC; + + /* Read response frames */ + cmd.data_ptr = (uintptr_t)rsp_frm; + cmd.opcode = MMC_READ_MULTIPLE_BLOCK; + cmd.write_flag = 0; + cmd.blocks = rsp_nfrm; + st = IOCTL(fd, MMC_IOC_CMD, &cmd); + if (st < 0) + return TEE_ERROR_GENERIC; + break; + + default: + printf("Unsupported message type: %d", msg_type); + return TEE_ERROR_GENERIC; + } + + return TEE_SUCCESS; +} + +static uint32_t rpmb_get_dev_info(uint16_t dev_id, struct rpmb_dev_info *info) +{ + int fd = 0; + uint32_t res = 0; + uint8_t ext_csd[512] = { 0 }; + + res = read_cid(dev_id, info->cid); + if (res != TEE_SUCCESS) + return res; + + fd = mmc_fd(dev_id); + if (fd < 0) + return TEE_ERROR_BAD_PARAMETERS; + + res = read_ext_csd(fd, ext_csd); + if (res != TEE_SUCCESS) + goto err; + + info->rel_wr_sec_c = ext_csd[222]; + info->rpmb_size_mult = ext_csd[168]; + info->ret_code = RPMB_CMD_GET_DEV_INFO_RET_OK; + +err: + close_mmc_fd(fd); + return res; +} + + +/* + * req is one struct rpmb_req followed by one or more struct rpmb_data_frame + * rsp is either one struct rpmb_dev_info or one or more struct rpmb_data_frame + */ +uint32_t rpmb_process_request_emu(void *req, size_t req_size, + void *rsp, size_t rsp_size) +{ + struct rpmb_req *sreq = req; + size_t req_nfrm = 0; + size_t rsp_nfrm = 0; + uint32_t res = 0; + int fd = 0; + + if (req_size < sizeof(*sreq)) + return TEE_ERROR_BAD_PARAMETERS; + + switch (sreq->cmd) { + case RPMB_CMD_DATA_REQ: + req_nfrm = (req_size - sizeof(struct rpmb_req)) / 512; + rsp_nfrm = rsp_size / 512; + fd = mmc_rpmb_fd(sreq->dev_id); + if (fd < 0) + return TEE_ERROR_BAD_PARAMETERS; + res = rpmb_data_req(fd, RPMB_REQ_DATA(req), req_nfrm, rsp, + rsp_nfrm); + break; + + case RPMB_CMD_GET_DEV_INFO: + if (req_size != sizeof(struct rpmb_req) || + rsp_size != sizeof(struct rpmb_dev_info)) { + printf("Invalid req/rsp size"); + return TEE_ERROR_BAD_PARAMETERS; + } + res = rpmb_get_dev_info(sreq->dev_id, + (struct rpmb_dev_info *)rsp); + break; + + default: + printf("Unsupported RPMB command: %d", sreq->cmd); + res = TEE_ERROR_BAD_PARAMETERS; + break; + } + + return res; +} |