The position of the USS in the load-app header is incorrect. Fix it in the driver and the emulator, so it matches the tkey-sign program. Co-developed-by: Claude <claude@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com>
370 lines
8.6 KiB
C
370 lines
8.6 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* Copyright (C) 2025 Canonical Ltd
|
|
*
|
|
* TKey emulator for testing TKey functionality in sandbox
|
|
*/
|
|
|
|
#define LOG_CATEGORY UCLASS_TKEY
|
|
|
|
#include <dm.h>
|
|
#include <errno.h>
|
|
#include <log.h>
|
|
#include <malloc.h>
|
|
#include <tkey.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/string.h>
|
|
#include <asm/unaligned.h>
|
|
|
|
/* TKey protocol frame structure */
|
|
#define FRAME_SIZE 128
|
|
#define FRAME_HEADER_SIZE 1
|
|
#define FRAME_DATA_SIZE (FRAME_SIZE - FRAME_HEADER_SIZE)
|
|
|
|
/* Frame header bit masks and values */
|
|
#define FRAME_ENDPOINT_MASK 0x18
|
|
#define FRAME_ENDPOINT_SHIFT 3
|
|
#define ENDPOINT_FIRMWARE 2
|
|
#define ENDPOINT_APP 3
|
|
|
|
/* Firmware Commands */
|
|
#define FW_CMD_GET_NAME_VERSION 0x01
|
|
#define FW_CMD_GET_UDI 0x08
|
|
#define FW_CMD_LOAD_APP 0x03
|
|
#define FW_CMD_LOAD_APP_DATA 0x05
|
|
|
|
/* App Commands */
|
|
#define APP_CMD_GET_PUBKEY 0x01
|
|
|
|
/* USB Response format markers */
|
|
#define USB_FRAME_MARKER 0x52
|
|
#define USB_RSP_NAME_VERSION 0x02
|
|
#define USB_RSP_GET_UDI 0x09
|
|
|
|
/* Status codes */
|
|
#define STATUS_OK 0x00
|
|
#define STATUS_ERROR 0x01
|
|
|
|
/*
|
|
* struct tkey_emul_plat - TKey emulator platform data (persists across remove)
|
|
*
|
|
* @disconnected: Whether device is disconnected (for testing removal)
|
|
*/
|
|
struct tkey_emul_plat {
|
|
bool disconnected;
|
|
};
|
|
|
|
/*
|
|
* struct tkey_emul_priv - TKey emulator state
|
|
*
|
|
* @app_loaded: Whether an app is loaded (app mode vs firmware mode)
|
|
* @udi: Unique Device Identifier (8 bytes)
|
|
* @app_size: Size of loaded app
|
|
* @pubkey: Simulated public key (32 bytes)
|
|
* @resp: Buffer for storing response to be read
|
|
* @resp_len: Length of data in response buffer
|
|
* @total_loaded: Track total app data loaded
|
|
*/
|
|
struct tkey_emul_priv {
|
|
bool app_loaded;
|
|
u8 udi[8];
|
|
u32 app_size;
|
|
u8 pubkey[32];
|
|
u8 resp[FRAME_SIZE];
|
|
int resp_len;
|
|
u32 total_loaded;
|
|
};
|
|
|
|
static int tkey_emul_read(struct udevice *dev, void *buf, int len,
|
|
int timeout_ms)
|
|
{
|
|
/*
|
|
* Read operations are immediate with no actual I/O. The data is
|
|
* prepared by write operations in the emulated response buffer
|
|
*/
|
|
log_debug("read: %d bytes requested\n", len);
|
|
|
|
return -ENOSYS;
|
|
}
|
|
|
|
static int handle_fw_get_name_version(struct tkey_emul_priv *priv)
|
|
{
|
|
/* USB format: 0x52 0x02 [tk1 ] [name1] [version] */
|
|
priv->resp[0] = USB_FRAME_MARKER;
|
|
priv->resp[1] = USB_RSP_NAME_VERSION;
|
|
memcpy(priv->resp + 2, "tk1 ", 4);
|
|
|
|
/* name1 changes based on firmware vs app mode */
|
|
if (priv->app_loaded)
|
|
memcpy(priv->resp + 6, "sign", 4);
|
|
else
|
|
memcpy(priv->resp + 6, "mkdf", 4);
|
|
put_unaligned_le32(4, priv->resp + 10);
|
|
priv->resp_len = 14;
|
|
log_debug("GET_NAME_VERSION (mode=%s)\n",
|
|
priv->app_loaded ? "app" : "firmware");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int handle_fw_get_udi(struct tkey_emul_priv *priv)
|
|
{
|
|
/* UDI is only available in firmware mode */
|
|
if (priv->app_loaded) {
|
|
priv->resp_len = 0;
|
|
log_debug("GET_UDI rejected (app mode)\n");
|
|
} else {
|
|
priv->resp[0] = USB_FRAME_MARKER;
|
|
priv->resp[1] = USB_RSP_GET_UDI;
|
|
priv->resp[2] = STATUS_OK;
|
|
memcpy(priv->resp + 3, priv->udi, 8);
|
|
priv->resp_len = 11;
|
|
log_debug("GET_UDI OK\n");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int handle_fw_load_app(struct tkey_emul_priv *priv, const u8 *data)
|
|
{
|
|
/* App size is in bytes 2-5 (big endian) */
|
|
priv->app_size = get_unaligned_be32(data + 2);
|
|
|
|
/* Simple ACK - just return status */
|
|
priv->resp[0] = STATUS_OK;
|
|
priv->resp_len = 1;
|
|
log_debug("LOAD_APP (size=%u)\n", priv->app_size);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int handle_fw_load_app_data(struct tkey_emul_priv *priv, const u8 *data)
|
|
{
|
|
int chunk_size = get_unaligned_be32(data + 2);
|
|
|
|
priv->total_loaded += chunk_size;
|
|
|
|
/* Simple ACK */
|
|
priv->resp[0] = STATUS_OK;
|
|
priv->resp_len = 1;
|
|
|
|
if (priv->total_loaded >= priv->app_size) {
|
|
/* App fully loaded - enter app mode */
|
|
priv->app_loaded = true;
|
|
priv->total_loaded = 0;
|
|
log_debug("App loaded, entering app mode\n");
|
|
} else {
|
|
log_debug("LOAD_APP_DATA (%u/%u)\n",
|
|
priv->total_loaded, priv->app_size);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int handle_firmware_cmd(struct udevice *dev, u8 cmd, const u8 *data)
|
|
{
|
|
struct tkey_emul_priv *priv = dev_get_priv(dev);
|
|
|
|
switch (cmd) {
|
|
case FW_CMD_GET_NAME_VERSION:
|
|
return handle_fw_get_name_version(priv);
|
|
case FW_CMD_GET_UDI:
|
|
return handle_fw_get_udi(priv);
|
|
case FW_CMD_LOAD_APP:
|
|
return handle_fw_load_app(priv, data);
|
|
case FW_CMD_LOAD_APP_DATA:
|
|
return handle_fw_load_app_data(priv, data);
|
|
default:
|
|
log_err("Unknown firmware command %02x\n", cmd);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
static int handle_app_get_pubkey(struct tkey_emul_priv *priv)
|
|
{
|
|
/*
|
|
* Response format: 1-byte response code (0x02) + 32-byte pubkey
|
|
* tkey_get_pubkey() expects this format and skips the response code
|
|
*/
|
|
priv->resp[0] = 0x02; /* Response code for GET_PUBKEY */
|
|
memcpy(priv->resp + 1, priv->pubkey, 32);
|
|
priv->resp_len = 33;
|
|
log_debug("GET_PUBKEY\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int handle_app_cmd(struct udevice *dev, u8 cmd)
|
|
{
|
|
struct tkey_emul_priv *priv = dev_get_priv(dev);
|
|
|
|
if (!priv->app_loaded) {
|
|
log_err("App command sent but not in app mode\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
switch (cmd) {
|
|
case APP_CMD_GET_PUBKEY:
|
|
return handle_app_get_pubkey(priv);
|
|
default:
|
|
log_err("Unknown app command %02x\n", cmd);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
static int tkey_emul_write(struct udevice *dev, const void *buf, int len)
|
|
{
|
|
struct tkey_emul_plat *plat = dev_get_plat(dev);
|
|
const u8 *data = buf;
|
|
u8 header, endpoint, cmd;
|
|
int ret;
|
|
|
|
/* Simulate device disconnection */
|
|
if (plat->disconnected)
|
|
return -EIO;
|
|
|
|
if (len < 2)
|
|
return -EINVAL;
|
|
|
|
header = data[0];
|
|
endpoint = (header & FRAME_ENDPOINT_MASK) >> FRAME_ENDPOINT_SHIFT;
|
|
cmd = data[1];
|
|
|
|
log_debug("header %02x endpoint %u cmd %02x\n", header, endpoint, cmd);
|
|
|
|
/* Route to appropriate endpoint handler */
|
|
if (endpoint == ENDPOINT_FIRMWARE) {
|
|
ret = handle_firmware_cmd(dev, cmd, data);
|
|
} else if (endpoint == ENDPOINT_APP) {
|
|
ret = handle_app_cmd(dev, cmd);
|
|
} else {
|
|
log_err("Unknown endpoint %u\n", endpoint);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return ret ? ret : len;
|
|
}
|
|
|
|
static int tkey_emul_read_all(struct udevice *dev, void *buf, int maxlen,
|
|
int timeout_ms)
|
|
{
|
|
struct tkey_emul_plat *plat = dev_get_plat(dev);
|
|
struct tkey_emul_priv *priv = dev_get_priv(dev);
|
|
int len;
|
|
|
|
/* Simulate device disconnection */
|
|
if (plat->disconnected)
|
|
return -EIO;
|
|
|
|
len = min(priv->resp_len, maxlen);
|
|
|
|
log_debug("read_all: %d bytes max, returning %d bytes\n", maxlen, len);
|
|
|
|
/* Copy the raw USB response data including the 0x52 marker */
|
|
if (len > 0)
|
|
memcpy(buf, priv->resp, len);
|
|
|
|
return len;
|
|
}
|
|
|
|
int tkey_emul_reset_for_test(struct udevice *dev)
|
|
{
|
|
struct tkey_emul_priv *priv = dev_get_priv(dev);
|
|
|
|
/* Reset to firmware mode */
|
|
priv->app_loaded = false;
|
|
priv->total_loaded = 0;
|
|
priv->resp_len = 0;
|
|
log_debug("Reset emulator to firmware mode\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
int tkey_emul_set_pubkey_for_test(struct udevice *dev, const void *pubkey)
|
|
{
|
|
struct tkey_emul_priv *priv = dev_get_priv(dev);
|
|
|
|
memcpy(priv->pubkey, pubkey, 32);
|
|
log_debug("Set test pubkey\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
int tkey_emul_set_app_mode_for_test(struct udevice *dev, bool app_mode)
|
|
{
|
|
/*
|
|
* Only set app_loaded if device is active (has priv data).
|
|
* After device_remove(), priv is freed, so we can't access it.
|
|
* When device is re-probed, it will start in firmware mode by default.
|
|
*/
|
|
if (device_active(dev)) {
|
|
struct tkey_emul_priv *priv = dev_get_priv(dev);
|
|
|
|
priv->app_loaded = app_mode;
|
|
}
|
|
|
|
log_debug("Set emulator to %s mode\n", app_mode ? "app" : "firmware");
|
|
|
|
return 0;
|
|
}
|
|
|
|
int tkey_emul_set_connected_for_test(struct udevice *dev, bool connected)
|
|
{
|
|
struct tkey_emul_plat *plat = dev_get_plat(dev);
|
|
|
|
plat->disconnected = !connected;
|
|
log_debug("Set emulator %s\n", connected ? "connected" : "disconnected");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tkey_emul_probe(struct udevice *dev)
|
|
{
|
|
struct tkey_emul_plat *plat = dev_get_plat(dev);
|
|
struct tkey_emul_priv *priv = dev_get_priv(dev);
|
|
int i;
|
|
|
|
/* Fail probe if device is disconnected */
|
|
if (plat->disconnected) {
|
|
log_debug("probe failed - device disconnected\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
/* Generate a deterministic UDI based on device name */
|
|
for (i = 0; i < 8; i++)
|
|
priv->udi[i] = 0xa0 + i;
|
|
|
|
/* Generate a deterministic public key */
|
|
for (i = 0; i < 32; i++)
|
|
priv->pubkey[i] = 0x50 + (i & 0xf);
|
|
|
|
log_debug("init with UDI: ");
|
|
for (i = 0; i < 8; i++)
|
|
log_debug("%02x", priv->udi[i]);
|
|
log_debug("\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* TKey uclass operations */
|
|
static const struct tkey_ops tkey_emul_ops = {
|
|
.read = tkey_emul_read,
|
|
.write = tkey_emul_write,
|
|
.read_all = tkey_emul_read_all,
|
|
};
|
|
|
|
static const struct udevice_id tkey_emul_ids[] = {
|
|
{ .compatible = "tkey,emul" },
|
|
{ }
|
|
};
|
|
|
|
U_BOOT_DRIVER(tkey_emul) = {
|
|
.name = "tkey_emul",
|
|
.id = UCLASS_TKEY,
|
|
.of_match = tkey_emul_ids,
|
|
.probe = tkey_emul_probe,
|
|
.ops = &tkey_emul_ops,
|
|
.priv_auto = sizeof(struct tkey_emul_priv),
|
|
.plat_auto = sizeof(struct tkey_emul_plat),
|
|
};
|