From patchwork Sun Oct 19 07:23:03 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 627 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1760858629; bh=6Aw8gl03h+ZkrcoyNt14Dx4x0FMnFw5/8+cwH7bOETM=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=SLNjKcixmE+v5MMI3VjFaogW08q/D5TNvUdUov1KfB5+M18kshbSMrmnT30oYiDDu 9KZ1/9WuxJRzTnSjpN5Z5dB3kCgWRTpy03DgCNxzVWR3VhVibOF1UvFHpWiyeEUbuQ jj3u/522ZQOjudovHfpDElYMB5++S5K8605ZYktrED1QRrOBNV/CZBOH/swZc5oaaN CDMmYAqwJZRFxk7zWj+W8PG+T8z7PmbZZ4y/9DOWR86PvnakZ6RdpoeXqV5m70w6XE XjegwxFp7BdTQ4jPRkBtaNp5oIhyG1BAJK/ibcZplvwYAYkLeOwdIA/5LEAdIS2Zgl inGwYcBRjla+Q== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id BB62E681BB for ; Sun, 19 Oct 2025 01:23:49 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id 9GgFrrnTxKcT for ; Sun, 19 Oct 2025 01:23:49 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1760858628; bh=6Aw8gl03h+ZkrcoyNt14Dx4x0FMnFw5/8+cwH7bOETM=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=QhMMX6gtc58vbJ8DWjaVZ0BCqIG9kk5/OgPNPvPo70hbjdAx9Q152bTc64FrPC57c 8YPT3PeSHutrKv6Jp0WAC1/Vq4s4YxxurcTltSBHI8PguMmpgmPwUXSuBDbTqrTNFG dmZ+kjVAkm06w/ta8icfKAvB37HWQsvyt9id7tnbKL5ghM0MHro12aoJnPPVQ95G6y QQWEqoHKx7DNToV/9Lh87us/64m9tR8L4X/Rhxp+Bl9tIApy3/D8YcXRnY/WnL3F4N sgkqo6MDc+ylFDil+NifW8nzduGGnRZpsaMYzuEz2k4q95LF9zs5nSmj5hHgfe5gVN qc6NLWlM4Zw4w== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id AF88B68168 for ; Sun, 19 Oct 2025 01:23:48 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1760858627; bh=F00ZRlb77qpx8v9IFomSbxCXzaf09022FziAi4whKHg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=odv2hZB3ltsml3/3kGC7fvjAxMo7zte5+Z2QHzmzHN7nl0BQZ6W2nlOwQV5j5CMtY 0C2r6dC+nCVAdrhuXYETTitU1XQ5OICu2NhdyWl42YvgVfR7cNXuFgolCWQxWOSZ8N RXl+Rx16HMbQlxI47r88I3SyfACkIENnIVOKsBw+P6SUxZOdL1v5ugT+kG4TBjwfME kGDUdluBr57oqNc0rN57qDTyXtsOKcwgpDpLkNen2SE/T7ERHUCOQUtGigIS1DL5/N u16rhReH/FgX812SFoqTGh0HNPoKg7fJC5HuQ5lKIrV/GrJ3fGTgCZCmHojmqB03l4 sSA9ja1bYOjQA== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 798F56807A; Sun, 19 Oct 2025 01:23:47 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id 8EEbz1T7qLIb; Sun, 19 Oct 2025 01:23:47 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1760858625; bh=XWB1YN/Jl5WNidZgNL5nZmTaAdiHqsmv9V+o17F6NjM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=I4TozPJ7RxMgpKnRUDcWMdI+emxxGcdi3oUOGbAKO8GbDMWmTvakKh1LccYnTeNDy ++WCngo7+i20v+DUXglUhuDcZLiNS4WRrmZj+p+PTXBGi/SIiLbfR+k7WI64fZfMq2 vjd/ZqYnxTKp6uu3XYhDf+k/r8WozbzvhdcDzEKuL5ak12YGrRHMi0PbaMOu32YctM wb0yfIgAg41ziMOG4s8qZZcxmjy2gV5bbVLoiiHwnpXgwoaZcdDuBANGz4x0VlW3Iv VE67m9jjzmAwvbly7r2DcOwP2Z+8c3xocXmUj77jePlx7yzJBIF5MIMBwctka3RmQJ 46u8sUuhCS5mw== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id 9ABE26810E; Sun, 19 Oct 2025 01:23:45 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Sun, 19 Oct 2025 01:23:03 -0600 Message-ID: <20251019072313.3235339-4-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20251019072313.3235339-1-sjg@u-boot.org> References: <20251019072313.3235339-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: PH3A2LUSZX2ALSRMQ26OYH4VQ4CBKZHD X-Message-ID-Hash: PH3A2LUSZX2ALSRMQ26OYH4VQ4CBKZHD X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass , Claude X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 3/8] tkey: Provide a uclass for the Tillitis TKey List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass The TKey is a USB device which can run ephemeral firmware and perform cyrptographic operations. Add a uclass for the communication layer. Co-developed-by: Claude Signed-off-by: Simon Glass --- drivers/misc/Kconfig | 11 + drivers/misc/Makefile | 1 + drivers/misc/tkey-uclass.c | 741 +++++++++++++++++++++++++++++++++++++ include/dm/uclass-id.h | 1 + include/tkey.h | 227 ++++++++++++ 5 files changed, 981 insertions(+) create mode 100644 drivers/misc/tkey-uclass.c create mode 100644 include/tkey.h diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 9559b2c56d5..a352fa5fee0 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -33,6 +33,17 @@ config TPL_MISC set of generic read, write and ioctl methods may be used to access the device. +config TKEY + bool "TKey security token support" + depends on DM + default y if SANDBOX + select BLAKE2 + help + Enable driver model support for Tillitis TKey security tokens. + This provides a common interface for TKey operations including + reading device information, getting the unique device identifier + (UDI), and communicating with the device. + config VPL_MISC bool "Enable Driver Model for Misc drivers in VPL" depends on VPL_DM diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index fd45491551a..23fd8f8ef3b 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -5,6 +5,7 @@ obj-$(CONFIG_$(PHASE_)MISC) += misc-uclass.o obj-$(CONFIG_$(PHASE_)NVMEM) += nvmem.o +obj-$(CONFIG_TKEY) += tkey-uclass.o obj-$(CONFIG_$(PHASE_)CROS_EC) += cros_ec.o obj-$(CONFIG_$(PHASE_)CROS_EC_SANDBOX) += cros_ec_sandbox.o diff --git a/drivers/misc/tkey-uclass.c b/drivers/misc/tkey-uclass.c new file mode 100644 index 00000000000..4696f9c8d83 --- /dev/null +++ b/drivers/misc/tkey-uclass.c @@ -0,0 +1,741 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2025 Canonical Ltd + * + * Tillitis TKey security token uclass + */ + +#define LOG_CATEGORY UCLASS_TKEY + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* TKey Protocol Constants */ +#define TKEY_FRAME_HEADER_SIZE 1 +#define TKEY_MAX_DATA_SIZE 128 +#define TKEY_MAX_FRAME_SIZE (TKEY_FRAME_HEADER_SIZE + \ + TKEY_MAX_DATA_SIZE) + +/* Frame Header Bits */ +#define TKEY_FRAME_ID_MASK 0x60 +#define TKEY_FRAME_ENDPOINT_MASK 0x18 +#define TKEY_FRAME_STATUS_MASK 0x04 +#define TKEY_FRAME_LEN_MASK 0x03 + +/* Frame ID Values */ +#define TKEY_FRAME_ID_CMD 0 +#define TKEY_FRAME_ID_RSP 1 + +/* Endpoint Values */ +#define TKEY_ENDPOINT_FIRMWARE 2 +#define TKEY_ENDPOINT_APP 3 + +/* Data Length Values */ +#define TKEY_LEN_1_BYTE 0 +#define TKEY_LEN_4_BYTES 1 +#define TKEY_LEN_32_BYTES 2 +#define TKEY_LEN_128_BYTES 3 + +/* Status Values */ +#define TKEY_STATUS_OK 0 +#define TKEY_STATUS_ERROR 0x04 + +/* Firmware Commands */ +#define TKEY_FW_CMD_NAME_VERSION 0x01 +#define TKEY_FW_CMD_LOAD_APP 0x03 +#define TKEY_FW_CMD_LOAD_APP_DATA 0x05 +#define TKEY_FW_CMD_GET_UDI 0x08 + +/* Signer App Commands */ +#define TKEY_APP_CMD_GET_PUBKEY 0x01 +#define TKEY_APP_RSP_GET_PUBKEY 0x02 + +/* Constants */ +#define TKEY_USS_SIZE 32 +#define TKEY_PUBKEY_SIZE 32 + +/* Timeouts (ms) */ +#define TKEY_TIMEOUT_MS 1000 +#define TKEY_LOAD_TIMEOUT_MS 2000 + +/* TKey frame structure */ +struct tkey_frame { + u8 header; + u8 data[TKEY_MAX_DATA_SIZE]; +}; + +/** + * make_hdr() - Build a TKey frame header byte + * + * Constructs a TKey protocol frame header by encoding the frame ID, endpoint, + * status, and length code into a single byte according to the TKey protocol + * specification. + * + * Frame header format (8 bits): + * Bit 7: Reserved (always 0) + * Bits 6-5: Frame ID (0=CMD, 1=RSP, 2-3=reserved) + * Bits 4-3: Endpoint (0-1=reserved, 2=Firmware, 3=App) + * Bit 2: Status (0=OK, 1=Error) + * Bits 1-0: Length code (0=1 byte, 1=4 bytes, 2=32 bytes, 3=128 bytes) + * + * @id: Frame ID (TKEY_FRAME_ID_CMD=0 or TKEY_FRAME_ID_RSP=1) + * @endpoint: Target endpoint (TKEY_ENDPOINT_FIRMWARE=2 or TKEY_ENDPOINT_APP=3) + * @status: Status flag (TKEY_STATUS_OK=0 or TKEY_STATUS_ERROR=1) + * @len: Length code (TKEY_LEN_1_BYTE=0, TKEY_LEN_4_BYTES=1, + * TKEY_LEN_32_BYTES=2, TKEY_LEN_128_BYTES=3) + * Return: 8-bit frame header value + */ +static uint make_hdr(uint id, uint endpoint, uint status, uint len) +{ + return ((id & 0x3) << 5) | ((endpoint & 0x3) << 3) | + ((status & 0x1) << 2) | (len & 0x3); +} + +static int tkey_send_frame(struct udevice *dev, + const struct tkey_frame *frame, int len) +{ + u8 buffer[TKEY_MAX_FRAME_SIZE]; + int total_len = TKEY_FRAME_HEADER_SIZE + len; + int ret; + + log_debug("Sending frame - header=%02x, len=%x\n", frame->header, len); + + /* Build frame */ + buffer[0] = frame->header; + if (len > 0) + memcpy(&buffer[1], frame->data, len); + + /* Send via generic write */ + ret = tkey_write(dev, buffer, total_len); + if (ret < 0) { + log_debug("Frame send failed\n"); + return ret; + } + log_debug("Frame sent successfully\n"); + + return total_len; +} + +static int tkey_recv_frame(struct udevice *dev, struct tkey_frame *frame, + int timeout_ms) +{ + const struct tkey_ops *ops; + int len, ret; + u8 buf[256]; + + log_debug("Receiving frame...\n"); + ops = tkey_get_ops(dev); + + /* Try read_all first for USB devices that send raw responses */ + if (ops->read_all) { + log_debug("Using read_all for USB raw response reception\n"); + ret = tkey_read_all(dev, buf, sizeof(buf), timeout_ms); + if (ret < 0) { + log_debug("Read_all failed: %d\n", ret); + return ret; + } + if (ret < 1) { + log_debug("Read_all got no data\n"); + return -EIO; + } + log_debug("USB raw response: %x bytes received\n", ret); + + /* + * USB TKey sends raw responses, not framed responses. Create a + * synthetic frame with a success header + */ + frame->header = make_hdr(TKEY_FRAME_ID_RSP, + TKEY_ENDPOINT_FIRMWARE, + TKEY_STATUS_OK, TKEY_LEN_128_BYTES); + + /* Copy the raw response data */ + len = 0; + if (ret > 0) { + len = min(ret, (int)TKEY_MAX_DATA_SIZE); + memcpy(frame->data, buf, len); + } + + log_debug("USB raw response converted to frame: header=%02x, " + "data_len=%x\n", frame->header, len); + return TKEY_FRAME_HEADER_SIZE + len; + } + + /* Fallback to byte-by-byte reading for serial devices */ + log_debug("Using byte-by-byte frame reception\n"); + + /* Read header first */ + ret = tkey_read(dev, &frame->header, 1, timeout_ms); + if (ret != 1) { + log_debug("Header read failed: got %x bytes\n", ret); + return (ret < 0) ? ret : -EIO; + } + + log_debug("Received header: %02x\n", frame->header); + + /* Decode data length from header */ + switch (frame->header & TKEY_FRAME_LEN_MASK) { + case TKEY_LEN_1_BYTE: + len = 1; + break; + case TKEY_LEN_4_BYTES: + len = 4; + break; + case TKEY_LEN_32_BYTES: + len = 32; + break; + case TKEY_LEN_128_BYTES: + len = 128; + break; + default: + log_debug("Invalid length code: %02x\n", + frame->header & TKEY_FRAME_LEN_MASK); + return -EINVAL; + } + + log_debug("Expected data length: %x bytes\n", len); + + /* Read data bytes if any */ + if (len > 0) { + ret = tkey_read(dev, frame->data, len, timeout_ms); + if (ret != len) { + log_debug("Data read failed: expected %x, got %x " + "bytes\n", len, ret); + return (ret < 0) ? ret : -EIO; + } + } + + log_debug("got frame: %x total bytes\n", TKEY_FRAME_HEADER_SIZE + len); + + return TKEY_FRAME_HEADER_SIZE + len; +} + +int tkey_read(struct udevice *dev, void *buf, int len, int timeout_ms) +{ + const struct tkey_ops *ops = tkey_get_ops(dev); + + return ops->read(dev, buf, len, timeout_ms); +} + +int tkey_write(struct udevice *dev, const void *buf, int len) +{ + const struct tkey_ops *ops = tkey_get_ops(dev); + + return ops->write(dev, buf, len); +} + +int tkey_read_all(struct udevice *dev, void *buf, int maxlen, int timeout_ms) +{ + const struct tkey_ops *ops = tkey_get_ops(dev); + + /* Use read_all if available, otherwise fall back to regular read */ + if (ops->read_all) + return ops->read_all(dev, buf, maxlen, timeout_ms); + + return ops->read(dev, buf, maxlen, timeout_ms); +} + +int tkey_get_udi(struct udevice *dev, void *udi) +{ + struct tkey_frame cmd_frame, rsp_frame; + int ret; + + /* Build command frame */ + cmd_frame.header = make_hdr(TKEY_FRAME_ID_CMD, TKEY_ENDPOINT_FIRMWARE, + 0, TKEY_LEN_1_BYTE); + cmd_frame.data[0] = TKEY_FW_CMD_GET_UDI; + + /* Send command */ + ret = tkey_send_frame(dev, &cmd_frame, 1); + if (ret < 0) + return ret; + + /* Receive response */ + ret = tkey_recv_frame(dev, &rsp_frame, TKEY_TIMEOUT_MS); + if (ret < 0) + return ret; + + /* Check response status */ + if (rsp_frame.header & TKEY_FRAME_STATUS_MASK) { + /* GetUDI is a firmware command - check if we're in app mode */ + char name0[TKEY_NAME_SIZE], name1[TKEY_NAME_SIZE]; + u32 version; + + if (!tkey_get_name_version(dev, name0, name1, &version)) { + if (!strcmp(name0, "tk1 ") && !strcmp(name1, "sign")) { + log_debug("GetUDI failed - device is in app mode, UDI only available in firmware mode\n"); + return -ENOTSUPP; + } + } + + log_debug("GetUDI failed with error status, error code=%02x\n", + ret > 1 ? rsp_frame.data[0] : 0); + return -EIO; + } + + /* Extract UDI */ + if (ret >= (TKEY_FRAME_HEADER_SIZE + TKEY_UDI_SIZE)) { + /* + * For USB responses, check if we have the expected response + * pattern. USB TKey UDI responses have format: [padding...] + * [0x52] [0x09] [status] [UDI...] + */ + int ofs = -1; + + /* Look for the USB response pattern 0x52 0x09 */ + for (int i = 0; i < ret - TKEY_UDI_SIZE - 3; i++) { + if (rsp_frame.data[i] == 0x52 && + rsp_frame.data[i + 1] == 0x09) { + ofs = i; + break; + } + } + + if (ofs >= 0) { + /* USB format: UDI starts after 0x52 0x09 status */ + memcpy(udi, &rsp_frame.data[ofs + 3], TKEY_UDI_SIZE); + } else { + /* Standard format: UDI starts at offset 0 */ + memcpy(udi, rsp_frame.data, TKEY_UDI_SIZE); + } + return 0; + } + + return -EINVAL; +} + +int tkey_get_name_version(struct udevice *dev, char name0[TKEY_NAME_SIZE], + char name1[TKEY_NAME_SIZE], u32 *version) +{ + struct tkey_frame cmd_frame, rsp_frame; + int ret; + + /* Build command frame */ + cmd_frame.header = make_hdr(TKEY_FRAME_ID_CMD, TKEY_ENDPOINT_FIRMWARE, + 0, TKEY_LEN_1_BYTE); + cmd_frame.data[0] = TKEY_FW_CMD_NAME_VERSION; + + /* Send command */ + ret = tkey_send_frame(dev, &cmd_frame, 1); + if (ret < 0) + return ret; + + /* Receive response */ + ret = tkey_recv_frame(dev, &rsp_frame, TKEY_TIMEOUT_MS); + if (ret < 0) + return ret; + + /* Check response status and handle different modes */ + if (rsp_frame.header & TKEY_FRAME_STATUS_MASK) { + /* + * Error status set - could be app mode responding to + * firmware command + */ + log_debug("GetNameVersion status bit set, header=%02x, " + "error code=%02x\n", rsp_frame.header, + ret > 1 ? rsp_frame.data[0] : 0); + + /* + * In app mode, TKey responds with error status to firmware + * commands. Try to decode as app mode response + */ + if (ret >= 1 && rsp_frame.data[0] == 0x00) { + /* App mode: return standard app identifiers */ + strcpy(name0, "tk1 "); + strcpy(name1, "sign"); + *version = 1; /* Default app version */ + log_debug("Detected app mode response, using default " + "app identifiers\n"); + return 0; + } + return -EIO; + } + + /* Parse response data */ + if (ret >= 13) { /* Header + 4 + 4 + 4 bytes */ + /* + * For USB responses, check if we have the expected response + * pattern. USB TKey responses have format: [padding...] [0x52] + * [0x02] [tk1 ] [mkdf] [version] + */ + int ofs = -1; + + /* Look for the USB response pattern 0x52 0x02 */ + for (int i = 0; i < ret - 13; i++) { + if (rsp_frame.data[i] == 0x52 && + rsp_frame.data[i + 1] == 0x02) { + ofs = i; + break; + } + } + + if (ofs >= 0) { + /* USB format: found pattern at offset */ + memcpy(name0, &rsp_frame.data[ofs + 2], 4); + name0[4] = '\0'; + memcpy(name1, &rsp_frame.data[ofs + 6], 4); + name1[4] = '\0'; + *version = + get_unaligned_le32(&rsp_frame.data[ofs + 10]); + } else { + /* + * Standard format: skip the first byte (command + * response code) + */ + memcpy(name0, &rsp_frame.data[1], 4); + name0[4] = '\0'; + memcpy(name1, &rsp_frame.data[5], 4); + name1[4] = '\0'; + *version = get_unaligned_le32(&rsp_frame.data[9]); + } + } + + return 0; +} + +int tkey_in_app_mode(struct udevice *dev) +{ + char name0[TKEY_NAME_SIZE], name1[TKEY_NAME_SIZE]; + u32 version; + int ret; + + ret = tkey_get_name_version(dev, name0, name1, &version); + if (ret) + return ret; + + /* Check if in firmware mode */ + if (!strcmp(name0, "tk1 ") && !strcmp(name1, "mkdf")) + return 0; /* Firmware mode */ + + /* Check if in app mode */ + if (!strcmp(name0, "tk1 ") && !strcmp(name1, "sign")) + return 1; /* App mode */ + + /* Unknown mode */ + return -EINVAL; +} + +static int tkey_load_app_header(struct udevice *dev, int app_size, + const void *uss, int uss_size) +{ + struct tkey_frame cmd_frame, rsp_frame; + int ret; + + log_debug("Loading app header, size=%u\n", app_size); + + /* + * Build LOAD_APP command frame with app size (128-byte frame like + * Go app) + */ + cmd_frame.header = make_hdr(TKEY_FRAME_ID_CMD, TKEY_ENDPOINT_FIRMWARE, + TKEY_STATUS_OK, TKEY_LEN_128_BYTES); + cmd_frame.data[0] = TKEY_FW_CMD_LOAD_APP; + /* Pack app size as little-endian 32-bit */ + cmd_frame.data[1] = app_size & 0xff; + cmd_frame.data[2] = (app_size >> 8) & 0xff; + cmd_frame.data[3] = (app_size >> 16) & 0xff; + cmd_frame.data[4] = (app_size >> 24) & 0xff; + + /* Include USS if provided */ + if (uss && uss_size > 0) { + blake2s_state state; + u8 uss_hash[32]; + + /* Hash the USS using BLAKE2s to get 32 bytes */ + ret = blake2s_init(&state, 32); + if (ret) { + log_debug("Failed to init BLAKE2s\n"); + return ret; + } + + ret = blake2s_update(&state, uss, uss_size); + if (ret) { + log_debug("Failed to update BLAKE2s\n"); + return ret; + } + + ret = blake2s_final(&state, uss_hash, 32); + if (ret) { + log_debug("Failed to finalize BLAKE2s\n"); + return ret; + } + + /* USS present flag */ + cmd_frame.data[5] = 1; + /* Copy USS hash (32 bytes) */ + memcpy(&cmd_frame.data[6], uss_hash, 32); + /* Pad remaining bytes with zeros */ + memset(&cmd_frame.data[38], '\0', 128 - 38); + + log_debug("USS hash included in app header\n"); + } else { + /* No USS - set flag to 0 and pad with zeros */ + memset(&cmd_frame.data[5], '\0', 128 - 5); + } + + /* Send command */ + ret = tkey_send_frame(dev, &cmd_frame, 128); + if (ret < 0) + return ret; + + /* Receive response */ + ret = tkey_recv_frame(dev, &rsp_frame, TKEY_LOAD_TIMEOUT_MS); + if (ret < 0) + return ret; + + /* Check response status */ + if (rsp_frame.header & TKEY_STATUS_ERROR) { + log_debug("Load app header failed with error status\n"); + return -EIO; + } + + log_debug("App header loaded successfully\n"); + + return 0; +} + +static int tkey_load_app_data(struct udevice *dev, const void *data, int size) +{ + struct tkey_frame cmd_frame, rsp_frame; + int offset = 0; + int ret; + + log_debug("Loading app data, %u bytes\n", size); + + while (offset < size) { + int todo = min(size - offset, TKEY_MAX_DATA_SIZE - 1); + u8 len_code; + + /* Determine length code for chunk */ + if (todo <= 1) + len_code = TKEY_LEN_1_BYTE; + else if (todo <= 4) + len_code = TKEY_LEN_4_BYTES; + else if (todo <= 32) + len_code = TKEY_LEN_32_BYTES; + else + len_code = TKEY_LEN_128_BYTES; + + /* + * Build LOAD_APP_DATA command (always use 128-byte frames + * like Go app) + */ + cmd_frame.header = make_hdr(TKEY_FRAME_ID_CMD, + TKEY_ENDPOINT_FIRMWARE, + TKEY_STATUS_OK, + TKEY_LEN_128_BYTES); + cmd_frame.data[0] = TKEY_FW_CMD_LOAD_APP_DATA; + memcpy(&cmd_frame.data[1], data + offset, todo); + + /* Pad remaining bytes with zeros */ + if (todo + 1 < 128) + memset(&cmd_frame.data[todo + 1], '\0', + 128 - (todo + 1)); + + /* Send chunk (always 128 bytes like Go app) */ + ret = tkey_send_frame(dev, &cmd_frame, 128); + if (ret < 0) + return ret; + + /* Receive response */ + ret = tkey_recv_frame(dev, &rsp_frame, TKEY_LOAD_TIMEOUT_MS); + if (ret < 0) + return ret; + + /* Check response status */ + if (rsp_frame.header & TKEY_STATUS_ERROR) { + log_debug("Load app data failed at offset %u\n", + offset); + return -EIO; + } + + offset += todo; + log_debug("Loaded chunk: %u/%u bytes\n", offset, size); + schedule(); + } + + log_debug("App data loaded successfully\n"); + + return 0; +} + +int tkey_load_app_with_uss(struct udevice *dev, const void *app_data, + int app_size, const void *uss, int uss_size) +{ + int ret; + + /* Check if we're in firmware mode first */ + ret = tkey_in_app_mode(dev); + if (ret < 0) { + log_debug("Failed to check device mode (error %d)\n", ret); + return ret; + } + + if (ret) { + log_debug("Device must be in firmware mode to load app\n"); + return -ENOTSUPP; + } + + log_debug("Loading app (%u bytes)...\n", app_size); + + /* Send app header with size and USS (if provided) */ + ret = tkey_load_app_header(dev, app_size, uss, uss_size); + if (ret) { + log_debug("Failed to send app header (error %d)\n", ret); + return ret; + } + + /* Send app data */ + ret = tkey_load_app_data(dev, app_data, app_size); + if (ret) { + log_debug("Failed to send app data (error %d)\n", ret); + return ret; + } + + log_debug("App loaded successfully\n"); + + return 0; +} + +int tkey_load_app(struct udevice *dev, const void *app_data, int app_size) +{ + return tkey_load_app_with_uss(dev, app_data, app_size, NULL, 0); +} + +int tkey_get_pubkey(struct udevice *dev, void *pubkey) +{ + struct tkey_frame cmd_frame, rsp_frame; + int ret; + + /* Build GET_PUBKEY command frame */ + cmd_frame.header = make_hdr(TKEY_FRAME_ID_CMD, TKEY_ENDPOINT_APP, + TKEY_STATUS_OK, TKEY_LEN_1_BYTE); + cmd_frame.data[0] = TKEY_APP_CMD_GET_PUBKEY; + + log_debug("Getting public key from signer app\n"); + + /* Send command */ + ret = tkey_send_frame(dev, &cmd_frame, 1); + if (ret < 0) + return ret; + + /* Receive response */ + ret = tkey_recv_frame(dev, &rsp_frame, TKEY_TIMEOUT_MS); + if (ret < 0) + return ret; + + /* Check response status */ + if (rsp_frame.header & TKEY_FRAME_STATUS_MASK) { + log_debug("GetPubkey failed with error status\n"); + return -EIO; + } + + /* Extract public key (32 bytes) from response */ + if (ret >= TKEY_FRAME_HEADER_SIZE + TKEY_PUBKEY_SIZE) { + memcpy(pubkey, rsp_frame.data, TKEY_PUBKEY_SIZE); + log_debug("Public key retrieved successfully\n"); + return 0; + } + log_debug("GetPubkey response too short: %d bytes\n", ret); + + return -EINVAL; +} + +int tkey_derive_disk_key(struct udevice *dev, const void *app_data, + int app_size, const void *uss, int uss_size, + void *disk_key, void *pubkey, void *key_hash) +{ + int ret; + + /* Load the signer app with USS */ + log_debug("Loading signer app with USS for disk key derivation\n"); + ret = tkey_load_app_with_uss(dev, app_data, app_size, uss, + uss_size); + if (ret == -ENOTSUPP) { + /* Already in app mode - continue */ + log_debug("App already loaded, retrieving key\n"); + } else if (ret) { + log_debug("Failed to load app (error %d)\n", ret); + return ret; + } + + /* Get public key from signer */ + ret = tkey_get_pubkey(dev, pubkey); + if (ret) { + log_debug("Failed to get public key (error %d)\n", ret); + return ret; + } + + log_debug("Public key retrieved\n"); + + /* Derive disk encryption key from public key using BLAKE2b */ + ret = blake2b(disk_key, 32, pubkey, 32, NULL, 0); + if (ret) { + log_debug("Failed to derive disk key (error %d)\n", ret); + return ret; + } + + log_debug("Disk encryption key derived\n"); + + /* Generate verification hash if requested */ + if (key_hash) { + ret = blake2b(key_hash, 32, disk_key, 32, NULL, 0); + if (ret) { + log_debug("Failed to generate verification hash " + "(error %d)\n", ret); + return ret; + } + log_debug("Verification hash generated\n"); + } + + return 0; +} + +int tkey_derive_wrapping_key(struct udevice *dev, const char *password, + void *wrapping_key) +{ + u8 udi[TKEY_UDI_SIZE]; + blake2b_state state; + int ret; + + /* Get UDI from device (only available in firmware mode) */ + ret = tkey_get_udi(dev, udi); + if (ret) { + log_debug("Failed to get UDI (error %d)\n", ret); + return ret; + } + + /* Derive wrapping key using BLAKE2b(UDI || password) */ + ret = blake2b_init(&state, TKEY_WRAPPING_KEY_SIZE); + if (ret) + return ret; + + ret = blake2b_update(&state, udi, TKEY_UDI_SIZE); + if (ret) + return ret; + + ret = blake2b_update(&state, password, strlen(password)); + if (ret) + return ret; + + ret = blake2b_final(&state, wrapping_key, TKEY_WRAPPING_KEY_SIZE); + if (ret) + return ret; + + log_debug("Wrapping key derived from password and UDI\n"); + + return 0; +} + +UCLASS_DRIVER(tkey) = { + .id = UCLASS_TKEY, + .name = "tkey", +}; diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h index 6bc2d440f96..7c553c0bd3b 100644 --- a/include/dm/uclass-id.h +++ b/include/dm/uclass-id.h @@ -149,6 +149,7 @@ enum uclass_id { UCLASS_SYSINFO, /* Device information from hardware */ UCLASS_SYSRESET, /* System reset device */ UCLASS_TCPM, /* TypeC port manager */ + UCLASS_TKEY, /* Tillitis TKey security token */ UCLASS_TEE, /* Trusted Execution Environment device */ UCLASS_THERMAL, /* Thermal sensor */ UCLASS_TIMER, /* Timer device */ diff --git a/include/tkey.h b/include/tkey.h new file mode 100644 index 00000000000..8f4f4c7e512 --- /dev/null +++ b/include/tkey.h @@ -0,0 +1,227 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2025 Canonical Ltd + * + * Tillitis TKey security-token uclass interface + */ + +#ifndef _TKEY_UCLASS_H +#define _TKEY_UCLASS_H + +#include + +struct tkey_frame; +struct udevice; + +/* TKey constants */ +#define TKEY_NAME_SIZE 5 +#define TKEY_CDI_SIZE 32 +#define TKEY_UDI_SIZE 8 +#define TKEY_WRAPPING_KEY_SIZE 32 +#define TKEY_USS_MAX_SIZE 32 +#define TKEY_PUBKEY_SIZE 32 +#define TKEY_DISK_KEY_SIZE 32 +#define TKEY_HASH_SIZE 32 + +/** + * struct tkey_ops - The functions that a TKey driver must implement. + * + * @read: Read data from TKey device + * @write: Write data to TKey device + * @read_all: Read all available data from TKey device (optional) + */ +struct tkey_ops { + /** + * read() - Read data from TKey device + * + * @dev: TKey device + * @buf: Buffer to store read data + * @len: Maximum number of bytes to read + * @timeout_ms: Timeout in milliseconds + * + * Returns: Number of bytes read on success, -ve error on failure + */ + int (*read)(struct udevice *dev, void *buf, int len, int timeout_ms); + + /** + * write() - Write data to TKey device + * + * @dev: TKey device + * @buf: Buffer containing data to write + * @len: Number of bytes to write + * + * Returns: Number of bytes written on success, -ve error on failure + */ + int (*write)(struct udevice *dev, const void *buf, int len); + + /** + * read_all() - Read all available data from TKey device (optional) + * + * @dev: TKey device + * @buf: Buffer to store read data + * @maxlen: Maximum number of bytes to read + * @timeout_ms: Timeout in milliseconds + * + * This method reads all available data in one operation, which is + * more suitable for USB devices that send complete frames. + * + * Returns: Number of bytes read on success, -ve error on failure + */ + int (*read_all)(struct udevice *dev, void *buf, int maxlen, + int timeout_ms); +}; + +#define tkey_get_ops(dev) ((struct tkey_ops *)(dev)->driver->ops) + +/** + * tkey_read() - Read data from TKey device + * + * @dev: TKey device + * @buf: Buffer to store read data + * @len: Maximum number of bytes to read + * @timeout_ms: Timeout in milliseconds + * Return: Number of bytes read on success, -ve error on failure + */ +int tkey_read(struct udevice *dev, void *buf, int len, int timeout_ms); + +/** + * tkey_write() - Write data to TKey device + * + * @dev: TKey device + * @buf: Buffer containing data to write + * @len: Number of bytes to write + * Return: Number of bytes written on success, -ve error on failure + */ +int tkey_write(struct udevice *dev, const void *buf, int len); + +/** + * tkey_read_all() - Read all available data from TKey device + * + * @dev: TKey device + * @buf: Buffer to store read data + * @maxlen: Maximum number of bytes to read + * @timeout_ms: Timeout in milliseconds + * + * Reads all available data in one operation, suitable for USB devices. + * Falls back to regular read if read_all is not implemented. + * + * Return: Number of bytes read on success, -ve error on failure + */ +int tkey_read_all(struct udevice *dev, void *buf, int maxlen, + int timeout_ms); + +/** + * tkey_get_udi() - Get Unique Device Identifier + * + * @dev: TKey device + * @udi: Buffer to store UDI (must be at least 8 bytes) + * Return: 0 on success, -ve error on failure + */ +int tkey_get_udi(struct udevice *dev, void *udi); + +/** + * tkey_get_name_version() - Get device name and version + * + * @dev: TKey device + * @name0: Buffer for first name field (TKEY_NAME_SIZE bytes) + * @name1: Buffer for second name field (TKEY_NAME_SIZE bytes) + * @version: Pointer to store version number + * Return: 0 on success, -ve error on failure + */ +int tkey_get_name_version(struct udevice *dev, char name0[TKEY_NAME_SIZE], + char name1[TKEY_NAME_SIZE], u32 *version); + +/** + * tkey_in_app_mode() - Check if device is in firmware or app mode + * + * @dev: TKey device + * Return: 0 if in firmware mode, 1 if in app mode, -ve error on failure + */ +int tkey_in_app_mode(struct udevice *dev); + +/** + * tkey_load_app() - Load complete app to TKey device + * + * @dev: TKey device + * @app_data: Complete app binary data + * @app_size: Size of app data + * + * This function performs all steps needed to load an app: + * - Verifies device is in firmware mode + * - Sends app header with size + * - Sends app data in chunks + * + * Return: 0 on success, -ve error on failure (-ENOTSUPP if not in + * firmware mode) + */ +int tkey_load_app(struct udevice *dev, const void *app_data, int app_size); + +/** + * tkey_load_app_with_uss() - Load app with User-Supplied Secret + * + * @dev: TKey device + * @app_data: Complete app binary data + * @app_size: Size of app data + * @uss: User-Supplied Secret (password/passphrase) - can be NULL + * @uss_size: Size of USS data (max 32 bytes) + * + * This function performs all steps needed to load an app with USS: + * - Verifies device is in firmware mode + * - Sends app header with size + * - Sends app data in chunks + * - Sends USS if provided + * + * Return: 0 on success, -ve error on failure (-ENOTSUPP if not in + * firmware mode) + */ +int tkey_load_app_with_uss(struct udevice *dev, const void *app_data, + int app_size, const void *uss, int uss_size); + +/** + * tkey_get_pubkey() - Get public key from signer app + * + * @dev: TKey device (must be in app mode with signer loaded) + * @pubkey: Buffer to store public key (must be at least 32 bytes) + * Return: 0 on success, -ve error on failure + */ +int tkey_get_pubkey(struct udevice *dev, void *pubkey); + +/** + * tkey_derive_disk_key() - Derive disk encryption key from USS + * + * @dev: TKey device + * @app_data: Signer app binary data + * @app_size: Size of app data + * @uss: User-Supplied Secret for key derivation + * @uss_size: Size of USS + * @disk_key: Buffer to store derived disk key (32 bytes) + * @pubkey: Buffer to store public key (32 bytes) + * @key_hash: Buffer to store verification hash (32 bytes), or NULL if + * not needed + * + * This function loads the signer app with USS, retrieves the public key, + * and derives a disk encryption key. Optionally generates a verification + * hash. + * + * Return: 0 on success, -ve error on failure + */ +int tkey_derive_disk_key(struct udevice *dev, const void *app_data, + int app_size, const void *uss, int uss_size, + void *disk_key, void *pubkey, void *key_hash); + +/** + * tkey_derive_wrapping_key() - Derive wrapping key from password and UDI + * + * @dev: TKey device (must be in firmware mode to access UDI) + * @password: User-provided password + * @wrapping_key: Buffer to store wrapping key (32 bytes) + * + * This function gets the device UDI and derives a wrapping key using + * BLAKE2b(UDI || password). + * + * Return: 0 on success, -ve error on failure + */ +int tkey_derive_wrapping_key(struct udevice *dev, const char *password, + void *wrapping_key); + +#endif /* _TKEY_UCLASS_H */