From: Simon Glass <sjg@chromium.org> Add a new 'tkey' command that provides an interface to interact with Tillitis TKey security tokens. Subcommands include: - info: Display device information (UDI, name, version, mode) - load: Load and run applications on the TKey - pubkey: Get the public key from a signer app - getkey: Derive disk encryption keys with password and USS This command enables U-Boot to use TKey devices for secure key derivation for full-disk encryption. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- cmd/Kconfig | 10 ++ cmd/Makefile | 1 + cmd/tkey.c | 298 +++++++++++++++++++++++++++++++++++++++++ doc/usage/cmd/tkey.rst | 247 ++++++++++++++++++++++++++++++++++ doc/usage/index.rst | 1 + test/cmd/Makefile | 1 + test/cmd/tkey.c | 67 +++++++++ 7 files changed, 625 insertions(+) create mode 100644 cmd/tkey.c create mode 100644 doc/usage/cmd/tkey.rst create mode 100644 test/cmd/tkey.c diff --git a/cmd/Kconfig b/cmd/Kconfig index 224d5a83987..f4d6e544cc0 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -2390,6 +2390,16 @@ config CMD_MP This enables commands to bringup different processors in multiprocessor cases. +config CMD_TKEY + bool "tkey - Tillitis TKey operations" + depends on TKEY + default y + help + The allows interacting with a Tillitis TKey security token, + including connecting to the device, getting device information and + creating cryptographic wrapping keys from passwords combined with + device secrets. + config CMD_TIMER bool "timer" help diff --git a/cmd/Makefile b/cmd/Makefile index b73725cfe41..28e9c261683 100644 --- a/cmd/Makefile +++ b/cmd/Makefile @@ -189,6 +189,7 @@ obj-$(CONFIG_CMD_TEMPERATURE) += temperature.o obj-$(CONFIG_CMD_TERMINAL) += terminal.o obj-$(CONFIG_CMD_TIME) += time.o obj-$(CONFIG_CMD_TIMER) += timer.o +obj-$(CONFIG_CMD_TKEY) += tkey.o obj-$(CONFIG_CMD_TRACE) += trace.o obj-$(CONFIG_HUSH_PARSER) += test.o obj-$(CONFIG_CMD_TPM) += tpm-common.o diff --git a/cmd/tkey.c b/cmd/tkey.c new file mode 100644 index 00000000000..a269ca86085 --- /dev/null +++ b/cmd/tkey.c @@ -0,0 +1,298 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2025 Canonical Ltd + * + * Command for communicating with Tillitis TKey to create wrapping keys + * from user-provided passwords. + */ + +#include <command.h> +#include <console.h> +#include <dm.h> +#include <hexdump.h> +#include <malloc.h> +#include <time.h> +#include <tkey.h> +#include <asm/unaligned.h> +#include <linux/ctype.h> +#include <linux/delay.h> +#include <linux/string.h> +#include <linux/errno.h> + +static struct udevice *tkey_get_device(void) +{ + struct udevice *dev; + int ret; + + ret = uclass_first_device_err(UCLASS_TKEY, &dev); + if (ret) { + printf("No device found (err %dE)\n", ret); + return NULL; + } + + return dev; +} + +static void print_hex(const char *label, const u8 *data, size_t len) +{ + size_t i; + + printf("%s: ", label); + for (i = 0; i < len; i++) + printf("%02x", data[i]); + printf("\n"); +} + +static int do_tkey_connect(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + + dev = tkey_get_device(); + if (!dev) + return CMD_RET_FAILURE; + + printf("Connected to TKey device\n"); + + return CMD_RET_SUCCESS; +} + +static int do_tkey_info(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + char name0[TKEY_NAME_SIZE], name1[TKEY_NAME_SIZE]; + u8 udi[TKEY_UDI_SIZE]; + struct udevice *dev; + u32 version; + int ret; + + dev = tkey_get_device(); + if (!dev) + return CMD_RET_FAILURE; + + ret = tkey_get_name_version(dev, name0, name1, &version); + if (ret) { + printf("Failed to get device info (err %dE)\n", ret); + return CMD_RET_FAILURE; + } + + printf("Name0: %.4s Name1: %.4s Version: %u\n", name0, name1, version); + + ret = tkey_get_udi(dev, udi); + if (ret) { + if (ret == -ENOTSUPP) + printf("UDI not available - replug device\n"); + else + printf("Failed to get UDI (err %dE)\n", ret); + return CMD_RET_FAILURE; + } + print_hex("UDI", udi, TKEY_UDI_SIZE); + + return CMD_RET_SUCCESS; +} + +static int do_tkey_wrapkey(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + u8 wrapping_key[TKEY_WRAPPING_KEY_SIZE]; + const char *password; + struct udevice *dev; + int ret; + + if (argc != 2) + return CMD_RET_USAGE; + + dev = tkey_get_device(); + if (!dev) + return CMD_RET_FAILURE; + + password = argv[1]; + + ret = tkey_derive_wrapping_key(dev, password, wrapping_key); + if (ret) { + if (ret == -ENOTSUPP) + printf("UDI not available - replug device\n"); + else + printf("Cannot derive wrapping key (err %dE)\n", ret); + return CMD_RET_FAILURE; + } + + print_hex("Wrapping Key", wrapping_key, TKEY_WRAPPING_KEY_SIZE); + + return CMD_RET_SUCCESS; +} + +static int do_tkey_fwmode(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + int ret; + + dev = tkey_get_device(); + if (!dev) + return CMD_RET_FAILURE; + + ret = tkey_in_app_mode(dev); + if (ret < 0) { + printf("Failed to check device mode (err %dE)\n", ret); + return CMD_RET_FAILURE; + } + + if (!ret) + printf("firmware mode\n"); + else + printf("app mode\n"); + + return CMD_RET_SUCCESS; +} + +static int do_tkey_signer(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + printf("signer binary: %lx bytes at %p-%p\n", TKEY_SIGNER_SIZE, + __signer_1_0_0_begin, __signer_1_0_0_end); + + return CMD_RET_SUCCESS; +} + +static int do_tkey_getkey(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + const char *hash = NULL; + u8 expect[TKEY_HASH_SIZE]; + u8 disk_key[TKEY_DISK_KEY_SIZE]; + u8 key_hash[TKEY_HASH_SIZE]; + u8 pubkey[TKEY_PUBKEY_SIZE]; + bool verify = false; + struct udevice *dev; + u32 uss_len, ret; + const char *uss; + + if (argc != 2 && argc != 3) + return CMD_RET_USAGE; + + dev = tkey_get_device(); + if (!dev) + return CMD_RET_FAILURE; + + uss = argv[1]; + uss_len = strlen(uss); + if (uss_len > TKEY_USS_MAX_SIZE) { + printf("USS too long (max %x bytes, got %x)\n", + TKEY_USS_MAX_SIZE, uss_len); + return CMD_RET_FAILURE; + } + + /* Check if verification hash is provided */ + if (argc == 3) { + int i; + + hash = argv[2]; + verify = true; + + /* Convert hex string to bytes */ + if (strlen(hash) != TKEY_HASH_SIZE * 2) { + printf("Verification hash must be %x hex chars\n", + TKEY_HASH_SIZE * 2); + return CMD_RET_USAGE; + } + + for (i = 0; i < TKEY_HASH_SIZE; i++) + expect[i] = (hex_to_bin(hash[i * 2]) << 4) | + hex_to_bin(hash[i * 2 + 1]); + } + + /* Derive disk key using uclass function */ + ret = tkey_derive_disk_key(dev, (const u8 *)__signer_1_0_0_begin, + TKEY_SIGNER_SIZE, (const u8 *)uss, + uss_len, disk_key, pubkey, key_hash); + if (ret) { + printf("Failed to derive disk key (err %dE)\n", ret); + return CMD_RET_FAILURE; + } + + /* Display results */ + print_hex("Public Key", pubkey, TKEY_PUBKEY_SIZE); + print_hex("Disk Key", disk_key, TKEY_DISK_KEY_SIZE); + + /* Verify or display verification hash */ + if (verify) { + /* Verify USS by comparing hashes */ + if (memcmp(key_hash, expect, TKEY_HASH_SIZE) == 0) { + printf("\npassword correct\n"); + } else { + printf("\nwrong password\n"); + print_hex("Expected", expect, TKEY_HASH_SIZE); + print_hex("Got", key_hash, TKEY_HASH_SIZE); + return CMD_RET_FAILURE; + } + } else { + print_hex("Verification Hash", key_hash, TKEY_HASH_SIZE); + /* to verify USS later: tkey getkey <uss> <verification-hash> */ + } + + return CMD_RET_SUCCESS; +} + +static int do_tkey_loadapp(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + const char *uss = NULL; + u32 ret, ulen = 0; + + if (argc != 1 && argc != 2) + return CMD_RET_USAGE; + + dev = tkey_get_device(); + if (!dev) + return CMD_RET_FAILURE; + + /* Optional USS parameter */ + if (argc == 2) { + uss = argv[1]; + ulen = strlen(uss); + if (ulen > TKEY_USS_MAX_SIZE) { + printf("USS too long (max %x bytes, got %x)\n", + TKEY_USS_MAX_SIZE, ulen); + return CMD_RET_FAILURE; + } + } + + printf("Loading signer app (%lx bytes)%s...", TKEY_SIGNER_SIZE, + uss ? " with USS" : ""); + ret = tkey_load_app_with_uss(dev, (const u8 *)__signer_1_0_0_begin, + TKEY_SIGNER_SIZE, (const u8 *)uss, ulen); + if (ret) { + if (ret == -ENOTSUPP) + printf("Invalid mode - replug device?\n"); + else + printf("Failed to load app (err %dE)\n", ret); + return CMD_RET_FAILURE; + } + printf("done\n"); + + return CMD_RET_SUCCESS; +} + +U_BOOT_LONGHELP(tkey, + "connect - Connect to TKey device\n" + "tkey fwmode - Check if device is in firmware or app mode\n" + "tkey getkey <uss> [verify-hash] - Get disk encryption key\n" + " Loads app with USS, derives key. Same USS always produces same key.\n" + " Optional verify-hash checks if USS is correct\n" + "tkey info - Show TKey device information\n" + "tkey loadapp [uss] - Load embedded signer app to TKey\n" + " Firmware mode only. Optional USS for key derivation\n" + "tkey signer - Show embedded signer binary information\n" + "tkey wrapkey <password> - Create wrapping key from password and UDI"); + +U_BOOT_CMD_WITH_SUBCMDS(tkey, "Tillitis TKey security token operations", + tkey_help_text, + U_BOOT_SUBCMD_MKENT(connect, 1, 1, do_tkey_connect), + U_BOOT_SUBCMD_MKENT(fwmode, 1, 1, do_tkey_fwmode), + U_BOOT_SUBCMD_MKENT(getkey, 3, 1, do_tkey_getkey), + U_BOOT_SUBCMD_MKENT(info, 1, 1, do_tkey_info), + U_BOOT_SUBCMD_MKENT(loadapp, 2, 1, do_tkey_loadapp), + U_BOOT_SUBCMD_MKENT(signer, 1, 1, do_tkey_signer), + U_BOOT_SUBCMD_MKENT(wrapkey, 2, 1, do_tkey_wrapkey)); diff --git a/doc/usage/cmd/tkey.rst b/doc/usage/cmd/tkey.rst new file mode 100644 index 00000000000..6a9f37354eb --- /dev/null +++ b/doc/usage/cmd/tkey.rst @@ -0,0 +1,247 @@ +.. SPDX-License-Identifier: GPL-2.0+: + +.. index:: + single: tkey (command) + +tkey command +============ + +Synopsis +-------- + +:: + + tkey connect + tkey fwmode + tkey getkey <uss> [verify-hash] + tkey info + tkey loadapp [uss] + tkey signer + tkey wrapkey <password> + +Description +----------- + +The *tkey* command provides an interface to interact with Tillitis TKey +security tokens. The TKey is a USB security device that can be used for +cryptographic operations, particularly for deriving encryption keys in a +secure and reproducible manner. + +The TKey operates in two modes: + +Firmware mode + The device starts in this mode after being plugged in. In firmware mode, + the device provides access to its Unique Device Identifier (UDI) and allows + loading applications. + +App mode + After an application is loaded, the device enters app mode. The UDI is no + longer accessible, but the loaded app can perform cryptographic operations. + +The primary use case is full disk encryption (FDE) key derivation, where the +TKey combines a User-Supplied Secret (USS, typically a password) with its +internal UDI to generate deterministic encryption keys. + + +tkey connect +~~~~~~~~~~~~ + +Test connectivity to a TKey device. This command attempts to find and connect +to the first available TKey device in the system. + + +tkey fwmode +~~~~~~~~~~~ + +Check whether the TKey device is currently in firmware mode or app mode. + +Firmware mode + The device has just been plugged in or reset. The UDI is accessible and + apps can be loaded. + +App mode + An application has been loaded and is running. The UDI is not accessible. + + +tkey getkey +~~~~~~~~~~~ + +Derive a disk encryption key by loading the embedded signer app with a +User-Supplied Secret (USS). This is the main command for full-disk-encryption +workflows. + +The command performs these steps: + +#. Loads the embedded signer app with the provided USS +#. Retrieves the public key from the signer app +#. Derives the disk encryption key (BLAKE2b hash of the public key) +#. Computes a verification hash (BLAKE2b hash of the disk key) + +The USS is typically a user password or passphrase. The same USS always produces +the same disk encryption key, making this suitable for unlocking encrypted +disks. + +If a verification hash is provided as the second argument, the command verifies +that the USS is correct by comparing the computed hash with the expected hash. +This is useful for validating user passwords before attempting to decrypt a +disk. + +uss + User-Supplied Secret (password/passphrase) for key derivation + +verify-hash + Optional 64-character hex string to verify the USS is correct + +The command outputs: + +Public Key + 32-byte Ed25519 public key derived from USS + +Disk Key + 32-byte encryption key (BLAKE2b-256 of public key) + +Verification Hash + 32-byte hash of disk key (save this for later verification) + + +tkey info +~~~~~~~~~ + +Display information about the TKey device, including its name, version, and +Unique Device Identifier (UDI). + +The UDI is only available in firmware mode. If the device is in app mode, the +command will report that the UDI is not available and suggest replugging the +device. + + +tkey loadapp +~~~~~~~~~~~~ + +Load the embedded signer application to the TKey device. This can only be done +when the device is in firmware mode. + +An optional USS (User-Supplied Secret) can be provided, which will be used by +the signer app for key derivation. If no USS is provided, the app loads without +a secret. + +After loading, the device transitions to app mode and the UDI becomes +inaccessible. + +uss + Optional User-Supplied Secret for the signer app + + +tkey signer +~~~~~~~~~~~ + +Display information about the embedded signer application binary that is +compiled into U-Boot. + + +tkey wrapkey +~~~~~~~~~~~~ + +Derive a wrapping key from a password and the device's UDI. This uses the +BLAKE2b-256 hash function to combine the UDI with the password. + +The wrapping key can be used to encrypt/decrypt other secrets. Unlike getkey, +this command does not load an app - it only requires the UDI, so it must be +run in firmware mode. + +The same password always produces the same wrapping key for a given device, +but different TKey devices (with different UDIs) will produce different +wrapping keys even with the same password. + +password + Password to combine with UDI for key derivation + + +Example +------- + +Connect to device:: + + => tkey connect + Connected to TKey device + +Check device mode:: + + => tkey fwmode + firmware mode + + => tkey loadapp + Loading signer app (a9c bytes)... + + => tkey fwmode + app mode + +Get device information (firmware mode):: + + => tkey info + Name0: tk1 Name1: mkdf Version: 4 + UDI: a0a1a2a3a4a5a6a7 + +Get device information (app mode):: + + => tkey info + Name0: tk1 Name1: sign Version: 4 + UDI not available - replug device + +Derive disk encryption key without verification:: + + => tkey getkey mypassword + Public Key: 1a2b3c4d... + Disk Key: 9f8e7d6c... + Verification Hash: 5a4b3c2d... + +Derive disk encryption key with verification (correct password):: + + => tkey getkey mypassword 5a4b3c2d1e0f... + Public Key: 1a2b3c4d... + Disk Key: 9f8e7d6c... + + password correct + +Derive disk encryption key with verification (wrong password):: + + => tkey getkey wrongpassword 5a4b3c2d1e0f... + Public Key: aaaa1111... + Disk Key: bbbb2222... + + wrong password + Expected: 5a4b3c2d1e0f... + Got: cccc3333... + +Load app without USS:: + + => tkey loadapp + Loading signer app (a9c bytes)... + +Load app with USS:: + + => tkey loadapp mypassword + Loading signer app (a9c bytes) with USS... + +Show signer binary information:: + + => tkey signer + signer binary: a9c bytes at 0x1234-0x5678 + +Derive wrapping key from password:: + + => tkey wrapkey mypassword + Wrapping Key: 95229cd376898f3fb022a627349dc985bc4675da580d2696bdd6f71f488e306c + + +Configuration +------------- + +The tkey command is available if CONFIG_CMD_TKEY is enabled. The command +requires a TKey driver to be configured (USB or serial). + + +See also +-------- + +* `Tillitis TKey documentation <https://tillitis.se/>`_ diff --git a/doc/usage/index.rst b/doc/usage/index.rst index d1887f7d26a..701f03ca373 100644 --- a/doc/usage/index.rst +++ b/doc/usage/index.rst @@ -130,6 +130,7 @@ Shell commands cmd/source cmd/tcpm cmd/temperature + cmd/tkey cmd/tftpput cmd/trace cmd/true diff --git a/test/cmd/Makefile b/test/cmd/Makefile index ffb78f69041..3fc07f0cacf 100644 --- a/test/cmd/Makefile +++ b/test/cmd/Makefile @@ -43,6 +43,7 @@ obj-$(CONFIG_CMD_READ) += rw.o obj-$(CONFIG_CMD_SETEXPR) += setexpr.o obj-$(CONFIG_CMD_SMBIOS) += smbios.o obj-$(CONFIG_CMD_TEMPERATURE) += temperature.o +obj-$(CONFIG_CMD_TKEY) += tkey.o ifdef CONFIG_NET obj-$(CONFIG_CMD_WGET) += wget.o endif diff --git a/test/cmd/tkey.c b/test/cmd/tkey.c new file mode 100644 index 00000000000..605ce070f0e --- /dev/null +++ b/test/cmd/tkey.c @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Tests for tkey command + * + * Copyright (C) 2025 Canonical Ltd + */ + +#include <console.h> +#include <dm.h> +#include <dm/test.h> +#include <test/cmd.h> +#include <test/ut.h> + +/* Test 'tkey' command help output */ +static int cmd_test_tkey_help(struct unit_test_state *uts) +{ + ut_asserteq(1, run_command("tkey", 0)); + ut_assert_nextlinen("tkey - Tillitis TKey security token operations"); + ut_assert_nextline_empty(); + ut_assert_nextlinen("Usage:"); + ut_assert_nextlinen("tkey connect"); + ut_assert_skip_to_linen("tkey wrapkey"); + + return 0; +} +CMD_TEST(cmd_test_tkey_help, UTF_DM | UTF_SCAN_FDT | UTF_CONSOLE); + +/* Test 'tkey' subcommands with emulator */ +static int cmd_test_tkey_sandbox(struct unit_test_state *uts) +{ + struct udevice *dev; + + /* TKey device should be available in sandbox */ + ut_assertok(uclass_first_device_err(UCLASS_TKEY, &dev)); + + /* Test info command */ + ut_assertok(run_command("tkey info", 0)); + ut_assert_nextline("Name0: tk1 Name1: mkdf Version: 4"); + ut_assert_nextline("UDI: a0a1a2a3a4a5a6a7"); + + /* Test fwmode command - should be in firmware mode initially */ + ut_assertok(run_command("tkey fwmode", 0)); + ut_assert_nextline("firmware mode"); + + /* Test signer command */ + ut_assertok(run_command("tkey signer", 0)); + ut_assert_nextlinen("signer binary: "); + + /* Test wrapkey command */ + ut_assertok(run_command("tkey wrapkey testpass", 0)); + ut_assert_nextline("Wrapping Key: f91450f0396768885aeaee7f0cc3305de25f6e50db79e7978a83c08896fcbf0d"); + + /* Test getkey command */ + ut_assertok(run_command("tkey getkey testuss", 0)); + ut_assert_nextline("Public Key: 505152535455565758595a5b5c5d5e5f505152535455565758595a5b5c5d5e5f"); + ut_assert_nextline("Disk Key: 228b2f6abf8be05649b2417586150bbf3e1b3f669afa1c6151ddc72957933c21"); + ut_assert_nextline("Verification Hash: a72a46b8f8c7ff0824416ada886f62b6c2808896d71201a32814ab432c7a81cf"); + + /* After getkey, device should be in app mode */ + ut_assertok(run_command("tkey fwmode", 0)); + ut_assert_nextline("app mode"); + + ut_assert_console_end(); + + return 0; +} +CMD_TEST(cmd_test_tkey_sandbox, UTF_DM | UTF_SCAN_FDT | UTF_CONSOLE); -- 2.43.0