[Concept,6/8] tkey: Add a command

Message ID 20251019072313.3235339-7-sjg@u-boot.org
State New
Headers
Series tkey: Provide basic support for Tillitis TKey |

Commit Message

Simon Glass Oct. 19, 2025, 7:23 a.m. UTC
  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
  

Patch

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);