From: Simon Glass <simon.glass@canonical.com> This series finishes off the TKey implementation, including an expo menu (bootctl), a Python script to automate common operations and some documentation to pull it all together. Simon Glass (7): luks: Correct condition for calling handle_encrypted() test: fs_helper: Support LUKS keyfile and master key test: Add mmc13 and mmc14 devices for TKey and pre-derived test: luks: Add test for pre-derived master key unlock test: bootctl: Add passphrase UI and TKey unlock tests scripts: Add tkey_fde_key.py for TKey disk encryption doc: Add TKey full disk encryption documentation arch/sandbox/dts/test.dts | 16 + boot/bootctl/logic.c | 2 +- doc/usage/blkmap.rst | 24 + doc/usage/cmd/blkmap.rst | 2 + doc/usage/cmd/bootflow.rst | 25 + doc/usage/cmd/luks.rst | 2 + doc/usage/cmd/tkey.rst | 2 + doc/usage/index.rst | 1 + doc/usage/luks.rst | 19 +- doc/usage/tkey-fde.rst | 585 ++++++++++ scripts/tkey_fde_key.py | 2003 +++++++++++++++++++++++++++++++++++ test/boot/bootctl/bootctl.c | 433 +++++++- test/boot/luks.c | 52 + test/py/img/common.py | 13 +- test/py/img/ubuntu.py | 12 +- test/py/tests/fs_helper.py | 66 +- test/py/tests/test_ut.py | 32 + 17 files changed, 3261 insertions(+), 28 deletions(-) create mode 100644 doc/usage/tkey-fde.rst create mode 100755 scripts/tkey_fde_key.py -- 2.43.0 base-commit: 7bdb0c15ffd747959c0bd5d9aea2b768f505c65f branch: secj
From: Simon Glass <simon.glass@canonical.com> Use CONFIG_BLK_LUKS instead of CONFIG_LUKS, which does not exist. This allows the test to pass. Signed-off-by: Simon Glass <simon.glass@canonical.com> --- boot/bootctl/logic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boot/bootctl/logic.c b/boot/bootctl/logic.c index 2ed106628e7..471e38e238c 100644 --- a/boot/bootctl/logic.c +++ b/boot/bootctl/logic.c @@ -903,7 +903,7 @@ static int logic_poll(struct udevice *dev) return log_msg_ret("gos", -ENOENT); /* If encrypted, handle pass entry and unlock */ - if (IS_ENABLED(CONFIG_LUKS) && + if (IS_ENABLED(CONFIG_BLK_LUKS) && (os->bflow.flags & BOOTFLOWF_ENCRYPTED)) { ret = handle_encrypted(dev, os, seq); if (ret) -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add encrypt_keyfile and master_keyfile parameters to FsHelper and the image setup functions. This allows creating encrypted test images using: - A key file instead of a passphrase (encrypt_keyfile) - A specific master key for pre-derived unlock testing (master_keyfile) The keyfile takes precedence over passphrase when both are provided. Also reduce Argon2 memory parameters to values suitable for U-Boot testing. These new features will allow use of a real TKey for trying out this feature locally, as well as the emulated TKey for automated testing. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- test/py/img/common.py | 13 ++++++-- test/py/img/ubuntu.py | 12 +++++-- test/py/tests/fs_helper.py | 66 +++++++++++++++++++++++++++++--------- 3 files changed, 70 insertions(+), 21 deletions(-) diff --git a/test/py/img/common.py b/test/py/img/common.py index 74ea04771c7..547066b24a5 100644 --- a/test/py/img/common.py +++ b/test/py/img/common.py @@ -33,7 +33,8 @@ def copy_partition(ubman, fsfile, outname): def setup_extlinux_image(config, log, devnum, basename, vmlinux, initrd, dtbdir, - script, part2_size=1, use_fde=0, luks_kdf='pbkdf2'): + script, part2_size=1, use_fde=0, luks_kdf='pbkdf2', + encrypt_keyfile=None, master_keyfile=None): """Create a 20MB disk image with a single FAT partition Args: @@ -49,6 +50,10 @@ def setup_extlinux_image(config, log, devnum, basename, vmlinux, initrd, dtbdir, use_fde (int): LUKS version for full-disk encryption (0=none, 1=LUKS1, 2=LUKS2) luks_kdf (str): Key derivation function for LUKS2: 'pbkdf2' or 'argon2id'. Defaults to 'pbkdf2'. Ignored for LUKS1. + encrypt_keyfile (str, optional): Path to key file for LUKS encryption. + If provided, takes precedence over passphrase. + master_keyfile (str, optional): Path to file containing the raw master + key. If provided, this exact key is used as the LUKS master key. """ fsh = FsHelper(config, 'vfat', 18, prefix=basename) fsh.setup() @@ -84,9 +89,11 @@ def setup_extlinux_image(config, log, devnum, basename, vmlinux, initrd, dtbdir, ext4 = FsHelper(config, 'ext4', max(1, part2_size - 30), prefix=basename, part_mb=part2_size, - passphrase='test' if use_fde else None, + passphrase='test' if (use_fde and not encrypt_keyfile) else None, + encrypt_keyfile=encrypt_keyfile, luks_version=use_fde if use_fde else 2, - luks_kdf=luks_kdf) + luks_kdf=luks_kdf, + master_keyfile=master_keyfile) ext4.setup() bindir = os.path.join(ext4.srcdir, 'bin') diff --git a/test/py/img/ubuntu.py b/test/py/img/ubuntu.py index 243fa38d021..1f3016b79a6 100644 --- a/test/py/img/ubuntu.py +++ b/test/py/img/ubuntu.py @@ -7,7 +7,8 @@ from img.common import setup_extlinux_image def setup_ubuntu_image(config, log, devnum, basename, version='24.04.1 LTS', - use_fde=0, luks_kdf='pbkdf2'): + use_fde=0, luks_kdf='pbkdf2', encrypt_keyfile=None, + master_keyfile=None): """Create a Ubuntu disk image with a FAT partition and ext4 partition This creates a FAT partition containing extlinux files, kernel, etc. and a @@ -21,6 +22,11 @@ def setup_ubuntu_image(config, log, devnum, basename, version='24.04.1 LTS', use_fde (int): LUKS version for full-disk encryption (0=none, 1=LUKS1, 2=LUKS2) luks_kdf (str): Key derivation function for LUKS2: 'pbkdf2' or 'argon2id'. Defaults to 'pbkdf2'. Ignored for LUKS1. + encrypt_keyfile (str, optional): Path to key file for LUKS encryption. + If provided, takes precedence over passphrase. + master_keyfile (str, optional): Path to file containing the raw master + key. If provided, this exact key is used as the LUKS master key, + enabling pre_derived unlock mode. """ vmlinux = 'vmlinuz-6.8.0-53-generic' initrd = 'initrd.img-6.8.0-53-generic' @@ -52,4 +58,6 @@ label l0r ''' % ((version, vmlinux, initrd) * 2) setup_extlinux_image(config, log, devnum, basename, vmlinux, initrd, dtbdir, script, part2_size=60 if use_fde else 1, - use_fde=use_fde, luks_kdf=luks_kdf) + use_fde=use_fde, luks_kdf=luks_kdf, + encrypt_keyfile=encrypt_keyfile, + master_keyfile=master_keyfile) diff --git a/test/py/tests/fs_helper.py b/test/py/tests/fs_helper.py index d88cc270b95..3c4c6e2df6f 100644 --- a/test/py/tests/fs_helper.py +++ b/test/py/tests/fs_helper.py @@ -62,12 +62,21 @@ class FsHelper: fsh.mk_fs() # Creates and encrypts the FS with LUKS2+Argon2 ... + To create an encrypted LUKS2 partition with a key file: + + with FsHelper(ubman.config, 'ext4', 10, 'mmc1', + encrypt_keyfile='/path/to/keyfile') as fsh: + # create files in the fsh.srcdir directory + fsh.mk_fs() # Creates and encrypts the filesystem with key file + ... + Properties: fs_img (str): Filename for the filesystem image; this is set to a default value but can be overwritten """ def __init__(self, config, fs_type, size_mb, prefix, part_mb=None, - passphrase=None, luks_version=2, luks_kdf='pbkdf2'): + passphrase=None, encrypt_keyfile=None, luks_version=2, + luks_kdf='pbkdf2', master_keyfile=None): """Set up a new object Args: @@ -81,9 +90,14 @@ class FsHelper: the filesystem, to create space for disk-encryption metadata passphrase (str, optional): If provided, encrypt the filesystem with LUKS using this passphrase + encrypt_keyfile (str, optional): Path to key file for LUKS + encryption. If provided, takes precedence over passphrase. luks_version (int): LUKS version to use (1 or 2). Defaults to 2. luks_kdf (str): Key derivation function for LUKS2: 'pbkdf2' or 'argon2id'. Defaults to 'pbkdf2'. Ignored for LUKS1. + master_keyfile (str, optional): Path to file containing the raw + master key. If provided, this exact key is used as the LUKS + master key (via --master-key-file), enabling pre_derived unlock. """ if ('fat' not in fs_type and 'ext' not in fs_type and fs_type not in ['exfat', 'fs_generic']): @@ -96,8 +110,10 @@ class FsHelper: self.prefix = prefix self.quiet = True self.passphrase = passphrase + self.encrypt_keyfile = encrypt_keyfile self.luks_version = luks_version self.luks_kdf = luks_kdf + self.master_keyfile = master_keyfile # Use a default filename; the caller can adjust it leaf = f'{prefix}.{fs_type}.img' @@ -170,8 +186,8 @@ class FsHelper: shell=True) # Encrypt the filesystem if requested - if self.passphrase: - self.encrypt_luks(self.passphrase) + if self.passphrase or self.encrypt_keyfile: + self.encrypt_luks() def setup(self): """Set up the srcdir ready to receive files""" @@ -186,22 +202,29 @@ class FsHelper: self.tmpdir = tempfile.TemporaryDirectory('fs_helper') self.srcdir = self.tmpdir.name - def encrypt_luks(self, passphrase): + def encrypt_luks(self): """Encrypt the filesystem image with LUKS This replaces the filesystem image with a LUKS-encrypted version. The LUKS version is determined by self.luks_version. - - Args: - passphrase (str): Passphrase for the LUKS container + Uses either passphrase or keyfile for encryption. Returns: str: Path to the encrypted image Raises: CalledProcessError: If cryptsetup is not available or fails - ValueError: If an unsupported LUKS version is specified + ValueError: If an unsupported LUKS version is specified or if neither + passphrase nor keyfile is provided + Exception: If anything else goes wrong """ + # Validate that we have either passphrase or keyfile + if not self.passphrase and not self.encrypt_keyfile: + raise ValueError('Either encrypt_passphrase or encrypt_keyfile must be provided') + + # If both are provided, keyfile takes precedence + use_keyfile = self.encrypt_keyfile is not None + use_master_key = self.master_keyfile is not None # LUKS encryption parameters if self.luks_version == 1: # LUKS1 parameters @@ -216,7 +239,7 @@ class FsHelper: hash_alg = 'sha256' luks_type = 'luks2' else: - raise ValueError(f"Unsupported LUKS version: {self.luks_version}") + raise ValueError(f'Unsupported LUKS version: {self.luks_version}') key_size_str = str(key_size) @@ -236,7 +259,7 @@ class FsHelper: result = run(['sudo', 'modprobe', 'dm_mod'], stdout=DEVNULL, stderr=DEVNULL, check=False) if result.returncode != 0: - raise RuntimeError( + raise ValueError( 'Device-mapper is not available. Please ensure the dm_mod ' 'kernel module is loaded and you have permission to use ' 'device-mapper. This is required for LUKS encryption tests.') @@ -262,22 +285,33 @@ class FsHelper: # For Argon2, use low memory/time settings suitable for testing if self.luks_kdf == 'argon2id': cmd.extend([ - '--pbkdf-memory', '65536', # 64MB - '--pbkdf-parallel', '4', + '--pbkdf-memory', '8192', # 8MB (reduced for U-Boot) + '--pbkdf-parallel', '1', # Single thread for simplicity ]) + # Add master key file option if provided + if use_master_key: + cmd.extend(['--master-key-file', self.master_keyfile]) + + # Add key file or passphrase option + if use_keyfile: + cmd.extend(['--key-file', self.encrypt_keyfile]) + cmd.append(luks_img) + # When using passphrase, provide it via stdin; otherwise set input=None run(cmd, - input=f'{passphrase}\n'.encode(), + input=f'{self.passphrase}\n'.encode() if not use_keyfile else None, stdout=DEVNULL if self.quiet else None, stderr=DEVNULL if self.quiet else None, check=True) # Open the LUKS device (requires sudo) - # Use --key-file=- to read passphrase from stdin - result = run(['sudo', 'cryptsetup', 'open', '--key-file=-', - luks_img, device_name], input=passphrase.encode(), + # Use --key-file with file path or '-' for stdin + result = run(['sudo', 'cryptsetup', 'open', + '--key-file', self.encrypt_keyfile if use_keyfile else '-', + luks_img, device_name], + input=self.passphrase.encode() if not use_keyfile else None, stdout=DEVNULL if self.quiet else None, stderr=None, check=True) # Copy the filesystem data into the LUKS container -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add two new MMC test devices: - mmc13: LUKS2 encrypted with TKey-derived key, for testing TKey-based disk encryption unlock - mmc14: LUKS2 encrypted with a known master key, for testing the pre-derived master key unlock path The test setup generates keys matching the TKey emulator's deterministic output. An override.bin file can be used to test with a physical TKey. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- arch/sandbox/dts/test.dts | 16 ++++++++++++++++ test/py/tests/test_ut.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts index 7fef23a9285..7a67565e4a9 100644 --- a/arch/sandbox/dts/test.dts +++ b/arch/sandbox/dts/test.dts @@ -50,6 +50,8 @@ mmc10 = "/mmc10"; mmc11 = "/mmc11"; mmc12 = "/mmc12"; + mmc13 = "/mmc13"; + mmc14 = "/mmc14"; pci0 = &pci0; pci1 = &pci1; pci2 = &pci2; @@ -1220,6 +1222,20 @@ filename = "mmc12.img"; }; + /* This is used for LUKS version 2 tests with TKey */ + mmc13 { + status = "disabled"; + compatible = "sandbox,mmc"; + filename = "mmc13.img"; + }; + + /* This is used for LUKS version 2 tests with pre-derived master key */ + mmc14 { + status = "disabled"; + compatible = "sandbox,mmc"; + filename = "mmc14.img"; + }; + pch { compatible = "sandbox,pch"; }; diff --git a/test/py/tests/test_ut.py b/test/py/tests/test_ut.py index e2b4d49a2e0..b9ba240c848 100644 --- a/test/py/tests/test_ut.py +++ b/test/py/tests/test_ut.py @@ -9,6 +9,7 @@ test one at a time, as well setting up some files needed by the tests. """ import collections import gzip +import hashlib import os import os.path import pytest @@ -83,9 +84,40 @@ def test_ut_dm_init_bootstd(u_boot_config, u_boot_log): setup_ubuntu_image(u_boot_config, u_boot_log, 3, 'flash', '25.04') setup_localboot_image(u_boot_config, u_boot_log) setup_vbe_image(u_boot_config, u_boot_log) + + # Generate TKey emulator disk key for LUKS encryption + # The emulator generates pubkey as 0x50 + (i & 0xf) for i in range(32) + # Disk key = SHA256(hex_string_of_pubkey), matching tkey_derive_disk_key() + # Allow override via external key file for testing with real keys + override_keyfile = os.path.join(u_boot_config.source_dir, 'override.bin') + if os.path.exists(override_keyfile): + keyfile = override_keyfile + u_boot_log.action(f'Using override TKey key: {keyfile}') + else: + pubkey = bytes([0x50 + (i & 0xf) for i in range(32)]) + disk_key = hashlib.sha256(pubkey.hex().encode()).digest() + keyfile = os.path.join(u_boot_config.persistent_data_dir, 'tkey_emul.key') + with open(keyfile, 'wb') as f: + f.write(disk_key) + u_boot_log.action(f'Generated TKey emulator disk key: {keyfile}') + setup_ubuntu_image(u_boot_config, u_boot_log, 11, 'mmc', use_fde=1) setup_ubuntu_image(u_boot_config, u_boot_log, 12, 'mmc', use_fde=2, luks_kdf='argon2id') + setup_ubuntu_image(u_boot_config, u_boot_log, 13, 'mmc', use_fde=2, + luks_kdf='argon2id', encrypt_keyfile=keyfile) + + # Create mmc14 with a known master key for pre_derived unlock testing + # For LUKS2 with aes-xts-plain64, we need a 64-byte (512-bit) master key + master_key = bytes([0x20 + (i & 0x3f) for i in range(64)]) + master_keyfile = os.path.join(u_boot_config.persistent_data_dir, + 'luks_master.key') + with open(master_keyfile, 'wb') as f: + f.write(master_key) + u_boot_log.action(f'Generated LUKS master key: {master_keyfile}') + setup_ubuntu_image(u_boot_config, u_boot_log, 14, 'mmc', use_fde=2, + luks_kdf='argon2id', encrypt_keyfile=keyfile, + master_keyfile=master_keyfile) def test_ut(ubman, ut_subtest): """Execute a "ut" subtest. -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add a test for the LUKS pre-derived master key unlock path using mmc14. The test verifies that: - A LUKS partition can be unlocked with the correct pre-derived key - Files can be read from the decrypted filesystem - Unlock fails with an incorrect pre-derived key This exercises the -p flag path in the luks unlock command. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- test/boot/luks.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/test/boot/luks.c b/test/boot/luks.c index dfd6f7b411c..339c7d7fc94 100644 --- a/test/boot/luks.c +++ b/test/boot/luks.c @@ -299,3 +299,55 @@ static int bootstd_test_luks2_unlock(struct unit_test_state *uts) return 0; } BOOTSTD_TEST(bootstd_test_luks2_unlock, UTF_DM | UTF_SCAN_FDT | UTF_CONSOLE); + +/* Setup mmc14 device */ +static int setup_mmc14(struct unit_test_state *uts, struct udevice **mmcp) +{ + ut_assertok(setup_mmc_device(uts, "mmc14", mmcp)); + + return 0; +} + +/* Test LUKS2 unlock with pre-derived master key on mmc14 */ +static int bootstd_test_luks2_unlock_prederived(struct unit_test_state *uts) +{ + struct blk_desc *desc; + struct udevice *mmc; + loff_t file_size; + + /* + * mmc14 is encrypted with a known master key: + * bytes([0x20 + (i & 0x3f) for i in range(64)]) + * This tests the pre_derived=true path in luks_unlock() + */ + ut_assertok(setup_mmc14(uts, &mmc)); + + /* Test unlocking partition 2 with pre-derived master key (-p flag) */ + ut_assertok(run_command("luks unlock -p mmc e:2 " + "202122232425262728292a2b2c2d2e2f" + "303132333435363738393a3b3c3d3e3f" + "404142434445464748494a4b4c4d4e4f" + "505152535455565758595a5b5c5d5e5f", 0)); + ut_assert_nextline("Unlocking LUKS2 partition..."); + ut_assert_nextline("Unlocked LUKS partition as blkmap device 'luks-mmc-e:2'"); + ut_assert_console_end(); + + /* Verify that a file can be read from the decrypted filesystem */ + desc = blk_get_devnum_by_uclass_idname("blkmap", 0); + ut_assertnonnull(desc); + + ut_assertok(fs_set_blk_dev_with_part(desc, 0)); + ut_assertok(fs_size("/bin/bash", &file_size)); + ut_asserteq(5, file_size); + + /* Test unlocking with wrong pre-derived key */ + ut_asserteq(1, run_command("luks unlock -p mmc e:2 " + "0000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000", 0)); + ut_assert_nextline("Unlocking LUKS2 partition..."); + ut_assert_skip_to_line("Failed to unlock LUKS partition (err -13: Permission denied)"); + + return 0; +} +BOOTSTD_TEST(bootstd_test_luks2_unlock_prederived, + UTF_DM | UTF_SCAN_FDT | UTF_CONSOLE); -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add tests for the bootctl passphrase entry UI and TKey-based LUKS unlock flow: - check_passphrase(): Tests the passphrase textline widget, verifying character input, backspace handling, and passphrase retrieval - prepare_tkey_test(): Sets up the TKey emulator with a test pubkey and configures app mode to test replugging scenarios - try_tkey_unlock(): Tests the complete TKey unlock flow including passphrase entry and LUKS partition decryption - bootctl_logic_tkey: Full integration test for TKey-based encrypted boot with mouse click interactions Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- test/boot/bootctl/bootctl.c | 433 +++++++++++++++++++++++++++++++++++- 1 file changed, 431 insertions(+), 2 deletions(-) diff --git a/test/boot/bootctl/bootctl.c b/test/boot/bootctl/bootctl.c index 6c12cf708e0..f251cd18507 100644 --- a/test/boot/bootctl/bootctl.c +++ b/test/boot/bootctl/bootctl.c @@ -16,18 +16,26 @@ #include <bootstd.h> #include <dm.h> #include <expo.h> +#include <menu.h> +#include <mouse.h> #include <os.h> +#include <tkey.h> #include "bootctl_common.h" #include <bootctl/logic.h> #include <bootctl/measure.h> #include <bootctl/oslist.h> #include <bootctl/state.h> #include <bootctl/ui.h> +#include <dm/device-internal.h> #include <dm/lists.h> #include <test/ut.h> #include <test/video.h> #include "../bootstd_common.h" - +#include "../../../boot/bootflow_internal.h" +#include "../../../boot/scene_internal.h" +#include "../bootstd_common.h" +#include "../expo_common.h" +// /* test that expected devices are available and can be probed */ static int bootctl_base(struct unit_test_state *uts) { @@ -333,6 +341,97 @@ static int bootctl_simple_measure(struct unit_test_state *uts) } BOOTCTL_TEST(bootctl_simple_measure, UTF_DM | UTF_SCAN_FDT | UTF_CONSOLE); +/** + * check_passphrase() - Test passphrase functionality for an encrypted item + * + * @uts: Test state + * @ui_dev: UI device to test + * @seq: Sequence number of the encrypted bootflow item + * Return: 0 if OK, -ve on error + */ +static int check_passphrase(struct unit_test_state *uts, + struct udevice *ui_dev, int seq) +{ + struct bc_ui_priv *uc_priv = dev_get_uclass_priv(ui_dev); + const char *retrieved_passphrase = NULL; + struct scene_obj *label_obj, *edit_obj; + struct scene_obj_textline *tline; + struct scene *scn = uc_priv->scn; + bool selected; + int seq_out; + + /* Show passphrase for the specified item (this also opens it) */ + ut_assertok(bc_ui_show_pass(ui_dev, seq, true)); + ut_assertok(bc_ui_render(ui_dev)); + + /* Verify passphrase textline and its child objects are now visible */ + tline = scene_obj_find(scn, ITEM_PASS + seq, SCENEOBJT_TEXTLINE); + ut_assertnonnull(tline); + ut_asserteq(false, tline->obj.flags & SCENEOF_HIDE); + ut_assert(tline->obj.flags & SCENEOF_OPEN); + + /* Verify the scene's highlight is set to the passphrase textline */ + ut_asserteq(ITEM_PASS + seq, scn->highlight_id); + + label_obj = scene_obj_find(scn, ITEM_PASS_LABEL + seq, SCENEOBJT_NONE); + ut_assertnonnull(label_obj); + ut_asserteq(false, label_obj->flags & SCENEOF_HIDE); + + edit_obj = scene_obj_find(scn, ITEM_PASS_EDIT + seq, SCENEOBJT_NONE); + ut_assertnonnull(edit_obj); + ut_asserteq(false, edit_obj->flags & SCENEOF_HIDE); + + /* Type 't', 'e', 's', 't' - each poll processes one character */ + ut_asserteq(4, console_in_puts("test")); + ut_assertok(bc_ui_poll(ui_dev, &seq_out, &selected)); + ut_asserteq_str("t", abuf_data(&tline->buf)); + ut_assertok(bc_ui_poll(ui_dev, &seq_out, &selected)); + ut_asserteq_str("te", abuf_data(&tline->buf)); + ut_assertok(bc_ui_poll(ui_dev, &seq_out, &selected)); + ut_asserteq_str("tes", abuf_data(&tline->buf)); + ut_assertok(bc_ui_poll(ui_dev, &seq_out, &selected)); + ut_asserteq_str("test", abuf_data(&tline->buf)); + + /* Send backspace to remove one character */ + ut_asserteq(1, console_in_puts("\b")); + ut_assertok(bc_ui_poll(ui_dev, &seq_out, &selected)); + ut_asserteq_str("tes", abuf_data(&tline->buf)); + + /* Re-add the 't' and verify */ + ut_asserteq(1, console_in_puts("t")); + ut_assertok(bc_ui_poll(ui_dev, &seq_out, &selected)); + ut_asserteq_str("test", abuf_data(&tline->buf)); + + /* Send return key to submit - should close textline and select */ + ut_asserteq(1, console_in_puts("\n")); + ut_assertok(bc_ui_poll(ui_dev, &seq_out, &selected)); + ut_assert(selected); + ut_asserteq(seq, seq_out); + + /* Verify we can retrieve the passphrase */ + ut_assertok(bc_ui_get_pass(ui_dev, seq, &retrieved_passphrase)); + ut_assertnonnull(retrieved_passphrase); + ut_asserteq_str("test", retrieved_passphrase); + + /* + * Verify the LUKS partition unlock would be attempted. In a real + * scenario, this would call luks_unlock(), but for the test we just + * verify the passphrase was correctly captured and the UI state + * indicates selection was made (which triggers the unlock logic) + */ + + /* Test hiding the passphrase field */ + ut_assertok(bc_ui_show_pass(ui_dev, seq, false)); + ut_assertok(bc_ui_render(ui_dev)); + + /* Verify all three objects are now hidden */ + ut_asserteq(true, tline->obj.flags & SCENEOF_HIDE); + ut_asserteq(true, label_obj->flags & SCENEOF_HIDE); + ut_asserteq(true, edit_obj->flags & SCENEOF_HIDE); + + return 0; +} + static int check_multiboot_ui(struct unit_test_state *uts, struct bootstd_priv *std) { @@ -457,6 +556,11 @@ static int check_multiboot_ui(struct unit_test_state *uts, } } + /* + * Test passphrase functionality for mmc11 (item 0, which is encrypted) + */ + ut_assertok(check_passphrase(uts, ui_dev, 0)); + membuf_dispose(&buf1); membuf_dispose(&buf2); membuf_dispose(&buf3); @@ -497,4 +601,329 @@ static int bootctl_multiboot_ui(struct unit_test_state *uts) return 0; } -BOOTCTL_TEST(bootctl_multiboot_ui, UTF_DM | UTF_SCAN_FDT); +BOOTCTL_TEST(bootctl_multiboot_ui, UTF_DM | UTF_SCAN_FDT | UTF_CONSOLE); + +/** + * click_os() - Click on an OS in the bootctl UI + * + * @uts: Unit test state + * @lpriv: Logic private data + * @seq: Sequence number of the OS to click + * Return: 0 if OK, -ve on error + */ +static int click_os(struct unit_test_state *uts, struct logic_priv *lpriv, + int seq) +{ + struct bc_ui_priv *uc_priv; + struct scene_obj *obj; + struct scene *scn; + struct expo *exp; + + uc_priv = dev_get_uclass_priv(lpriv->ui); + scn = uc_priv->scn; + exp = uc_priv->expo; + + /* Get the position of ITEM_DESC + seq and queue a click there */ + obj = scene_obj_find(scn, ITEM_DESC + seq, SCENEOBJT_NONE); + ut_assertnonnull(obj); + /* Click halfway along the object, 5 pixels from the top */ + ut_assertok(mouse_queue_click_for_test(exp->mouse, + obj->bbox.x0 + (obj->bbox.x1 - + obj->bbox.x0) / 2, + obj->bbox.y0 + 5)); + + return 0; +} + +/** + * prepare_tkey_test() - Prepare bootctl logic for TKey unlock testing + * + * This helper sets up the complete test environment including: + * - Preparing the logic and finding bootflows + * - Configuring TKey emulator with test pubkey + * - Setting TKey to app mode to test replugging + * - Starting the logic and polling to find OSes + * - Verifying encrypted bootflows were found + * + * @uts: Unit test state + * @logic: Bootctl logic device + * @emul_out: Returns the TKey emulator device + * @test_pubkey: Public key to configure in emulator + * Return: 0 on success, -ve on error + */ +static int prepare_tkey_test(struct unit_test_state *uts, + struct udevice *logic, + struct udevice **emul_out, + const u8 *test_pubkey) +{ + struct logic_priv *lpriv = dev_get_priv(logic); + struct udevice *emul; + + /* + * Prepare the logic. TKey device will be found automatically in + * tkey_poll() when needed (uses first device, which is tkey-emul) + */ + ut_assertok(bc_logic_prepare(logic)); + ut_assertnonnull(lpriv->ui); + ut_assertnonnull(lpriv->oslist); + + /* + * Configure the emulator to return a pubkey that matches the test + * LUKS image. The test image was created with this specific TKey. + * Get the emulator device to configure it. + */ + ut_assertok(uclass_get_device_by_name(UCLASS_TKEY, "tkey-emul", + &emul)); + ut_assertok(tkey_emul_set_pubkey_for_test(emul, test_pubkey)); + + /* + * Put TKey into app mode. This will force the unlock logic to + * request replugging the TKey. + */ + ut_assertok(tkey_emul_set_app_mode_for_test(emul, true)); + + /* Start the logic */ + ut_assertok(bc_logic_start(logic)); + + /* + * Override the TKey device to use the emulator. logic_start() finds + * the first device, but we want to use tkey-emul for testing. + */ + lpriv->tkey = emul; + + /* Poll twice to find both OSes (no delays, so completes quickly) */ + ut_assertok(bc_logic_poll(logic)); + ut_assertok(bc_logic_poll(logic)); + + /* Verify both OSes were found */ + ut_asserteq(2, lpriv->osinfo.count); + + /* First OS should be mmc13 and should be marked as encrypted */ + ut_asserteq_str("mmc13.bootdev.part_1", + alist_getw(&lpriv->osinfo, 0, + struct osinfo)->bflow.name); + ut_assert(alist_getw(&lpriv->osinfo, 0, struct osinfo)->bflow.flags & + BOOTFLOWF_ENCRYPTED); + + /* Verify TKey is enabled (device will be found later in tkey_poll) */ + ut_assert(lpriv->opt_tkey); + + *emul_out = emul; + return 0; +} + +/** + * try_tkey_unlock() - Try to unlock with TKey using a passphrase + * + * @uts: Unit test state + * @logic: Logic device + * @emul: TKey emulator device + * @test_pubkey: Expected public key (or NULL to keep wrong key for failure + * test) + * @passphrase: Passphrase to enter + * @load_iterations_out: Pointer to store load iteration count + * Return: 0 if OK, -ve on error + */ +static int try_tkey_unlock(struct unit_test_state *uts, struct udevice *logic, + struct udevice *emul, const u8 *test_pubkey, + const char *passphrase, int *load_iterations_out) +{ + struct logic_priv *lpriv = dev_get_priv(logic); + int load_iterations; + int i; + + /* Verify passphrase is being requested */ + ut_asserteq(UNS_WAITING_PASS, lpriv->ustate); + ut_asserteq(0, lpriv->selected_seq); + + /* Type the passphrase - each poll processes one character */ + ut_asserteq(strlen(passphrase), console_in_puts(passphrase)); + for (i = 0; i < strlen(passphrase); i++) + ut_assertok(bc_logic_poll(logic)); + + /* Press return to submit the passphrase */ + ut_asserteq(1, console_in_puts("\n")); + + /* Poll to process return - should transition to UNS_TKEY_START */ + ut_assertok(bc_logic_poll(logic)); + ut_asserteq(UNS_TKEY_START, lpriv->ustate); + + /* Poll - should transition to UNS_TKEY_WAIT_INSERT */ + ut_assertok(bc_logic_poll(logic)); + ut_asserteq(UNS_TKEY_WAIT_INSERT, lpriv->ustate); + + /* Poll - TKey should be detected, transition to UNS_TKEY_INSERTED */ + ut_assertok(bc_logic_poll(logic)); + ut_asserteq(UNS_TKEY_INSERTED, lpriv->ustate); + + /* + * Poll - TKey is in app mode, should request removal + * Transition to UNS_TKEY_WAIT_REMOVE + */ + ut_assertok(bc_logic_poll(logic)); + ut_asserteq(UNS_TKEY_WAIT_REMOVE, lpriv->ustate); + + /* Simulate TKey removal by disconnecting the emulator */ + ut_assertok(tkey_emul_set_connected_for_test(emul, false)); + + /* Poll - should detect removal, transition to UNS_TKEY_WAIT_INSERT */ + ut_assertok(bc_logic_poll(logic)); + ut_asserteq(UNS_TKEY_WAIT_INSERT, lpriv->ustate); + + /* Simulate TKey reinsertion (reconnect the device) */ + ut_assertok(tkey_emul_set_connected_for_test(emul, true)); + + /* + * Poll - TKey should be detected again, transition to + * UNS_TKEY_INSERTED + */ + ut_assertok(bc_logic_poll(logic)); + ut_asserteq(UNS_TKEY_INSERTED, lpriv->ustate); + + /* + * After reprobe, the emulator gets new priv data. + * Set the pubkey if provided (for success), or skip it (for failure) + */ + if (test_pubkey) + ut_assertok(tkey_emul_set_pubkey_for_test(emul, test_pubkey)); + + /* Poll - should start loading, transition to UNS_TKEY_LOADING */ + ut_assertok(bc_logic_poll(logic)); + ut_asserteq(UNS_TKEY_LOADING, lpriv->ustate); + + /* Poll while TKey app is loading */ + load_iterations = 0; + while (lpriv->ustate == UNS_TKEY_LOADING) { + ut_assertok(bc_logic_poll(logic)); + load_iterations++; + /* Exact count: 28KB / 127 bytes */ + ut_assert(load_iterations <= 221); + } + + /* Verify loading completed - should be in UNS_TKEY_READY */ + ut_asserteq(UNS_TKEY_READY, lpriv->ustate); + ut_asserteq(221, load_iterations); + + if (load_iterations_out) + *load_iterations_out = load_iterations; + + /* Poll - should derive key and transition to UNS_TKEY_UNLOCK */ + ut_assertok(bc_logic_poll(logic)); + ut_asserteq(UNS_TKEY_UNLOCK, lpriv->ustate); + + /* Poll - should perform unlock and transition to UNS_UNLOCK_RESULT */ + ut_assertok(bc_logic_poll(logic)); + ut_asserteq(UNS_UNLOCK_RESULT, lpriv->ustate); + + /* Poll - should process result */ + ut_assertok(bc_logic_poll(logic)); + + return 0; +} + +/* test TKey unlock with logic device - wrong then correct passphrase */ +static int bootctl_logic_tkey(struct unit_test_state *uts) +{ + /* Correct pubkey matching emulator default - produces valid disk key */ + const u8 test_pubkey[32] = { + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f + }; + /* + * Wrong pubkey - produces an invalid disk key for testing unlock + * failure + */ + const u8 wrong_pubkey[32] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f + }; + struct udevice *emul, *logic, *dev; + struct logic_priv *lpriv; + ofnode root, node; + + test_set_skip_delays(true); + bootstd_reset_usb(); + + /* Enable mmc13 device which has the TKey-encrypted partition */ + root = oftree_root(oftree_default()); + node = ofnode_find_subnode(root, "mmc13"); + ut_assert(ofnode_valid(node)); + ut_assertok(lists_bind_fdt(gd->dm_root, node, &dev, NULL, false)); + + /* Get the logic device */ + ut_assertok(bootctl_get_dev(UCLASS_BOOTCTL, &logic)); + lpriv = dev_get_priv(logic); + + /* Enable TKey support and disable autoboot */ + lpriv->opt_tkey = true; + lpriv->opt_autoboot = false; + + /* Set boot order to include mmc13 before prepare */ + lpriv->opt_labels = "mmc13 usb3"; + + /* Prepare the test environment and verify encrypted bootflows found */ + ut_assertok(prepare_tkey_test(uts, logic, &emul, test_pubkey)); + + /* Queue a click on the first OS (seq 0) to select it */ + ut_assertok(click_os(uts, lpriv, 0)); + + /* Poll the logic - should process the click and ask for passphrase */ + ut_assertok(bc_logic_poll(logic)); + + /* + * First, test wrong passphrase to verify UNS_BAD_PASS state. + * Use wrong_pubkey to simulate a TKey producing an invalid disk key. + */ + ut_assertok(try_tkey_unlock(uts, logic, emul, wrong_pubkey, "wrongpw", + NULL)); + + /* Unlock should fail, transition to UNS_BAD_PASS */ + ut_asserteq(UNS_BAD_PASS, lpriv->ustate); + + /* + * Poll while in error display state - should remain in UNS_BAD_PASS + * Error timeout is checked but we skip delays in tests + */ + ut_assertok(bc_logic_poll(logic)); + ut_asserteq(UNS_BAD_PASS, lpriv->ustate); + + /* + * Advance time past the error timeout (5 seconds) to trigger + * transition back to UNS_IDLE + */ + timer_test_add_offset(6000); /* 6 seconds */ + + /* Poll - error timeout should expire, transition to UNS_IDLE */ + ut_assertok(bc_logic_poll(logic)); + ut_asserteq(UNS_IDLE, lpriv->ustate); + + /* Click on the OS again to re-select it */ + ut_assertok(click_os(uts, lpriv, 0)); + + /* Poll - should process click and ask for passphrase again */ + ut_assertok(bc_logic_poll(logic)); + + /* + * Now type the correct passphrase. The test image was created with + * USS "test" which produces the pubkey configured in the emulator + * above. + */ + ut_assertok(try_tkey_unlock(uts, logic, emul, test_pubkey, "test", + NULL)); + + /* Unlock should succeed, transition to UNS_OK */ + ut_asserteq(UNS_OK, lpriv->ustate); + + /* Verify TKey device was found and used */ + ut_assertnonnull(lpriv->tkey); + ut_assert(lpriv->tkey_present); + + test_set_skip_delays(false); + + return 0; +} +BOOTCTL_TEST(bootctl_logic_tkey, UTF_DM | UTF_SCAN_FDT | UTF_CONSOLE); -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add comprehensive documentation for TKey-based full disk encryption: - doc/usage/tkey-fde.rst: Main documentation covering the TKey FDE workflow, key derivation process, U-Boot integration, the tkey-fde-key.py script usage, creating test images, troubleshooting, and security considerations - Add cross-references from related documentation pages including blkmap, bootflow, luks, and tkey command references - Update luks.rst with a section on hardware-backed key derivation Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- doc/usage/blkmap.rst | 24 ++ doc/usage/cmd/blkmap.rst | 2 + doc/usage/cmd/bootflow.rst | 25 ++ doc/usage/cmd/luks.rst | 2 + doc/usage/cmd/tkey.rst | 2 + doc/usage/index.rst | 1 + doc/usage/luks.rst | 19 +- doc/usage/tkey-fde.rst | 585 +++++++++++++++++++++++++++++++++++++ 8 files changed, 656 insertions(+), 4 deletions(-) create mode 100644 doc/usage/tkey-fde.rst diff --git a/doc/usage/blkmap.rst b/doc/usage/blkmap.rst index e9b3cfeaaa1..b6d3fae7aff 100644 --- a/doc/usage/blkmap.rst +++ b/doc/usage/blkmap.rst @@ -110,7 +110,31 @@ Now we can access the filesystem: blkmap get sq dev devnum load blkmap ${devnum} ${loadaddr} /etc/version + +Example: Accessing LUKS-encrypted partitions +--------------------------------------------- + +When LUKS-encrypted partitions are unlocked, U-Boot automatically creates +blkmap devices that provide on-the-fly decryption. This allows transparent +access to encrypted filesystems. + +For example, after unlocking a LUKS partition:: + + luks unlock mmc 0:2 mypassword + +A blkmap device is created (e.g., ``blkmap 0``) that can be used like any +other block device:: + + ls blkmap 0 / + load blkmap 0 ${kernel_addr_r} /boot/vmlinuz + +For more information on LUKS encryption, including hardware-backed key +derivation with TKey devices, see :doc:`luks` and :doc:`tkey-fde`. + + See also -------- * :doc:`/usage/cmd/blkmap` +* :doc:`luks` - LUKS encryption support +* :doc:`tkey-fde` - TKey full disk encryption diff --git a/doc/usage/cmd/blkmap.rst b/doc/usage/cmd/blkmap.rst index aaa4cd403c0..564898abd6c 100644 --- a/doc/usage/cmd/blkmap.rst +++ b/doc/usage/cmd/blkmap.rst @@ -321,3 +321,5 @@ See Also -------- * :doc:`../blkmap` - Blkmap device documentation and examples +* :doc:`../luks` - LUKS encryption support (uses blkmap for decrypted access) +* :doc:`../tkey-fde` - TKey full disk encryption diff --git a/doc/usage/cmd/bootflow.rst b/doc/usage/cmd/bootflow.rst index 938e5c79903..4082aa3c198 100644 --- a/doc/usage/cmd/bootflow.rst +++ b/doc/usage/cmd/bootflow.rst @@ -751,4 +751,29 @@ else 1. For other subcommands, the return value $? is always 0 (true). +Encrypted Partitions +-------------------- + +Bootflow scanning automatically detects and handles LUKS-encrypted partitions. +When an encrypted partition is detected during scanning, U-Boot will: + +1. Detect the LUKS encryption and display an indicator in the bootflow list +2. Prompt the user for a password when attempting to boot +3. Attempt to unlock the partition using the provided password +4. If a TKey device is present, use hardware-backed key derivation for enhanced + security + +For detailed information on LUKS encryption and TKey-based full disk encryption, +see :doc:`../luks` and :doc:`../tkey-fde`. + + +See also +-------- + +* :doc:`../luks` - LUKS encryption support +* :doc:`../tkey-fde` - TKey full disk encryption +* :doc:`bootdev` - Boot device documentation +* :doc:`/develop/bootstd/index` - Standard boot documentation + + .. BootflowStates_: diff --git a/doc/usage/cmd/luks.rst b/doc/usage/cmd/luks.rst index 1a9cba875ce..15a87ea12cd 100644 --- a/doc/usage/cmd/luks.rst +++ b/doc/usage/cmd/luks.rst @@ -307,6 +307,8 @@ See also -------- * :doc:`../luks` - Comprehensive LUKS feature documentation +* :doc:`../tkey-fde` - TKey full disk encryption with hardware-backed keys * :doc:`blkmap` - Blkmap device documentation +* :doc:`tkey` - TKey command reference * cryptsetup project: https://gitlab.com/cryptsetup/cryptsetup * LUKS on-disk format specifications: https://gitlab.com/cryptsetup/cryptsetup/-/wikis/home diff --git a/doc/usage/cmd/tkey.rst b/doc/usage/cmd/tkey.rst index b7d138f7307..56da02f4b5c 100644 --- a/doc/usage/cmd/tkey.rst +++ b/doc/usage/cmd/tkey.rst @@ -260,4 +260,6 @@ requires a TKey driver to be configured (USB or serial). See also -------- +* :doc:`../tkey-fde` - TKey full disk encryption guide +* :doc:`luks` - LUKS command reference * `Tillitis TKey documentation <https://tillitis.se/>`_ diff --git a/doc/usage/index.rst b/doc/usage/index.rst index 8913c0a4f9b..bee7884a066 100644 --- a/doc/usage/index.rst +++ b/doc/usage/index.rst @@ -12,6 +12,7 @@ Use U-Boot fdt_overlays fit/index luks + tkey-fde netconsole partitions cmdline diff --git a/doc/usage/luks.rst b/doc/usage/luks.rst index db8558ffb56..36d66081dc1 100644 --- a/doc/usage/luks.rst +++ b/doc/usage/luks.rst @@ -422,8 +422,7 @@ See ``test/py/tests/fs_helper.py`` for the ``FsHelper`` class: # Create LUKS2 encrypted filesystem with Argon2id with FsHelper(config, 'ext4', 30, 'test', - part_mb=60, - encrypt_passphrase='mypassword', + part_mb=60, passphrase='mypassword', luks_kdf='argon2id') as fsh: # Add files to fsh.srcdir with open(os.path.join(fsh.srcdir, 'hello.txt'), 'w') as f: @@ -434,8 +433,7 @@ See ``test/py/tests/fs_helper.py`` for the ``FsHelper`` class: # Create LUKS1 encrypted filesystem with FsHelper(config, 'ext4', 30, 'test', - part_mb=60, - encrypt_passphrase='mypassword', + part_mb=60, passphrase='mypassword', luks_version=1) as fsh: # Add files to fsh.srcdir with open(os.path.join(fsh.srcdir, 'hello.txt'), 'w') as f: @@ -472,6 +470,16 @@ CONFIG_SHA512 CONFIG_ARGON2 Enable Argon2 key derivation support (optional, for modern LUKS2) +Hardware-Backed Key Derivation +------------------------------- + +For enhanced security, LUKS partitions can be unlocked using hardware security +tokens like the Tillitis TKey. Instead of relying solely on a password, the +encryption key is derived from both the password and the device's unique +hardware identifier. + +See :doc:`tkey-fde` for complete details on using TKey for full disk encryption. + Limitations ----------- @@ -551,8 +559,11 @@ See ``test/boot/luks.c`` for tests: See Also -------- +* :doc:`tkey-fde` - TKey full disk encryption documentation * :doc:`cmd/luks` - LUKS command reference * :doc:`cmd/blkmap` - Blkmap command reference +* :doc:`cmd/tkey` - TKey command reference * :doc:`blkmap` - Blkmap device documentation * ``test/py/tests/fs_helper.py`` - Filesystem helper for creating test images +* ``scripts/tkey_fde_key.py`` - TKey key derivation tool * Linux ``cryptsetup`` documentation for LUKS disk format specification diff --git a/doc/usage/tkey-fde.rst b/doc/usage/tkey-fde.rst new file mode 100644 index 00000000000..3452dc79540 --- /dev/null +++ b/doc/usage/tkey-fde.rst @@ -0,0 +1,585 @@ +.. SPDX-License-Identifier: GPL-2.0+ + +TKey Full Disk Encryption +========================== + +Overview +-------- + +U-Boot supports using `Tillitis TKey <https://tillitis.se/>`_ hardware-security +tokens to unlock LUKS-encrypted partitions. This provides hardware-backed +full-disk encryption (FDE) where the encryption key is derived from two pieces +of information: + +* A user password/passphrase (USS - User Supplied Secret) +* The TKey's internal Unique Device Identifier (UDI) + +The same password on the same TKey always produces the same encryption key, +making it suitable for unlocking encrypted root filesystems at boot time. + +Note: Despite its name, FDE generally refers to the encryption of a single +partition on a disk, rather than an entire disk. + +**Key Features:** + +* Hardware-backed key derivation using TKey security token +* Compatible with standard LUKS1 and LUKS2 encrypted partitions +* Automatic unlock during boot flow detection +* Test infrastructure for creating encrypted disk images +* Python tools for key generation and disk encryption + +How It Works +------------ + +TKey Key Derivation Process +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The TKey derives encryption keys through this process: + +1. **Load Signer App**: The TKey firmware loads the signer application with + your password (USS) +2. **Generate Key Pair**: The signer app combines the USS with the device's UDI + to generate an Ed25519 key pair +3. **Derive Disk Key**: The public key is hashed with SHA-256 to produce a + 32-byte encryption key +4. **Decrypt Partition**: The key is used to unlock the LUKS-encrypted + partition + +This means: + +* The same password always produces the same key (deterministic) +* Different passwords produce completely different keys +* The physical TKey device is required (UDI is device-specific) +* No key material is stored on disk - only derived when needed + + +U-Boot Integration +~~~~~~~~~~~~~~~~~~ + +When U-Boot detects a LUKS-encrypted partition during bootflow booting: + +1. Prompts the user for their password +2. Detects if a TKey device is present +3. Loads the TKey signer app with the password +4. Derives the encryption key from the TKey +5. Attempts to unlock the LUKS partition +6. Creates a blkmap device for accessing decrypted data +7. Continues with normal boot process + + +Tools and Workflow +------------------ + +The ``scripts/tkey_fde_key.py`` Script +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This Python script handles TKey key derivation and disk encryption operations. + +**Basic Usage - Generate a key:** + +:: + + # Generate key interactively (prompts for password) + $ ./scripts/tkey_fde_key.py + + # Generate key from password file + $ echo "mypassword" > passfile + $ ./scripts/tkey_fde_key.py -p passfile -o diskkey.bin --binary + + # Generate key from stdin + $ echo "mypassword" | ./scripts/tkey_fde_key.py -p - --binary + +**Encrypting a Disk Image:** + +:: + + # Create a disk image + $ dd if=/dev/zero of=rootfs.img bs=1M count=1000 + + # Encrypt the entire disk with LUKS + $ ./scripts/tkey_fde_key.py -e rootfs.img -p passfile + + # Encrypt a specific partition + $ ./scripts/tkey_fde_key.py -e disk.img -P 2 -p passfile + +**Opening an Encrypted Disk:** + +:: + + # Open encrypted disk (creates /dev/mapper/tkey-disk) + $ ./scripts/tkey_fde_key.py -O rootfs.img -p passfile + + # Mount the decrypted filesystem + $ sudo mount /dev/mapper/tkey-disk /mnt + + # When done, unmount and close + $ sudo umount /mnt + $ sudo cryptsetup close tkey-disk + +**Advanced Options:** + +:: + + # Save both encrypted disk and backup key file + $ ./scripts/tkey_fde_key.py -e disk.img -p passfile -o backup.key + + # Use verbose output to see what's happening + $ ./scripts/tkey_fde_key.py -e disk.img -p passfile --verbose + + # Use debug mode for troubleshooting + $ ./scripts/tkey_fde_key.py --debug -e disk.img -p passfile + + +Creating Test Images +-------------------- + +Test Disk Images +~~~~~~~~~~~~~~~~ + +The U-Boot test infrastructure creates several LUKS-encrypted test images: + +* ``mmc11.img`` - LUKS1 encrypted Ubuntu image +* ``mmc12.img`` - LUKS2 encrypted Ubuntu image with Argon2id KDF +* ``mmc13.img`` - LUKS2 encrypted Ubuntu image for TKey testing +* ``mmc14.img`` - LUKS2 encrypted image with pre-derived master key + +By default, ``mmc13.img`` is encrypted with a key derived from the TKey +emulator's deterministic public key. This allows testing without physical +hardware. + +Using override.bin for Physical TKey Testing +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To test with a physical TKey device instead of the emulator, create an +``override.bin`` file containing the TKey-derived disk key: + +:: + + # Generate override.bin from your physical TKey with password "test" + $ echo "test" | ./scripts/tkey_fde_key.py -p - -o override.bin --binary + + # Regenerate test images with your TKey's key + $ ./test/py/test.py -B sandbox --build-dir /tmp/b/sandbox \ + --build -k test_ut_dm_init_bootstd + + # Test unlocking mmc13 with your physical TKey + $ /tmp/b/sandbox/u-boot -T -c \ + "tkey connect sandbox_tkey; sb devon mmc13; luks unlock -t mmc d:2 test" + +When ``override.bin`` exists in the source directory, the test infrastructure +uses it instead of the emulator's key to encrypt ``mmc13.img``. This allows +you to test the full TKey unlock flow with real hardware. + +To switch back to emulator testing, simply remove the override file: + +:: + + $ rm override.bin + $ ./test/py/test.py -B sandbox --build-dir /tmp/b/sandbox \ + --build -k test_ut_dm_init_bootstd + +Using the Python Test Infrastructure +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The test infrastructure in ``test/py/tests/test_ut.py`` handles TKey key +generation automatically: + +:: + + def test_ut_dm_init_bootstd(u_boot_config, u_boot_log): + """Initialize data for bootflow tests with TKey encryption""" + + # Check for override key file (for physical TKey testing) + override_keyfile = os.path.join(u_boot_config.source_dir, 'override.bin') + if os.path.exists(override_keyfile): + keyfile = override_keyfile + u_boot_log.action(f'Using override TKey key: {keyfile}') + else: + # Generate key matching TKey emulator's deterministic pubkey + pubkey = bytes([0x50 + (i & 0xf) for i in range(32)]) + disk_key = hashlib.sha256(pubkey.hex().encode()).digest() + keyfile = os.path.join(u_boot_config.persistent_data_dir, + 'tkey_emul.key') + with open(keyfile, 'wb') as f: + f.write(disk_key) + + # Create LUKS2 encrypted image for TKey testing + setup_ubuntu_image(u_boot_config, u_boot_log, 13, 'mmc', + use_fde=2, luks_kdf='argon2id', + encrypt_keyfile=keyfile) + +**Helper Class Usage:** + +See ``test/py/tests/fs_helper.py`` for the ``FsHelper`` class: + +:: + + from fs_helper import FsHelper, DiskHelper + + # Create LUKS2 encrypted filesystem with TKey key file + with FsHelper(config, 'ext4', 30, 'test', + part_mb=60, + encrypt_keyfile='/path/to/tkey-derived-key.bin') as fsh: + fsh.setup() + # Add files to fsh.srcdir + with open(os.path.join(fsh.srcdir, 'hello.txt'), 'w') as f: + f.write('Hello from TKey FDE!\n') + + # Create encrypted filesystem + fsh.mk_fs() + +Step-by-Step Workflow +---------------------- + +Complete Example: Testing TKey FDE with mmc13 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**1. Create Test Disk Images** + +Run the test infrastructure to create encrypted images: + +:: + + $ ./test/py/test.py -B sandbox --build-dir /tmp/b/sandbox \ + --build -k test_ut_dm_init_bootstd + +This creates ``mmc13.img`` (LUKS2 with Argon2id) encrypted with the TKey +emulator's key. + +**2. Test Unlocking with TKey Emulator** + +Run U-Boot sandbox and test the unlock process with the emulator: + +:: + + $ /tmp/b/sandbox/u-boot -T -c \ + "tkey connect tkey-emul; sb devon mmc13; luks unlock -t mmc d:2 test" + +**Expected Output:** + +:: + + Connected to TKey device + Device 'mmc13' enabled + Unlocking LUKS2 partition... + Using TKey for disk encryption key + Loading TKey signer app (6d78 bytes) with USS... + TKey public key: 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f + 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f + TKey disk key derived successfully + TKey derived disk key: e9 b0 59 92 68 ff 8b 08 3e f8 0d bd 04 be 20 7c + e9 a1 9a 60 a8 88 cc b3 fe 93 71 0a 0a 70 a3 4e + Unlocked LUKS partition as blkmap device 'luks-mmc-d:2' + +**3. Test with Physical TKey** + +To test with a real TKey device: + +:: + + # Generate override.bin from your physical TKey with password "test" + $ echo "test" | ./scripts/tkey_fde_key.py -p - -o override.bin --binary -f -v + Reading password from stdin... + Password length: 4, repr: 'test' + TKey detected via USB enumeration + ... + Ed25519 public key (hex): df4faa680d9fd79079cc572c1f84fb3fa59ab904dad652e90a22e5b672a67eb1 + Derived disk key (hex): 1546bdaf99e9ed9867d83ae69062c9da3202a617584a35ee4ae38672ec775a7f + Key saved to override.bin + + # Regenerate mmc13.img with your TKey's key + $ ./test/py/test.py -B sandbox --build-dir /tmp/b/sandbox \ + --build -k test_ut_dm_init_bootstd + + # Test unlocking with physical TKey (ensure TKey is plugged in) + $ /tmp/b/sandbox/u-boot -T -c "sb devon mmc13; luks unlock -t mmc d:2 test" -P + +**Expected Output with Physical TKey:** + +:: + + Device 'mmc13' enabled + Unlocking LUKS2 partition... + Using TKey for disk encryption key + Loading TKey signer app (6d78 bytes) with USS... + TKey public key: df 4f aa 68 0d 9f d7 90 79 cc 57 2c 1f 84 fb 3f + a5 9a b9 04 da d6 52 e9 0a 22 e5 b6 72 a6 7e b1 + TKey disk key derived successfully + TKey derived disk key: 15 46 bd af 99 e9 ed 98 67 d8 3a e6 90 62 c9 da + 32 02 a6 17 58 4a 35 ee 4a e3 86 72 ec 77 5a 7f + Unlocked LUKS partition as blkmap device 'luks-mmc-d:2' + +**4. Verify Encryption** + +You can verify the disk is encrypted by checking with cryptsetup: + +:: + + # Extract partition 2 from the disk image + $ dd if=mmc13.img bs=512 skip=38912 count=122880 of=mmc13_part2.img + + # Check LUKS header + $ cryptsetup luksDump mmc13_part2.img + LUKS header information + Version: 2 + ... + + # Test unlock with the TKey emulator key (when no override.bin exists) + # First generate the emulator's key + $ python3 -c " + import hashlib + pubkey = bytes([0x50 + (i & 0xf) for i in range(32)]) + key = hashlib.sha256(pubkey.hex().encode()).digest() + open('emul.key', 'wb').write(key) + " + $ sudo cryptsetup open mmc13_part2.img test-luks --key-file=emul.key + $ ls /dev/mapper/test-luks + /dev/mapper/test-luks + $ sudo cryptsetup close test-luks + +Troubleshooting +--------------- + +Key Mismatch Errors +~~~~~~~~~~~~~~~~~~~ + +**Problem:** U-Boot shows "Failed to unlock LUKS partition" + +:: + + LUKS1: Keyslot 0 failed with error -13 + Failed to unlock LUKS1 with binary passphrase (err=-2) + Failed to unlock LUKS partition (err=-13: Permission denied) + +**Cause:** The disk was encrypted with a different key than U-Boot is deriving. + +**Solutions:** + +1. **Verify you're using the correct password:** Make sure you enter the same + password in U-Boot that was used to generate the key file. + +2. **Regenerate the key and verify it matches:** + + :: + + # Generate key again with same password + $ ./scripts/tkey_fde_key.py -p mykey.txt -o test-key.bin --binary + + # Compare with original + $ diff mykey test-key.bin + + # If different, TKey may have been in different state + +3. **Recreate disk images with the correct key:** If U-Boot consistently + derives a different key than what was used for encryption, capture the key + U-Boot derives and use that to encrypt the disk: + + :: + + # Run U-Boot and note the "Binary pass" hex values from debug output + # Then recreate the key file with those values + $ python3 << 'EOF' + key_hex = "10f1132e6c27e6e8...d29b6b8a" # from U-Boot output + with open('mykey', 'wb') as f: + f.write(bytes.fromhex(key_hex)) + EOF + + # Recreate disk images with this key + $ rm mmc11.img mmc12.img + $ ./test/py/test.py -B sandbox --build-dir /tmp/b/sandbox \ + -k test_ut_dm_init_bootstd + +TKey Not Detected +~~~~~~~~~~~~~~~~~ + +**Problem:** U-Boot doesn't detect the TKey device + +**Solutions:** + +* Ensure TKey is plugged in before starting U-Boot +* Check USB device is accessible: ``ls /dev/ttyACM*`` +* Try replugging the TKey device +* For sandbox, ensure ``sandbox,device-path`` is set correctly in device tree + +TKey App Already Loaded +~~~~~~~~~~~~~~~~~~~~~~~~ + +**Problem:** TKey is in app mode instead of firmware mode + +**Solution:** Remove and reinsert the TKey. The device must be in firmware mode +to load the signer app. + +Configuration +------------- + +Kconfig Options +~~~~~~~~~~~~~~~ + +To enable TKey FDE support in U-Boot: + +:: + + CONFIG_CMD_TKEY=y # TKey command + CONFIG_TKEY_DRIVER=y # TKey device driver + CONFIG_CMD_LUKS=y # LUKS command + CONFIG_BLK_LUKS=y # LUKS block device support + CONFIG_BLKMAP=y # Block device mapping + CONFIG_BOOTCTL=y # Boot control with unlock support + CONFIG_BOOTCTL_LOGIC=y # Boot control unlock logic + CONFIG_ARGON2=y # For LUKS2 Argon2id support (optional) + +Device Tree Configuration +~~~~~~~~~~~~~~~~~~~~~~~~~ + +For sandbox testing, the TKey is configured in ``arch/sandbox/dts/test.dts``: + +:: + + tkey { + compatible = "sandbox,tkey"; + sandbox,device-path = "/dev/ttyACM0"; + }; + +For real hardware, configure the USB serial device path appropriately. + +Security Considerations +----------------------- + +Key Storage +~~~~~~~~~~~ + +* The TKey-derived key should **never be stored permanently** on disk +* Only temporary key files (like ``mykey``) used during testing should exist +* In production, keys should be derived fresh each boot from TKey + password +* The ``mykey`` file is for **testing only** and should be kept secure + +Password Security +~~~~~~~~~~~~~~~~~ + +* Use a strong password (at least 16 characters recommended) +* Different passwords produce completely different encryption keys +* The TKey's UDI adds additional entropy to the key derivation +* Consider using a hardware security token for additional protection + +Hardware Security +~~~~~~~~~~~~~~~~~ + +* Physical access to the TKey is required to derive keys +* The TKey's UDI is unique per device - keys cannot be derived without it +* If the TKey is lost, encrypted data cannot be recovered +* Consider keeping a backup TKey or traditional key recovery mechanism + +Memory Security +~~~~~~~~~~~~~~~ + +* Keys are held in memory while the device is unlocked +* Memory is not securely erased on warm reboot +* This is acceptable for boot-time use but not for long-term key storage + + +Comparison with Traditional LUKS +--------------------------------- + +**Traditional LUKS (Password Only):** + +* Encryption key derived only from password +* Vulnerable to offline password cracking attacks +* No hardware requirement - same password works anywhere + +**TKey-Enhanced LUKS:** + +* Encryption key derived from password + TKey UDI +* Requires physical TKey device to derive key +* Resistant to offline password cracking (attacker needs both password and + TKey) +* Same password + same TKey always produces same key +* Different TKey devices produce different keys even with same password + +Example Use Case: Secure Boot +------------------------------ + +A typical secure boot workflow with TKey FDE: + +1. **System Powers On** + + * U-Boot starts and scans for boot devices + * Finds LUKS-encrypted root partition + +2. **User Authentication** + + * U-Boot prompts user for password + * User inserts TKey device + * User enters password + +3. **Key Derivation** + + * U-Boot loads TKey signer app with password + * TKey derives encryption key from password + UDI + * Returns 32-byte encryption key to U-Boot + +4. **Partition Unlock** + + * U-Boot attempts to unlock LUKS partition + * If successful, creates blkmap device + * Encrypted data is accessible as standard block device + +5. **Boot Continues** + + * U-Boot loads kernel from unlocked partition + * System boots into encrypted root filesystem + +Testing +------- + +Unit Tests +~~~~~~~~~~ + +Run the bootctl TKey unlock tests: + +:: + + $ ./test/py/test.py -B sandbox --build-dir /tmp/b/sandbox \ + -k bootctl_logic_tkey + + +Manual Testing +~~~~~~~~~~~~~~ + +Test mmc13 with the TKey emulator: + +:: + + # 1. Build sandbox and create test images + $ crosfw sandbox -L + $ ./test/py/test.py -B sandbox --build-dir /tmp/b/sandbox \ + --build -k test_ut_dm_init_bootstd + + # 2. Test unlocking mmc13 with TKey emulator + $ /tmp/b/sandbox/u-boot -T -c \ + "tkey connect tkey-emul; sb devon mmc13; luks unlock -t mmc d:2 test" + +Test mmc13 with a physical TKey: + +:: + + # 1. Generate override.bin with your TKey and password "test" + $ echo "test" | ./scripts/tkey_fde_key.py -p - -o override.bin --binary + + # 2. Regenerate test images + $ ./test/py/test.py -B sandbox --build-dir /tmp/b/sandbox \ + --build -k test_ut_dm_init_bootstd + + # 3. Test unlocking with physical TKey + $ /tmp/b/sandbox/u-boot -T -c \ + "tkey connect sandbox_tkey; sb devon mmc13; luks unlock -t mmc d:2 test" + +See Also +-------- + +* :doc:`luks` - LUKS encryption documentation +* :doc:`cmd/luks` - LUKS command reference +* :doc:`cmd/tkey` - TKey command reference +* :doc:`cmd/blkmap` - Blkmap device mapping +* ``scripts/tkey_fde_key.py`` - TKey key derivation tool +* ``test/py/tests/fs_helper.py`` - Filesystem test helpers +* `Tillitis TKey Documentation <https://tillitis.se/>`_ -- 2.43.0
participants (1)
-
Simon Glass