[PATCH 00/24] luks: Provide basic support for unlocking a LUKS1 partition
From: Simon Glass <sjg@chromium.org> With full-disk encryption (FDE) it is traditional to unlock a LUKS partition within userspace as part of the initial ramdisk passed to Linux. The user is prompted for a passphrase and then the disk is unlocked. This works well but does have some drawbacks: - firmware has no way of knowing whether the boot will success - the 'passphrase' prompt comes quite late in the boot, which can be confusing for the user - specifically it is not possible to provide an integrated 'boot' UI in firmware where the user can enter the passphrase - in a VM environment, the key may be known in advance, but there is no way to take advantage of this - it is not possible to use an encryted disk unless also using a ramdisk This series makes a small step towards improving U-Boot in this area. It allows a passphrase to be checked against a LUKS1-encrypted partition. It also provides read-only access to the unencrypted data, so that files can be read. Simon Glass (24): aes: Fix key size handling for AES-192 and AES-256 doc: Provide documentation for the blkmap command log: Provide a macro to log a hex string panic: Provide a way to poweroff on panic sandbox: Enable CONFIG_PANIC_POWEROFF sandbox: Add devon and devoff subcommands to sb command mbedtls: hash: Provide the mbedtls hash type in the hash interface mbedtls: Allow use of PKCS#5 functions test/py: Support creating space after a filesystem test/py: Support FDE with the extlinux image test/py: Set up an Ubuntu image with space for FDE docker: Add cryptsetup package for LUKS testing CI: Update Docker image to including luks tools luks: Add a way to create an encrypted partition luks: Encrypt the mmc11 test image luks: Add the beginning of LUKS support luks: Add a simple command luks: Create a very simple JSON library luks: Create a disk image with LUKS2 encryption luks: Show the JSON information for LUKSv2 luks: Enhance blkmap to support LUKSv1 luks: Provide a way to unlock and map encrypted partitions luks: Add a subcommand to unlock an encrypted partition luks: Add detection of LUKS partition .gitlab-ci.yml | 6 +- MAINTAINERS | 14 + arch/sandbox/dts/test.dts | 8 + cmd/Kconfig | 9 + cmd/Makefile | 1 + cmd/luks.c | 133 +++++++ cmd/sb.c | 107 ++++- common/hash.c | 5 + configs/sandbox_defconfig | 2 + doc/usage/blkmap.rst | 5 + doc/usage/cmd/blkmap.rst | 323 +++++++++++++++ doc/usage/cmd/luks.rst | 254 ++++++++++++ doc/usage/cmd/sb.rst | 40 +- doc/usage/index.rst | 3 + doc/usage/luks.rst | 340 ++++++++++++++++ drivers/block/Kconfig | 22 ++ drivers/block/Makefile | 1 + drivers/block/blkmap.c | 152 +++++++ drivers/block/luks.c | 656 +++++++++++++++++++++++++++++++ include/blkmap.h | 24 ++ include/hash.h | 33 +- include/json.h | 23 ++ include/log.h | 16 + include/luks.h | 175 +++++++++ lib/Kconfig | 15 + lib/Makefile | 1 + lib/aes.c | 15 +- lib/json.c | 122 ++++++ lib/mbedtls/Kconfig | 14 + lib/mbedtls/Makefile | 2 + lib/mbedtls/mbedtls_def_config.h | 4 + lib/panic.c | 8 + test/boot/Makefile | 1 + test/boot/luks.c | 241 ++++++++++++ test/cmd/Makefile | 1 + test/cmd/sb.c | 123 ++++++ test/lib/Makefile | 1 + test/lib/json.c | 211 ++++++++++ test/py/img/common.py | 18 +- test/py/img/ubuntu.py | 12 +- test/py/tests/fs_helper.py | 142 ++++++- test/py/tests/test_ut.py | 3 +- tools/docker/Dockerfile | 1 + 43 files changed, 3262 insertions(+), 25 deletions(-) create mode 100644 cmd/luks.c create mode 100644 doc/usage/cmd/blkmap.rst create mode 100644 doc/usage/cmd/luks.rst create mode 100644 doc/usage/luks.rst create mode 100644 drivers/block/luks.c create mode 100644 include/json.h create mode 100644 include/luks.h create mode 100644 lib/json.c create mode 100644 test/boot/luks.c create mode 100644 test/cmd/sb.c create mode 100644 test/lib/json.c -- 2.43.0 base-commit: 2ee77c0b8cb36f203fa3b1436ae3e8f07a054347 branch: secc
From: Simon Glass <sjg@chromium.org> At present the aes_get_rounds() and aes_get_keycols() functions compare the key_len parameter (in bits) directly against AES*_KEY_LENGTH constants (in bytes), causing incorrect round and column counts for non-128-bit keys. Additionally, aes_expand_key() uses key_len as a byte count in memcpy(), copying far more data than intended and causing buffer overflows. Specifically, for AES-256 (256-bit key) it comparies 256 (bits) against 32 (bytes), failing the comparison. This causes AES-256 to use AES-128 parameters (10 rounds instead of 14) and the memcpy() to copy 256 bytes instead of 32. Fix by converting key_len from bits to bytes before comparisons and in memcpy. With this we get: - AES-128 (128 bits / 16 bytes): 10 rounds, 4 key columns - AES-192 (192 bits / 24 bytes): 12 rounds, 6 key columns - AES-256 (256 bits / 32 bytes): 14 rounds, 8 key columns Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> Fixes: 8302d1708ae ("aes: add support of aes192 and aes256") --- lib/aes.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/aes.c b/lib/aes.c index 39ad4a990f0..3bcbeeab9af 100644 --- a/lib/aes.c +++ b/lib/aes.c @@ -513,10 +513,11 @@ static u8 rcon[11] = { static u32 aes_get_rounds(u32 key_len) { u32 rounds = AES128_ROUNDS; + u32 key_len_bytes = key_len / 8; /* Convert bits to bytes */ - if (key_len == AES192_KEY_LENGTH) + if (key_len_bytes == AES192_KEY_LENGTH) rounds = AES192_ROUNDS; - else if (key_len == AES256_KEY_LENGTH) + else if (key_len_bytes == AES256_KEY_LENGTH) rounds = AES256_ROUNDS; return rounds; @@ -525,10 +526,11 @@ static u32 aes_get_rounds(u32 key_len) static u32 aes_get_keycols(u32 key_len) { u32 keycols = AES128_KEYCOLS; + u32 key_len_bytes = key_len / 8; /* Convert bits to bytes */ - if (key_len == AES192_KEY_LENGTH) + if (key_len_bytes == AES192_KEY_LENGTH) keycols = AES192_KEYCOLS; - else if (key_len == AES256_KEY_LENGTH) + else if (key_len_bytes == AES256_KEY_LENGTH) keycols = AES256_KEYCOLS; return keycols; @@ -538,12 +540,13 @@ static u32 aes_get_keycols(u32 key_len) void aes_expand_key(u8 *key, u32 key_len, u8 *expkey) { u8 tmp0, tmp1, tmp2, tmp3, tmp4; - u32 idx, aes_rounds, aes_keycols; + uint idx, aes_rounds, aes_keycols; aes_rounds = aes_get_rounds(key_len); aes_keycols = aes_get_keycols(key_len); - memcpy(expkey, key, key_len); + /* key_len is in bits; convert to bytes */ + memcpy(expkey, key, key_len / 8); for (idx = aes_keycols; idx < AES_STATECOLS * (aes_rounds + 1); idx++) { tmp0 = expkey[4*idx - 4]; -- 2.43.0
From: Simon Glass <sjg@chromium.org> This command lacks documentation in the normal place. Add it. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- doc/usage/blkmap.rst | 5 + doc/usage/cmd/blkmap.rst | 323 +++++++++++++++++++++++++++++++++++++++ doc/usage/index.rst | 1 + 3 files changed, 329 insertions(+) create mode 100644 doc/usage/cmd/blkmap.rst diff --git a/doc/usage/blkmap.rst b/doc/usage/blkmap.rst index 75f736c259f..e9b3cfeaaa1 100644 --- a/doc/usage/blkmap.rst +++ b/doc/usage/blkmap.rst @@ -109,3 +109,8 @@ Now we can access the filesystem: blkmap get sq dev devnum load blkmap ${devnum} ${loadaddr} /etc/version + +See also +-------- + +* :doc:`/usage/cmd/blkmap` diff --git a/doc/usage/cmd/blkmap.rst b/doc/usage/cmd/blkmap.rst new file mode 100644 index 00000000000..aaa4cd403c0 --- /dev/null +++ b/doc/usage/cmd/blkmap.rst @@ -0,0 +1,323 @@ +.. SPDX-License-Identifier: GPL-2.0+ + +.. index:: + single: blkmap (command) + +blkmap command +============== + +Synopsis +-------- + +:: + + blkmap info + blkmap part + blkmap dev [<dev>] + blkmap read <addr> <blk#> <cnt> + blkmap write <addr> <blk#> <cnt> + blkmap get <label> dev [<var>] + blkmap create <label> + blkmap destroy <label> + blkmap map <label> <blk#> <cnt> linear <interface> <dev> <blk#> + blkmap map <label> <blk#> <cnt> mem <addr> + +Description +----------- + +The *blkmap* command is used to create and manage virtual block devices that +are composed of one or more "slices" mapped from other block devices or memory +regions. + +See :doc:`../blkmap` for an overview of the blkmap subsystem and usage examples. + +label + Unique label assigned to a blkmap device during creation. Used to identify + the device for subsequent operations (map, get, destroy). + +blk# + Block number. Blocks are 512 bytes in size. When used as a starting position, + this specifies where in the device to begin the operation. When used as a + mapping parameter, it indicates which block in the source or destination to + use. + +cnt + Number of blocks (count). Each block is 512 bytes, so cnt=1 represents 512 + bytes, cnt=2048 represents 1MB, etc. + +addr + Memory address in hexadecimal format (e.g., ${loadaddr}, 0x82000000). Used + for specifying where to read/write data or where a memory region is located. + +blkmap info +~~~~~~~~~~~ + +List all configured blkmap devices and their properties:: + + blkmap info + +This displays information about all active blkmap devices, including device +number, vendor, product, revision, type, and capacity. + +blkmap part +~~~~~~~~~~~ + +List available partitions on the current blkmap device:: + + blkmap part + +This command displays the partition table of the currently selected blkmap +device. If the device has no partition table (whole-disk filesystem), it will +report "no blkmap partition table available". + +blkmap dev +~~~~~~~~~~ + +Show or set the current blkmap device:: + + blkmap dev [<dev>] + +dev + Optional device number to set as current. If omitted, displays the current + device information. + +blkmap read +~~~~~~~~~~~ + +Read data from the current blkmap device:: + + blkmap read <addr> <blk#> <cnt> + +blkmap write +~~~~~~~~~~~~ + +Write data to the current blkmap device:: + + blkmap write <addr> <blk#> <cnt> + +**Note**: Write support is limited and may not be available for all blkmap +types. + +blkmap get +~~~~~~~~~~ + +Get the device number for a labeled blkmap device:: + + blkmap get <label> dev [<var>] + +var + Optional environment variable name to store the device number. If omitted, + the device number is printed to stdout. + +This is useful for scripting, allowing you to find a blkmap device by its label +and store or use its device number. + +blkmap create +~~~~~~~~~~~~~ + +Create a new blkmap device with the specified label:: + + blkmap create <label> + +After creation, the device has no mappings and is empty. Use ``blkmap map`` +to add slices to the device. + +blkmap destroy +~~~~~~~~~~~~~~ + +Destroy a blkmap device and free its resources:: + + blkmap destroy <label> + +This removes the blkmap device and all its mappings. Any data stored only in +the blkmap device will be lost. + +blkmap map - linear +~~~~~~~~~~~~~~~~~~~ + +Map a region from another block device into the blkmap device:: + + blkmap map <label> <blk#> <cnt> linear <interface> <dev> <blk#> + +label + Label of the blkmap device to map into + +blk# + Starting block number in the blkmap device where this mapping begins + +cnt + Number of blocks to map + +interface + Source device interface (e.g., mmc, usb, scsi) + +dev + Source device number + +blk# (last parameter) + Starting block number on the source device + +This creates a linear mapping that redirects reads from a region of the blkmap +device to the corresponding region on another block device. Multiple mappings +can be added to compose a single virtual device from multiple sources. + +blkmap map - mem +~~~~~~~~~~~~~~~~ + +Map a memory region as a block device:: + + blkmap map <label> <blk#> <cnt> mem <addr> + +label + Label of the blkmap device to map into + +blk# + Starting block number in the blkmap device where this mapping begins + +cnt + Number of blocks to map + +addr + Memory address of the data to map + +This creates a mapping that exposes a memory region as block device sectors. +The memory region must be at least ``cnt * 512`` bytes in size. + +Usage Examples +-------------- + +See :doc:`../blkmap` for complete examples including: + +* Netbooting an Ext4 image +* Accessing filesystems inside FIT images +* Creating composite devices from multiple sources +* Using memory-backed block devices + +Implementation Details +---------------------- + +Blkmap devices are implemented as standard U-Boot block devices (UCLASS_BLK) +with a custom driver. Each device maintains an ordered list of "slices" - +mappings from a range of blocks to a source (another device or memory region). + +See :doc:`../blkmap` for more details on the implementation. + +Configuration +------------- + +The blkmap command is available when CONFIG_CMD_BLKMAP is enabled:: + + CONFIG_BLKMAP=y + CONFIG_CMD_BLKMAP=y + +Return Value +------------ + +The return value $? is set to 0 on success, 1 on failure. + +Examples +-------- + +List all blkmap devices +~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + => blkmap info + Device 0: Vendor: U-Boot Rev: 1.0 Prod: blkmap + Type: Hard Disk + Capacity: 60.0 MB = 0.0 GB (122880 x 512) + +Check or set current device +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + => blkmap dev + Device 0: Vendor: U-Boot Rev: 1.0 Prod: blkmap + Type: Hard Disk + Capacity: 60.0 MB = 0.0 GB (122880 x 512) + ... is now current device + + => blkmap dev 0 + Device 0: Vendor: U-Boot Rev: 1.0 Prod: blkmap + Type: Hard Disk + Capacity: 60.0 MB = 0.0 GB (122880 x 512) + ... is now current device + +Create a new device +~~~~~~~~~~~~~~~~~~~ + +:: + + => blkmap create mydisk + => blkmap info + Device 0: Vendor: U-Boot Rev: 1.0 Prod: blkmap + Type: Hard Disk + Capacity: 0.0 MB = 0.0 GB (1 x 512) + +Map a linear region from MMC +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Map MMC blocks 1000-1999 to blkmap blocks 0-999:: + + => blkmap create composed + => blkmap map composed 0 1000 linear mmc 0 1000 + +Map memory as a block device +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Map 1MB of memory as blocks:: + + => blkmap create ramdisk + => blkmap map ramdisk 0 2048 mem ${loadaddr} + +Read from a blkmap device +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + => blkmap dev 0 + => blkmap read ${loadaddr} 0 10 + blkmap read: device 0 block # 0, count 10 ... 10 blocks read: OK + +Get device number by label +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + => blkmap get testdev dev mydev + => printenv mydev + mydev=0 + +Complete workflow example +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Create, map, and use a device:: + + => blkmap create testdev + => blkmap map testdev 0 2048 linear mmc 0 2048 + => blkmap dev 0 + => blkmap read ${loadaddr} 0 1 + +Destroy a device +~~~~~~~~~~~~~~~~ + +:: + + => blkmap destroy mydisk + +Accessing whole-disk filesystems +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Blkmap devices often contain whole-disk filesystems (no partition table). +Access them using partition 0 or omitting the partition specification:: + + => ls blkmap 0 / + => ext4load blkmap 0 ${loadaddr} /boot/vmlinuz + => cat blkmap 0 /etc/config.txt + +See Also +-------- + +* :doc:`../blkmap` - Blkmap device documentation and examples diff --git a/doc/usage/index.rst b/doc/usage/index.rst index 701f03ca373..2fff101868c 100644 --- a/doc/usage/index.rst +++ b/doc/usage/index.rst @@ -34,6 +34,7 @@ Shell commands cmd/bdinfo cmd/bind cmd/blkcache + cmd/blkmap cmd/bootd cmd/bootdev cmd/bootefi -- 2.43.0
From: Simon Glass <sjg@chromium.org> Add a new log_debug_hex() macro which can log a a buffer as a hex string, e.g. for showing a hash value. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- include/log.h | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/include/log.h b/include/log.h index 4f6d6a2c2cf..e1a12c26133 100644 --- a/include/log.h +++ b/include/log.h @@ -188,6 +188,22 @@ int _log_buffer(enum log_category_t cat, enum log_level_t level, #define log_io(_fmt...) log(LOG_CATEGORY, LOGL_DEBUG_IO, ##_fmt) #define log_cont(_fmt...) log(LOGC_CONT, LOGL_CONT, ##_fmt) +/** + * log_debug_hex() - Print hex bytes for debugging + * + * @_label: Label to print before the hex bytes + * @_data: Pointer to the data to print + * @_len: Number of bytes to print + */ +#define log_debug_hex(_label, _data, _len) do { \ + const u8 *__data = (const u8 *)(_data); \ + int __len = (_len); \ + log_debug("%s ", _label); \ + for (int __i = 0; __i < __len; __i++) \ + log_cont("%02x", __data[__i]); \ + log_cont("\n"); \ +} while (0) + #ifdef LOG_DEBUG #define _LOG_DEBUG LOGL_FORCE_DEBUG #ifndef DEBUG -- 2.43.0
From: Simon Glass <sjg@chromium.org> For sandbox it normally doesn't make sense to reset when a panic occurs, since presumably it will just happen again. Add an option to power off instead. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- lib/Kconfig | 8 ++++++++ lib/panic.c | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/lib/Kconfig b/lib/Kconfig index c45a98313aa..9de4667731e 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -263,6 +263,14 @@ config PANIC_HANG development since you can try to debug the conditions that lead to the situation. +config PANIC_POWEROFF + bool "Power off the system on fatal error" + help + Define this option to power off the system in case of a fatal error, + instead of resetting. This is useful for development and testing to + avoid infinite reset loops when debugging issues like stack smashing. + The system will power off using sysreset. + config REGEX bool "Enable regular expression support" default y if NET diff --git a/lib/panic.c b/lib/panic.c index 0f578b5b513..adc338860a5 100644 --- a/lib/panic.c +++ b/lib/panic.c @@ -13,6 +13,9 @@ #if !defined(CONFIG_PANIC_HANG) #include <command.h> #endif +#if defined(CONFIG_PANIC_POWEROFF) +#include <sysreset.h> +#endif #include <linux/delay.h> #include <stdio.h> @@ -23,6 +26,11 @@ static void panic_finish(void) putc('\n'); #if defined(CONFIG_PANIC_HANG) hang(); +#elif defined(CONFIG_PANIC_POWEROFF) + flush(); /* flush the panic message before power off */ + + sysreset_walk(SYSRESET_POWER_OFF); + hang(); /* hang if power off fails */ #else flush(); /* flush the panic message before reset */ -- 2.43.0
From: Simon Glass <sjg@chromium.org> Enable this option so that sandbox exits when a panic occurs, rather than resetting in a loop. Signed-off-by: Simon Glass <sjg@chromium.org> --- configs/sandbox_defconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig index 0d115368d1b..601afde421d 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -359,6 +359,7 @@ CONFIG_FS_CBFS=y CONFIG_FS_EXFAT=y CONFIG_FS_CRAMFS=y CONFIG_ADDR_MAP=y +CONFIG_PANIC_POWEROFF=y CONFIG_CMD_DHRYSTONE=y CONFIG_MBEDTLS_LIB=y CONFIG_ECDSA=y -- 2.43.0
From: Simon Glass <sjg@chromium.org> There are quite a few media devices in test.dts which are not enabled by default, so are not bound on startup. Sometimes it is useful to be able to use these from the command line. Add 'sb devon' and 'sb devoff' subcommands to enable and disable devices from the device tree. For example, running sandbox with -T, then 'sb devon mmc11' enables the mmc11 device mentioned in test.dts Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- cmd/sb.c | 107 +++++++++++++++++++++++++++++++++++-- doc/usage/cmd/sb.rst | 40 +++++++++++++- test/cmd/Makefile | 1 + test/cmd/sb.c | 123 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 267 insertions(+), 4 deletions(-) create mode 100644 test/cmd/sb.c diff --git a/cmd/sb.c b/cmd/sb.c index 79f3fb0aacd..7de43e7edc3 100644 --- a/cmd/sb.c +++ b/cmd/sb.c @@ -10,6 +10,10 @@ #include <asm/cpu.h> #include <asm/global_data.h> #include <asm/state.h> +#include <dm/device-internal.h> +#include <dm/lists.h> + +DECLARE_GLOBAL_DATA_PTR; static int do_sb_handoff(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) @@ -47,12 +51,109 @@ static int do_sb_state(struct cmd_tbl *cmdtp, int flag, int argc, return 0; } +static int do_sb_devon(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + ofnode root, node; + int ret; + + if (argc != 2) + return CMD_RET_USAGE; + + /* Find the specified device tree node */ + root = oftree_root(oftree_default()); + node = ofnode_find_subnode(root, argv[1]); + if (!ofnode_valid(node)) { + printf("Device tree node '%s' not found\n", argv[1]); + return CMD_RET_FAILURE; + } + + /* Check if device is already bound */ + ret = device_find_global_by_ofnode(node, &dev); + if (!ret) { + printf("Device '%s' is already enabled\n", argv[1]); + return CMD_RET_FAILURE; + } + + /* Bind the device from device tree */ + ret = lists_bind_fdt(gd->dm_root, node, &dev, NULL, false); + if (ret) { + printf("Failed to bind device '%s' (err %dE)\n", argv[1], + ret); + return CMD_RET_FAILURE; + } + + /* Probe the device */ + ret = device_probe(dev); + if (ret) { + printf("Failed to probe device '%s' (err %dE)\n", argv[1], + ret); + return CMD_RET_FAILURE; + } + + printf("Device '%s' enabled\n", dev->name); + + return CMD_RET_SUCCESS; +} + +static int do_sb_devoff(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + ofnode root, node; + int ret; + + if (argc != 2) + return CMD_RET_USAGE; + + /* Find the specified device tree node */ + root = oftree_root(oftree_default()); + node = ofnode_find_subnode(root, argv[1]); + if (!ofnode_valid(node)) { + printf("Device tree node '%s' not found\n", argv[1]); + return CMD_RET_FAILURE; + } + + /* Find the device bound to this node */ + ret = device_find_global_by_ofnode(node, &dev); + if (ret) { + printf("Device '%s' not found or not bound (err %dE)\n", + argv[1], ret); + return CMD_RET_FAILURE; + } + + /* Remove the device (deactivate it) */ + ret = device_remove(dev, DM_REMOVE_NORMAL); + if (ret) { + printf("Failed to remove device '%s' (err %dE)\n", argv[1], + ret); + return CMD_RET_FAILURE; + } + + /* Unbind the device */ + ret = device_unbind(dev); + if (ret) { + printf("Failed to unbind device '%s' (err %dE)\n", argv[1], + ret); + return CMD_RET_FAILURE; + } + + printf("Device '%s' disabled\n", argv[1]); + + return CMD_RET_SUCCESS; +} + U_BOOT_LONGHELP(sb, - "handoff - Show handoff data received from SPL\n" - "sb map - Show mapped memory\n" - "sb state - Show sandbox state"); + "devoff <node> - Disable device from device tree node\n" + "sb devon <node> - Enable device from device tree node\n" + "sb handoff - Show handoff data received from SPL\n" + "sb map - Show mapped memory\n" + "sb state - Show sandbox state"); U_BOOT_CMD_WITH_SUBCMDS(sb, "Sandbox status commands", sb_help_text, + U_BOOT_SUBCMD_MKENT(devoff, 2, 1, do_sb_devoff), + U_BOOT_SUBCMD_MKENT(devon, 2, 1, do_sb_devon), U_BOOT_SUBCMD_MKENT(handoff, 1, 1, do_sb_handoff), U_BOOT_SUBCMD_MKENT(map, 1, 1, do_sb_map), U_BOOT_SUBCMD_MKENT(state, 1, 1, do_sb_state)); diff --git a/doc/usage/cmd/sb.rst b/doc/usage/cmd/sb.rst index 37431aff7c8..ee9b3d80767 100644 --- a/doc/usage/cmd/sb.rst +++ b/doc/usage/cmd/sb.rst @@ -11,6 +11,8 @@ Synopsis :: + sb devoff <node> + sb devon <node> sb handoff sb map sb state @@ -19,7 +21,24 @@ Description ----------- The *sb* command is used to display information about sandbox's internal -operation. See :doc:`/arch/sandbox/index` for more information. +operation and to manage devices. See :doc:`/arch/sandbox/index` for more +information. + +sb devoff +~~~~~~~~~ + +This disables a device that was previously enabled with *sb devon*. The device +is removed (deactivated) and unbound from the driver model. The parameter is +the name of a device tree node. + +sb devon +~~~~~~~~ + +This enables a device from the device tree. The device tree node is located, +bound to the driver model, and probed (activated). This is useful for testing +devices that are not automatically bound at startup, i.e. those marked as +status = "disabled" in the device tree. The parameter is the name of a root +devicetree node. sb handoff ~~~~~~~~~~ @@ -54,6 +73,25 @@ command-line with which sandbox was started. Example ------- +This shows enabling a device from the `test.dts` device tree. Note that sandbox +must be run with the -T flag to use the test device tree:: + + => sb devon mmc11 + Device 'mmc11' enabled + => ls mmc b + extlinux/ + 7 initrd.img-6.8.0-53-generic + 1616 vmlinuz-6.8.0-53-generic + + 2 file(s), 1 dir(s) + + => sb devoff mmc11 + Device 'mmc11' disabled + => ls mmc b + ** Bad device specification mmc b ** + Couldn't find partition mmc b + => + This shows checking for the presence of SPL-handoff information. For this to work, ``u-boot-spl`` must be run, with build that enables ``CONFIG_SPL``, such as ``sandbox_spl``:: diff --git a/test/cmd/Makefile b/test/cmd/Makefile index 9cd8ea3aaf0..4d8f93e2551 100644 --- a/test/cmd/Makefile +++ b/test/cmd/Makefile @@ -41,6 +41,7 @@ obj-$(CONFIG_CMD_MBR) += mbr.o obj-$(CONFIG_CMD_PINMUX) += pinmux.o obj-$(CONFIG_CMD_PWM) += pwm.o obj-$(CONFIG_CMD_READ) += rw.o +obj-y += sb.o obj-$(CONFIG_CMD_SETEXPR) += setexpr.o obj-$(CONFIG_CMD_SMBIOS) += smbios.o obj-$(CONFIG_CMD_TEMPERATURE) += temperature.o diff --git a/test/cmd/sb.c b/test/cmd/sb.c new file mode 100644 index 00000000000..cb871dffd57 --- /dev/null +++ b/test/cmd/sb.c @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Test for sb command + * + * Copyright (C) 2025 Canonical Ltd + */ + +#include <dm.h> +#include <dm/test.h> +#include <test/test.h> +#include <test/ut.h> + +/* Basic test of 'sb devon' and 'sb devoff' commands */ +static int dm_test_sb_devon_devoff(struct unit_test_state *uts) +{ + struct udevice *dev; + ofnode root, node; + + /* Find the mmc11 device tree node */ + root = oftree_root(oftree_default()); + node = ofnode_find_subnode(root, "mmc11"); + ut_assert(ofnode_valid(node)); + + /* Verify device is not initially bound */ + ut_assert(device_find_global_by_ofnode(node, &dev)); + + /* Enable the device using 'sb devon' */ + ut_assertok(run_command("sb devon mmc11", 0)); + ut_assert_nextline("Device 'mmc11' enabled"); + ut_assert_console_end(); + + /* Verify device is now bound and probed */ + ut_assertok(device_find_global_by_ofnode(node, &dev)); + ut_assertnonnull(dev); + ut_assert(device_active(dev)); + + /* Disable the device using 'sb devoff' */ + ut_assertok(run_command("sb devoff mmc11", 0)); + ut_assert_nextline("Device 'mmc11' disabled"); + ut_assert_console_end(); + + /* Verify device is no longer bound */ + ut_assert(device_find_global_by_ofnode(node, &dev)); + + return 0; +} +DM_TEST(dm_test_sb_devon_devoff, UTF_SCAN_FDT | UTF_CONSOLE); + +/* Test 'sb devon' with invalid node */ +static int dm_test_sb_devon_invalid(struct unit_test_state *uts) +{ + /* Try to enable non-existent device */ + ut_asserteq(1, run_command("sb devon nonexistent", 0)); + ut_assert_nextline("Device tree node 'nonexistent' not found"); + ut_assert_console_end(); + + return 0; +} +DM_TEST(dm_test_sb_devon_invalid, UTF_SCAN_FDT | UTF_CONSOLE); + +/* Test 'sb devoff' with invalid node */ +static int dm_test_sb_devoff_invalid(struct unit_test_state *uts) +{ + /* Try to disable non-existent device */ + ut_asserteq(1, run_command("sb devoff nonexistent", 0)); + ut_assert_nextline("Device tree node 'nonexistent' not found"); + ut_assert_console_end(); + + return 0; +} +DM_TEST(dm_test_sb_devoff_invalid, UTF_SCAN_FDT | UTF_CONSOLE); + +/* Test 'sb devon' on device that's already enabled */ +static int dm_test_sb_devon_already_enabled(struct unit_test_state *uts) +{ + /* Enable the device first */ + ut_assertok(run_command("sb devon mmc11", 0)); + ut_assert_nextline("Device 'mmc11' enabled"); + ut_assert_console_end(); + + /* Try to enable it again - should fail */ + ut_asserteq(1, run_command("sb devon mmc11", 0)); + ut_assert_nextline("Device 'mmc11' is already enabled"); + ut_assert_console_end(); + + /* Clean up - disable the device */ + ut_assertok(run_command("sb devoff mmc11", 0)); + ut_assert_nextline("Device 'mmc11' disabled"); + ut_assert_console_end(); + + return 0; +} +DM_TEST(dm_test_sb_devon_already_enabled, UTF_SCAN_FDT | UTF_CONSOLE); + +/* Test 'sb devoff' on device that's not bound */ +static int dm_test_sb_devoff_not_bound(struct unit_test_state *uts) +{ + struct udevice *dev; + ofnode root, node; + + /* Find the mmc11 device tree node */ + root = oftree_root(oftree_default()); + node = ofnode_find_subnode(root, "mmc11"); + ut_assert(ofnode_valid(node)); + + /* Ensure device is not bound (clean up from any previous test) */ + if (!device_find_global_by_ofnode(node, &dev)) { + ut_assertok(run_command("sb devoff mmc11", 0)); + ut_assert_nextlinen("Device 'mmc11' disabled"); + ut_assert_console_end(); + } + + /* Verify device is not bound */ + ut_assert(device_find_global_by_ofnode(node, &dev)); + + /* Try to disable a device that's not bound */ + ut_asserteq(1, run_command("sb devoff mmc11", 0)); + ut_assert_nextlinen("Device 'mmc11' not found or not bound"); + ut_assert_console_end(); + + return 0; +} +DM_TEST(dm_test_sb_devoff_not_bound, UTF_SCAN_FDT | UTF_CONSOLE); -- 2.43.0
From: Simon Glass <sjg@chromium.org> The mbedtls hashing has not been integrated into U-Boot's hash API. As a first step, add the mbedtls hash type into struct hash_algo and provide the values. This allows looking up the type by its name. Signed-off-by: Simon Glass <sjg@chromium.org> --- common/hash.c | 5 +++++ include/hash.h | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/common/hash.c b/common/hash.c index 0c45992d5c7..2e3b12c109a 100644 --- a/common/hash.c +++ b/common/hash.c @@ -212,6 +212,7 @@ static struct hash_algo hash_algo[] = { .digest_size = MD5_SUM_LEN, .chunk_size = CHUNKSZ_MD5, .hash_func_ws = md5_wd, + HASH_MBEDTLS_TYPE(MBEDTLS_MD_MD5) }, #endif #if CONFIG_IS_ENABLED(SHA1) @@ -233,6 +234,7 @@ static struct hash_algo hash_algo[] = { .hash_update = hash_update_sha1, .hash_finish = hash_finish_sha1, #endif + HASH_MBEDTLS_TYPE(MBEDTLS_MD_SHA1) }, #endif #if CONFIG_IS_ENABLED(SHA256) @@ -254,6 +256,7 @@ static struct hash_algo hash_algo[] = { .hash_update = hash_update_sha256, .hash_finish = hash_finish_sha256, #endif + HASH_MBEDTLS_TYPE(MBEDTLS_MD_SHA256) }, #endif #if CONFIG_IS_ENABLED(SHA384) @@ -275,6 +278,7 @@ static struct hash_algo hash_algo[] = { .hash_update = hash_update_sha384, .hash_finish = hash_finish_sha384, #endif + HASH_MBEDTLS_TYPE(MBEDTLS_MD_SHA384) }, #endif #if CONFIG_IS_ENABLED(SHA512) @@ -296,6 +300,7 @@ static struct hash_algo hash_algo[] = { .hash_update = hash_update_sha512, .hash_finish = hash_finish_sha512, #endif + HASH_MBEDTLS_TYPE(MBEDTLS_MD_SHA512) }, #endif #if CONFIG_IS_ENABLED(CRC16) diff --git a/include/hash.h b/include/hash.h index 8b3f79ec473..46a2be72f7e 100644 --- a/include/hash.h +++ b/include/hash.h @@ -10,6 +10,15 @@ #include <linux/kconfig.h> #endif +#if !defined(USE_HOSTCC) && CONFIG_IS_ENABLED(MBEDTLS_LIB) +#include <mbedtls_options.h> +#include <mbedtls/md.h> + +#define HASH_MBEDTLS_TYPE(_val) .md_type = _val, +#else +#define HASH_MBEDTLS_TYPE(_val) +#endif + struct cmd_tbl; /* @@ -44,6 +53,9 @@ struct hash_algo { void (*hash_func_ws)(const unsigned char *input, unsigned int ilen, unsigned char *output, unsigned int chunk_sz); int chunk_size; /* Watchdog chunk size */ +#if !defined(USE_HOSTCC) && CONFIG_IS_ENABLED(MBEDTLS_LIB) + mbedtls_md_type_t md_type; /* mbedtls hash type */ +#endif /* * hash_init: Create the context for progressive hashing * @@ -120,7 +132,26 @@ int hash_command(const char *algo_name, int flags, struct cmd_tbl *cmdtp, int hash_block(const char *algo_name, const void *data, unsigned int len, uint8_t *output, int *output_size); -#endif /* !USE_HOSTCC */ +#if CONFIG_IS_ENABLED(MBEDTLS_LIB) +static inline mbedtls_md_type_t hash_mbedtls_type(struct hash_algo *algo) +{ + return algo->md_type; +} +#else +static inline int hash_mbedtls_type(struct hash_algo *algo) +{ + return 0; +} +#endif + +#else /* USE_HOSTCC*/ + +static inline int hash_mbedtls_type(struct hash_algo *algo) +{ + return 0; +} + +#endif /* USE_HOSTCC */ /** * hash_lookup_algo() - Look up the hash_algo struct for an algorithm -- 2.43.0
From: Simon Glass <sjg@chromium.org> Add a few Kconfig options to allow PKCS#5 (PBKDF2) to be used within U-Boot Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- lib/mbedtls/Kconfig | 14 ++++++++++++++ lib/mbedtls/Makefile | 2 ++ lib/mbedtls/mbedtls_def_config.h | 4 ++++ 3 files changed, 20 insertions(+) diff --git a/lib/mbedtls/Kconfig b/lib/mbedtls/Kconfig index 789721ee6cd..2af043ba5b1 100644 --- a/lib/mbedtls/Kconfig +++ b/lib/mbedtls/Kconfig @@ -231,6 +231,13 @@ config HKDF_MBEDTLS This option enables support of key derivation using HKDF algorithm with MbedTLS crypto library. +config PKCS5_MBEDTLS + bool "Enable PKCS#5 support with MbedTLS crypto library" + depends on MBEDTLS_LIB_CRYPTO + help + This option enables support of PKCS#5 functions (PBKDF2) with + MbedTLS crypto library. Required for LUKS decryption. + endif # MBEDTLS_LIB_CRYPTO config MBEDTLS_LIB_X509 @@ -489,6 +496,13 @@ config SPL_HKDF_MBEDTLS This option enables support of key derivation using HKDF algorithm with MbedTLS crypto library in SPL. +config SPL_PKCS5_MBEDTLS + bool "Enable PKCS#5 support with MbedTLS crypto library (SPL)" + depends on SPL_MBEDTLS_LIB_CRYPTO + help + This option enables support of PKCS#5 functions (PBKDF2) with + MbedTLS crypto library in SPL. Required for LUKS decryption. + endif # SPL_MBEDTLS_LIB_CRYPTO config SPL_MBEDTLS_LIB_X509 diff --git a/lib/mbedtls/Makefile b/lib/mbedtls/Makefile index c5b445bd85c..0506a5a6b3e 100644 --- a/lib/mbedtls/Makefile +++ b/lib/mbedtls/Makefile @@ -35,6 +35,8 @@ mbedtls_lib_crypto-$(CONFIG_$(PHASE_)SHA512_MBEDTLS) += \ $(MBEDTLS_LIB_DIR)/sha512.o mbedtls_lib_crypto-$(CONFIG_$(PHASE_)HKDF_MBEDTLS) += \ $(MBEDTLS_LIB_DIR)/hkdf.o +mbedtls_lib_crypto-$(CONFIG_$(PHASE_)PKCS5_MBEDTLS) += \ + $(MBEDTLS_LIB_DIR)/pkcs5.o # MbedTLS X509 library obj-$(CONFIG_$(XPL_)MBEDTLS_LIB_X509) += mbedtls_lib_x509.o diff --git a/lib/mbedtls/mbedtls_def_config.h b/lib/mbedtls/mbedtls_def_config.h index dda3f4dd6e4..9e3beed07f4 100644 --- a/lib/mbedtls/mbedtls_def_config.h +++ b/lib/mbedtls/mbedtls_def_config.h @@ -60,6 +60,10 @@ #define MBEDTLS_HKDF_C #endif +#if CONFIG_IS_ENABLED(PKCS5_MBEDTLS) +#define MBEDTLS_PKCS5_C +#endif + #if CONFIG_IS_ENABLED(MBEDTLS_LIB_X509) #if CONFIG_IS_ENABLED(X509_CERTIFICATE_PARSER) -- 2.43.0
From: Simon Glass <sjg@chromium.org> At present the partition size is always the same as the filesystem within it. Add a way to specify a larger size, to make space for LUTS tables needed for full-disk encryption. Signed-off-by: Simon Glass <sjg@chromium.org> --- test/py/tests/fs_helper.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/test/py/tests/fs_helper.py b/test/py/tests/fs_helper.py index baf61cbd232..e4ac946064f 100644 --- a/test/py/tests/fs_helper.py +++ b/test/py/tests/fs_helper.py @@ -42,7 +42,7 @@ class FsHelper: 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): + def __init__(self, config, fs_type, size_mb, prefix, part_mb=None): """Set up a new object Args: @@ -51,6 +51,9 @@ class FsHelper: fat12, fat16, fat32, exfat, fs_generic (which means vfat) size_mb (int): Size of file system in MB prefix (str): Prefix string of volume's file name + part_mb (int, optional): Size of partition in MB. If None, defaults + to size_mb. This can be used to make the partition larger than + the filesystem, to create space for disk-encryption metadata """ if ('fat' not in fs_type and 'ext' not in fs_type and fs_type not in ['exfat', 'fs_generic']): @@ -59,6 +62,7 @@ class FsHelper: self.config = config self.fs_type = fs_type self.size_mb = size_mb + self.partition_mb = part_mb if part_mb is not None else size_mb self.prefix = prefix self.quiet = True @@ -222,10 +226,10 @@ class DiskHelper: for fsi, part_type, bootable in self.fs_list: if spec: spec += '\n' - spec += f'type={part_type:x}, size={fsi.size_mb}M, start={pos}M' + spec += f'type={part_type:x}, size={fsi.partition_mb}M, start={pos}M' if bootable: spec += ', bootable' - pos += fsi.size_mb + pos += fsi.partition_mb img_size = pos try: @@ -241,7 +245,7 @@ class DiskHelper: check_call( f'dd if={fsi.fs_img} of={self.fname} bs=1M seek={pos} conv=notrunc', shell=True) - pos += fsi.size_mb + pos += fsi.partition_mb return self.fname def cleanup(self): -- 2.43.0
From: Simon Glass <sjg@chromium.org> Add a parameter to indicate the size of the root partition so that it can have space for the LUTS metadata. Move the import of gzip to the top of the file while we are here. Signed-off-by: Simon Glass <sjg@chromium.org> --- test/py/img/common.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/test/py/img/common.py b/test/py/img/common.py index c9be6a22362..a08744fbb76 100644 --- a/test/py/img/common.py +++ b/test/py/img/common.py @@ -3,6 +3,7 @@ """Common utilities for image creation""" +import gzip import os import utils @@ -32,7 +33,7 @@ def copy_partition(ubman, fsfile, outname): def setup_extlinux_image(config, log, devnum, basename, vmlinux, initrd, dtbdir, - script): + script, part2_size=1): """Create a 20MB disk image with a single FAT partition Args: @@ -44,9 +45,8 @@ def setup_extlinux_image(config, log, devnum, basename, vmlinux, initrd, dtbdir, initrd (str): Ramdisk filename dtbdir (str or None): Devicetree filename script (str): Script to place in the extlinux.conf file + part2_size (int): Size of second partition in MB (default: 1) """ - import gzip - fsh = FsHelper(config, 'vfat', 18, prefix=basename) fsh.setup() @@ -79,8 +79,15 @@ def setup_extlinux_image(config, log, devnum, basename, vmlinux, initrd, dtbdir, img = DiskHelper(config, devnum, basename, True) img.add_fs(fsh, DiskHelper.VFAT, bootable=True) - ext4 = FsHelper(config, 'ext4', 1, prefix=basename) + ext4 = FsHelper(config, 'ext4', max(1, part2_size - 30), prefix=basename, + part_mb=part2_size) ext4.setup() + + bindir = os.path.join(ext4.srcdir, 'bin') + mkdir_cond(bindir) + with open(os.path.join(bindir, 'bash'), 'w', encoding='ascii') as fd: + print('bash', file=fd) + ext4.mk_fs() img.add_fs(ext4, DiskHelper.EXT4) -- 2.43.0
From: Simon Glass <sjg@chromium.org> Update one of the Ubuntu images to provide space for using full-disk encryption. Signed-off-by: Simon Glass <sjg@chromium.org> --- test/py/img/ubuntu.py | 11 ++++++++--- test/py/tests/test_ut.py | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/test/py/img/ubuntu.py b/test/py/img/ubuntu.py index 1247ec134d5..c60da4a0c41 100644 --- a/test/py/img/ubuntu.py +++ b/test/py/img/ubuntu.py @@ -6,14 +6,19 @@ from img.common import setup_extlinux_image -def setup_ubuntu_image(config, log, devnum, basename, version='24.04.1 LTS'): - """Create a 20MB Ubuntu disk image with a single FAT partition +def setup_ubuntu_image(config, log, devnum, basename, version='24.04.1 LTS', + use_fde=False): + """Create a Ubuntu disk image with a FAT partition and ext4 partition + + This creates a FAT partition containing extlinux files, kernel, etc. and a + separate ext4 partition containing the root disk Args: config (ArbitraryAttributeContainer): Configuration log (multiplexed_log.Logfile): Log to write to devnum (int): Device number to use, e.g. 1 basename (str): Base name to use in the filename, e.g. 'mmc' + use_fde (bool): True to set up full-disk encryption """ vmlinux = 'vmlinuz-6.8.0-53-generic' initrd = 'initrd.img-6.8.0-53-generic' @@ -44,4 +49,4 @@ label l0r initrd /boot/%s ''' % ((version, vmlinux, initrd) * 2) setup_extlinux_image(config, log, devnum, basename, vmlinux, initrd, dtbdir, - script) + script, part2_size=60 if use_fde else 1) diff --git a/test/py/tests/test_ut.py b/test/py/tests/test_ut.py index b532bdced13..218f5a74ac1 100644 --- a/test/py/tests/test_ut.py +++ b/test/py/tests/test_ut.py @@ -83,7 +83,7 @@ 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) - setup_ubuntu_image(u_boot_config, u_boot_log, 11, 'mmc') + setup_ubuntu_image(u_boot_config, u_boot_log, 11, 'mmc', use_fde=True) def test_ut(ubman, ut_subtest): """Execute a "ut" subtest. -- 2.43.0
From: Simon Glass <sjg@chromium.org> Add cryptsetup to the CI Docker image to enable LUKS encryption tests. This is needed to create test images. Signed-off-by: Simon Glass <sjg@chromium.org> --- tools/docker/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/docker/Dockerfile b/tools/docker/Dockerfile index ba98a293e4b..4665304fbfc 100644 --- a/tools/docker/Dockerfile +++ b/tools/docker/Dockerfile @@ -69,6 +69,7 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ clang-18 \ coreutils \ cpio \ + cryptsetup \ curl \ device-tree-compiler \ dosfstools \ -- 2.43.0
From: Simon Glass <sjg@chromium.org> Update the GitLab CI Docker image to use the new build with cryptsetup support for LUKS testing. This appears to require use of sudo, so add a comment about that. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- .gitlab-ci.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 534426f0538..68f2534918c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -20,7 +20,7 @@ default: - ${DEFAULT_TAG} # Grab our configured image. The source for this is found # in the u-boot tree at tools/docker/Dockerfile - image: ${MIRROR_DOCKER}/sjg20/u-boot-gitlab-ci-runner:jammy-20250714-10Sep2025p2 + image: ${MIRROR_DOCKER}/sjg20/u-boot-gitlab-ci-runner:jammy-20250404-24Oct2025p1 services: - name: container-$(CI_JOB_ID) command: ["--rm"] @@ -37,6 +37,10 @@ stages: .buildman_and_testpy_template: &buildman_and_testpy_dfn stage: test.py retry: 2 # QEMU may be too slow, etc. + # Note: These tests require device-mapper access for LUKS encryption tests. + # The GitLab runner must be configured with: + # - privileged = true in /etc/gitlab-runner/config.toml + # OR passwordless sudo for: modprobe, cryptsetup, dd rules: - if: $LAB_ONLY == "1" when: never -- 2.43.0
From: Simon Glass <sjg@chromium.org> If requested, use cryptsetup to encrypt a partition with the provided passphrase. This requires use of sudo since there doesn't seem to be any other way to write files into a filesystem on an encrypted disk. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- test/py/tests/fs_helper.py | 109 ++++++++++++++++++++++++++++++++++++- 1 file changed, 107 insertions(+), 2 deletions(-) diff --git a/test/py/tests/fs_helper.py b/test/py/tests/fs_helper.py index e4ac946064f..49747be1788 100644 --- a/test/py/tests/fs_helper.py +++ b/test/py/tests/fs_helper.py @@ -8,7 +8,7 @@ import re import os import shutil -from subprocess import call, check_call, check_output, CalledProcessError +from subprocess import call, check_call, check_output, CalledProcessError, run from subprocess import DEVNULL import tempfile @@ -38,11 +38,20 @@ class FsHelper: fsh.mk_fs() ... + To create an encrypted LUKS1 partition: + + with FsHelper(ubman.config, 'ext4', 10, 'mmc1', + encrypt_passphrase='test') as fsh: + # create files in the fsh.srcdir directory + fsh.mk_fs() # Creates and encrypts the filesystem + ... + 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): + def __init__(self, config, fs_type, size_mb, prefix, part_mb=None, + encrypt_passphrase=None): """Set up a new object Args: @@ -54,6 +63,8 @@ class FsHelper: part_mb (int, optional): Size of partition in MB. If None, defaults to size_mb. This can be used to make the partition larger than the filesystem, to create space for disk-encryption metadata + encrypt_passphrase (str, optional): If provided, encrypt the + filesystem with LUKS1 using this passphrase """ if ('fat' not in fs_type and 'ext' not in fs_type and fs_type not in ['exfat', 'fs_generic']): @@ -65,6 +76,7 @@ class FsHelper: self.partition_mb = part_mb if part_mb is not None else size_mb self.prefix = prefix self.quiet = True + self.encrypt_passphrase = encrypt_passphrase # Use a default filename; the caller can adjust it leaf = f'{prefix}.{fs_type}.img' @@ -136,6 +148,10 @@ class FsHelper: check_call(f'mcopy -i {fs_img} {flags} {self.srcdir}/* ::/', shell=True) + # Encrypt the filesystem if requested + if self.encrypt_passphrase: + self.encrypt_luks(self.encrypt_passphrase) + def setup(self): """Set up the srcdir ready to receive files""" if not self.srcdir: @@ -149,6 +165,95 @@ class FsHelper: self.tmpdir = tempfile.TemporaryDirectory('fs_helper') self.srcdir = self.tmpdir.name + def encrypt_luks(self, passphrase): + """Encrypt the filesystem image with LUKS1 + + This replaces the filesystem image with a LUKS1-encrypted version. + LUKS1 is used because U-Boot's unlock implementation currently only + supports LUKS version 1. + + Args: + passphrase (str): Passphrase for the LUKS container + + Returns: + str: Path to the encrypted image + + Raises: + CalledProcessError: If cryptsetup is not available or fails + """ + # LUKS1 encryption parameters + cipher = 'aes-cbc-essiv:sha256' + key_size = 256 + hash_alg = 'sha256' + + # Save the original filesystem image + orig_fs_img = f'{self.fs_img}.orig' + os.rename(self.fs_img, orig_fs_img) + + # Create a new image file for the LUKS container + luks_img = self.fs_img + luks_size_mb = self.partition_mb + check_call(f'dd if=/dev/zero of={luks_img} bs=1M count={luks_size_mb}', + shell=True, stdout=DEVNULL if self.quiet else None) + + # Ensure device-mapper kernel module is loaded + if not os.path.exists('/sys/class/misc/device-mapper'): + # Try to load the dm_mod kernel module + result = run(['sudo', 'modprobe', 'dm_mod'], + stdout=DEVNULL, stderr=DEVNULL, check=False) + if result.returncode != 0: + raise RuntimeError( + '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.') + + device_name = f'luks_test_{os.getpid()}' + + # Clean up any stale device with the same name + run(['sudo', 'cryptsetup', 'close', device_name], + stdout=DEVNULL, stderr=DEVNULL, check=False) + + try: + # Format as LUKS1 + run(['cryptsetup', 'luksFormat', + '--type', 'luks1', + '--cipher', cipher, + '--key-size', str(key_size), + '--hash', hash_alg, + '--iter-time', '10', # Very fast for testing (low security) + luks_img], + input=f'{passphrase}\n'.encode(), + 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(), + stdout=DEVNULL if self.quiet else None, stderr=None, + check=True) + # Copy the filesystem data into the LUKS container + check_call(f'sudo dd if={orig_fs_img} of=/dev/mapper/{device_name} bs=1M', + shell=True, stdout=DEVNULL if self.quiet else None) + + # Remove the original filesystem image + os.remove(orig_fs_img) + + except Exception: + # Clean up on error + if os.path.exists(luks_img): + os.remove(luks_img) + if os.path.exists(orig_fs_img): + os.rename(orig_fs_img, self.fs_img) + raise + finally: + # Always close the device if it's still open + run(['sudo', 'cryptsetup', 'close', device_name], + stdout=DEVNULL, stderr=DEVNULL, check=False) + + return self.fs_img + def cleanup(self): """Remove created image""" if self.tmpdir: -- 2.43.0
From: Simon Glass <sjg@chromium.org> Encrypt the ext4 partition in this image so that we can use it for tests. Signed-off-by: Simon Glass <sjg@chromium.org> --- test/py/img/common.py | 6 ++++-- test/py/img/ubuntu.py | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/test/py/img/common.py b/test/py/img/common.py index a08744fbb76..01745ce73b3 100644 --- a/test/py/img/common.py +++ b/test/py/img/common.py @@ -33,7 +33,7 @@ def copy_partition(ubman, fsfile, outname): def setup_extlinux_image(config, log, devnum, basename, vmlinux, initrd, dtbdir, - script, part2_size=1): + script, part2_size=1, use_fde=False): """Create a 20MB disk image with a single FAT partition Args: @@ -46,6 +46,7 @@ def setup_extlinux_image(config, log, devnum, basename, vmlinux, initrd, dtbdir, dtbdir (str or None): Devicetree filename script (str): Script to place in the extlinux.conf file part2_size (int): Size of second partition in MB (default: 1) + use_fde (bool): True to encrypt the ext4 partition with LUKS1 """ fsh = FsHelper(config, 'vfat', 18, prefix=basename) fsh.setup() @@ -80,7 +81,8 @@ def setup_extlinux_image(config, log, devnum, basename, vmlinux, initrd, dtbdir, img.add_fs(fsh, DiskHelper.VFAT, bootable=True) ext4 = FsHelper(config, 'ext4', max(1, part2_size - 30), prefix=basename, - part_mb=part2_size) + part_mb=part2_size, + encrypt_passphrase='test' if use_fde else None) ext4.setup() bindir = os.path.join(ext4.srcdir, 'bin') diff --git a/test/py/img/ubuntu.py b/test/py/img/ubuntu.py index c60da4a0c41..58ee9f20277 100644 --- a/test/py/img/ubuntu.py +++ b/test/py/img/ubuntu.py @@ -49,4 +49,5 @@ label l0r initrd /boot/%s ''' % ((version, vmlinux, initrd) * 2) setup_extlinux_image(config, log, devnum, basename, vmlinux, initrd, dtbdir, - script, part2_size=60 if use_fde else 1) + script, part2_size=60 if use_fde else 1, + use_fde=use_fde) -- 2.43.0
From: Simon Glass <sjg@chromium.org> Linux Unified Key Setup (LUKS) provides a way to encryption a disk partition with a a key an later unlock it. There are two versions (1 and 2). Add a definition of the main structures and the ability to detect a LUKS partition. Enable this for the sandbox board. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- MAINTAINERS | 8 +++ configs/sandbox_defconfig | 1 + drivers/block/Kconfig | 21 ++++++ drivers/block/Makefile | 1 + drivers/block/luks.c | 63 +++++++++++++++++ include/luks.h | 140 ++++++++++++++++++++++++++++++++++++++ test/boot/Makefile | 1 + test/boot/luks.c | 67 ++++++++++++++++++ 8 files changed, 302 insertions(+) create mode 100644 drivers/block/luks.c create mode 100644 include/luks.h create mode 100644 test/boot/luks.c diff --git a/MAINTAINERS b/MAINTAINERS index 2a2a42aec5e..08d3806b6c7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1289,6 +1289,14 @@ F: lib/getopt.c F: test/log/ F: test/py/tests/test_log.py +LUKS +M: Simon Glass <sjg@chromium.org> +S: Maintained +T: git https://concept.u-boot.org/u-boot/u-boot.git +F: drivers/block/luks.c +F: include/luks.h +F: test/boot/luks.c + MALI DISPLAY PROCESSORS M: Liviu Dudau <liviu.dudau@foss.arm.com> S: Supported diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig index 601afde421d..6a4b3e4363a 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -362,6 +362,7 @@ CONFIG_ADDR_MAP=y CONFIG_PANIC_POWEROFF=y CONFIG_CMD_DHRYSTONE=y CONFIG_MBEDTLS_LIB=y +CONFIG_BLK_LUKS=y CONFIG_ECDSA=y CONFIG_ECDSA_VERIFY=y CONFIG_TPM=y diff --git a/drivers/block/Kconfig b/drivers/block/Kconfig index adf13f2a243..b07012ec7c9 100644 --- a/drivers/block/Kconfig +++ b/drivers/block/Kconfig @@ -269,3 +269,24 @@ config RKMTD Enable "rkmtd" class and driver to create a virtual block device to transfer Rockchip boot block data to and from NAND with block orientate tools like "ums" and "rockusb". + +config BLK_LUKS + bool "Enable LUKS detection and decryption support" + depends on BLK && MBEDTLS_LIB + select BLKMAP + select AES + select SHA256 + select PBKDF2 + select PKCS5_MBEDTLS if MBEDTLS_LIB_CRYPTO + help + This provides support for detecting and decrypting LUKS (Linux Unified + Key Setup) encrypted partitions. LUKS is a disk encryption specification + used for full disk encryption on Linux systems. + + This option enables detection of both LUKS1 and LUKS2 encrypted + partitions by checking for the LUKS magic bytes and version + information in the partition header. + + LUKS1 decryption is supported using PBKDF2 key derivation and AES-CBC. + Decrypted partitions can be accessed transparently through the blkmap + device layer. diff --git a/drivers/block/Makefile b/drivers/block/Makefile index f5a9d8637a3..b428a5cfb78 100644 --- a/drivers/block/Makefile +++ b/drivers/block/Makefile @@ -11,6 +11,7 @@ endif ifndef CONFIG_XPL_BUILD obj-$(CONFIG_IDE) += ide.o +obj-$(CONFIG_BLK_LUKS) += luks.o obj-$(CONFIG_RKMTD) += rkmtd.o endif obj-$(CONFIG_SANDBOX) += sandbox.o host-uclass.o host_dev.o diff --git a/drivers/block/luks.c b/drivers/block/luks.c new file mode 100644 index 00000000000..189226cb0ab --- /dev/null +++ b/drivers/block/luks.c @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * LUKS (Linux Unified Key Setup) filesystem support + * + * Copyright (C) 2025 Canonical Ltd + */ + +#include <blk.h> +#include <dm.h> +#include <hexdump.h> +#include <log.h> +#include <luks.h> +#include <memalign.h> +#include <part.h> +#include <uboot_aes.h> +#include <linux/byteorder/generic.h> +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <mbedtls/md.h> +#include <mbedtls/pkcs5.h> + +int luks_get_version(struct udevice *blk, struct disk_partition *pinfo) +{ + struct blk_desc *desc; + int version; + + desc = dev_get_uclass_plat(blk); + + ALLOC_CACHE_ALIGN_BUFFER(unsigned char, buffer, desc->blksz); + + /* Read first block of the partition */ + if (blk_dread(desc, pinfo->start, 1, buffer) != 1) { + log_debug("Error: failed to read LUKS header\n"); + return -EIO; + } + + /* Check for LUKS magic bytes */ + if (memcmp(buffer, LUKS_MAGIC, LUKS_MAGIC_LEN)) + return -ENOENT; + + /* Read version field (16-bit big-endian at offset 6) */ + version = be16_to_cpu(*(__be16 *)(buffer + LUKS_MAGIC_LEN)); + + /* Validate version */ + if (version != LUKS_VERSION_1 && version != LUKS_VERSION_2) { + log_debug("Warning: unknown LUKS version %d\n", version); + return -EPROTONOSUPPORT; + } + + return version; +} + +int luks_detect(struct udevice *blk, struct disk_partition *pinfo) +{ + int version; + + version = luks_get_version(blk, pinfo); + if (IS_ERR_VALUE(version)) + return version; + + return 0; +} diff --git a/include/luks.h b/include/luks.h new file mode 100644 index 00000000000..ea6dd510c53 --- /dev/null +++ b/include/luks.h @@ -0,0 +1,140 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * LUKS (Linux Unified Key Setup) filesystem support + * + * Copyright (C) 2025 Canonical Ltd + */ + +#ifndef __LUKS_H__ +#define __LUKS_H__ + +#include <linux/types.h> + +struct udevice; +struct disk_partition; + +/* LUKS magic bytes: "LUKS" followed by 0xba 0xbe */ +#define LUKS_MAGIC "LUKS\xba\xbe" +#define LUKS_MAGIC_LEN 6 + +/* LUKS versions */ +#define LUKS_VERSION_1 1 +#define LUKS_VERSION_2 2 + +/* LUKS constants */ +#define LUKS_DIGESTSIZE 20 +#define LUKS_SALTSIZE 32 +#define LUKS_NUMKEYS 8 +#define LUKS_KEY_DISABLED 0x0000dead +#define LUKS_KEY_ENABLED 0x00ac71f3 +#define LUKS_STRIPES 4000 + +/** + * struct luks1_keyslot - LUKS1 key slot + * + * @active: Key slot state (LUKS_KEY_ENABLED/DISABLED) + * @iterations: PBKDF2 iteration count + * @salt: Salt for PBKDF2 + * @key_material_offset: Start sector of key material + * @stripes: Number of anti-forensic stripes + */ +struct luks1_keyslot { + __be32 active; + __be32 iterations; + char salt[LUKS_SALTSIZE]; + __be32 key_material_offset; + __be32 stripes; +} __packed; + +/** + * struct luks1_phdr - LUKS1 header structure + * + * @magic: LUKS magic bytes + * @version: LUKS version + * @cipher_name: Cipher name + * @cipher_mode: Cipher mode + * @hash_spec: Hash specification + * @payload_offset: Payload offset in sectors + * @key_bytes: Key length in bytes + * @mk_digest: Master key digest + * @mk_digest_salt: Salt for master key digest + * @mk_digest_iter: Iterations for master key digest + * @uuid: Partition UUID + * @key_slot: Key slots (8 total) + */ +struct luks1_phdr { + char magic[LUKS_MAGIC_LEN]; + __be16 version; + char cipher_name[32]; + char cipher_mode[32]; + char hash_spec[32]; + __be32 payload_offset; + __be32 key_bytes; + char mk_digest[LUKS_DIGESTSIZE]; + char mk_digest_salt[LUKS_SALTSIZE]; + __be32 mk_digest_iter; + char uuid[40]; + struct luks1_keyslot key_slot[LUKS_NUMKEYS]; +} __packed; + +/** + * struct luks2_hdr - LUKS2 binary header + * + * @magic: LUKS magic bytes + * @version: LUKS version + * @hdr_size: Header size (includes binary header + JSON area) + * @seqid: Sequence ID + * @label: Label string + * @csum_alg: Checksum algorithm + * @salt: Salt for header checksum + * @uuid: Partition UUID + * @subsystem: Subsystem identifier + * @hdr_offset: Offset of this header + * @_padding: Reserved padding + * @csum: Header checksum + * @_padding4096: Padding to 4096 bytes + */ +struct luks2_hdr { + char magic[LUKS_MAGIC_LEN]; + __be16 version; + __be64 hdr_size; + __be64 seqid; + char label[48]; + char csum_alg[32]; + u8 salt[64]; + char uuid[40]; + char subsystem[48]; + __be64 hdr_offset; + u8 _padding[184]; + u8 csum[64]; + u8 _padding4096[3584]; +} __packed; + +/** + * luks_detect() - Detect if a partition is LUKS encrypted + * + * @blk: Block device + * @pinfo: Partition information + * Return: 0 if LUKS partition detected, -ve on error + */ +int luks_detect(struct udevice *blk, struct disk_partition *pinfo); + +/** + * luks_get_version() - Get LUKS version of a partition + * + * @blk: Block device + * @pinfo: Partition information + * Return: LUKS version (1 or 2) if detected, -ve on error + */ +int luks_get_version(struct udevice *blk, struct disk_partition *pinfo); + +/** + * luks_show_info() - Display LUKS header information + * + * @blk: Block device + * @pinfo: Partition information + * Return: 0 on success, -ve on error + */ +int luks_show_info(struct udevice *blk, struct disk_partition *pinfo); + +#endif /* __LUKS_H__ */ diff --git a/test/boot/Makefile b/test/boot/Makefile index 7c492ba92af..71c482f8d24 100644 --- a/test/boot/Makefile +++ b/test/boot/Makefile @@ -5,6 +5,7 @@ ifdef CONFIG_UT_BOOTSTD obj-$(CONFIG_BOOTSTD) += bootdev.o bootstd_common.o bootflow.o bootmeth.o obj-$(CONFIG_FIT) += image.o +obj-$(CONFIG_BLK_LUKS) += luks.o obj-$(CONFIG_EXPO) += expo.o expo_common.o obj-$(CONFIG_CEDIT) += cedit.o expo_common.o diff --git a/test/boot/luks.c b/test/boot/luks.c new file mode 100644 index 00000000000..684fd643c3a --- /dev/null +++ b/test/boot/luks.c @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Test for LUKS detection + * + * Copyright (C) 2025 Canonical Ltd + */ + +#include <blk.h> +#include <dm.h> +#include <luks.h> +#include <mmc.h> +#include <part.h> +#include <asm/global_data.h> +#include <dm/device-internal.h> +#include <dm/lists.h> +#include <dm/test.h> +#include <test/test.h> +#include <test/ut.h> +#include "bootstd_common.h" + +DECLARE_GLOBAL_DATA_PTR; + +/* Common function to setup mmc11 device */ +static int setup_mmc11(struct unit_test_state *uts, struct udevice **mmcp) +{ + ofnode root, node; + + /* Enable the mmc11 node */ + root = oftree_root(oftree_default()); + node = ofnode_find_subnode(root, "mmc11"); + ut_assert(ofnode_valid(node)); + ut_assertok(lists_bind_fdt(gd->dm_root, node, mmcp, NULL, false)); + + /* Probe the device */ + ut_assertok(device_probe(*mmcp)); + + return 0; +} + +/* Test LUKS detection on mmc11 partitions */ +static int bootstd_test_luks_detect(struct unit_test_state *uts) +{ + struct disk_partition info; + struct blk_desc *desc; + struct udevice *mmc; + int ret; + + ut_assertok(setup_mmc11(uts, &mmc)); + desc = blk_get_by_device(mmc); + ut_assertnonnull(desc); + ut_assertnonnull(desc->bdev); + + /* Check partition 1 - should NOT be LUKS */ + ut_assertok(part_get_info(desc, 1, &info)); + ret = luks_detect(desc->bdev, &info); + ut_assert(ret < 0); /* Should fail - not LUKS */ + + /* Check partition 2 - should BE LUKS */ + ut_assertok(part_get_info(desc, 2, &info)); + ut_assertok(luks_detect(desc->bdev, &info)); + + /* Verify it's LUKS version 1 */ + ut_asserteq(1, luks_get_version(desc->bdev, &info)); + + return 0; +} +BOOTSTD_TEST(bootstd_test_luks_detect, UTF_DM | UTF_SCAN_FDT | UTF_CONSOLE); -- 2.43.0
From: Simon Glass <sjg@chromium.org> Add a 'luks' command which allows querying a partition to see if it is encrypted using LUKS, as well as showing information about a LUKS partition. Provide some documentation and a test. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- MAINTAINERS | 2 + cmd/Kconfig | 9 +++ cmd/Makefile | 1 + cmd/luks.c | 67 ++++++++++++++++++++ doc/usage/cmd/luks.rst | 139 +++++++++++++++++++++++++++++++++++++++++ doc/usage/index.rst | 1 + drivers/block/luks.c | 59 +++++++++++++++-- test/boot/luks.c | 42 +++++++++++++ 8 files changed, 315 insertions(+), 5 deletions(-) create mode 100644 cmd/luks.c create mode 100644 doc/usage/cmd/luks.rst diff --git a/MAINTAINERS b/MAINTAINERS index 08d3806b6c7..9b00829db93 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1293,6 +1293,8 @@ LUKS M: Simon Glass <sjg@chromium.org> S: Maintained T: git https://concept.u-boot.org/u-boot/u-boot.git +F: cmd/luks.c +F: doc/usage/cmd/luks.rst F: drivers/block/luks.c F: include/luks.h F: test/boot/luks.c diff --git a/cmd/Kconfig b/cmd/Kconfig index f4d6e544cc0..a45df78c8fd 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -2819,6 +2819,15 @@ config CMD_FS_UUID help Enables fsuuid command for filesystem UUID. +config CMD_LUKS + bool "luks command" + depends on BLK_LUKS + default y if BLK_LUKS + help + Enables the 'luks' command for detecting LUKS (Linux Unified Key + Setup) encrypted partitions. This command checks if a partition + is LUKS encrypted and displays the LUKS version (1 or 2). + config CMD_JFFS2 bool "jffs2 command" select FS_JFFS2 diff --git a/cmd/Makefile b/cmd/Makefile index 28e9c261683..2c6a16752bd 100644 --- a/cmd/Makefile +++ b/cmd/Makefile @@ -90,6 +90,7 @@ obj-$(CONFIG_CMD_SQUASHFS) += sqfs.o obj-$(CONFIG_CMD_SELECT_FONT) += font.o obj-$(CONFIG_CMD_FLASH) += flash.o obj-$(CONFIG_CMD_FPGA) += fpga.o +obj-$(CONFIG_CMD_LUKS) += luks.o obj-$(CONFIG_CMD_FPGAD) += fpgad.o obj-$(CONFIG_CMD_FS_GENERIC) += fs.o obj-$(CONFIG_CMD_FUSE) += fuse.o diff --git a/cmd/luks.c b/cmd/luks.c new file mode 100644 index 00000000000..04ddcdb1d46 --- /dev/null +++ b/cmd/luks.c @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * LUKS (Linux Unified Key Setup) command + * + * Copyright (C) 2025 Canonical Ltd + */ + +#include <blk.h> +#include <command.h> +#include <dm.h> +#include <luks.h> +#include <part.h> + +static int do_luks_detect(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct blk_desc *dev_desc; + struct disk_partition info; + int part, ret, version; + + if (argc != 3) + return CMD_RET_USAGE; + + part = blk_get_device_part_str(argv[1], argv[2], &dev_desc, &info, 1); + if (part < 0) + return CMD_RET_FAILURE; + + ret = luks_detect(dev_desc->bdev, &info); + if (ret < 0) { + printf("Not a LUKS partition (error %dE)\n", ret); + return CMD_RET_FAILURE; + } + version = luks_get_version(dev_desc->bdev, &info); + printf("LUKS%d encrypted partition detected\n", version); + + return CMD_RET_SUCCESS; +} + +static int do_luks_info(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct blk_desc *dev_desc; + struct disk_partition info; + int part, ret; + + if (argc != 3) + return CMD_RET_USAGE; + + part = blk_get_device_part_str(argv[1], argv[2], &dev_desc, &info, 1); + if (part < 0) + return CMD_RET_FAILURE; + + ret = luks_show_info(dev_desc->bdev, &info); + if (ret < 0) + return CMD_RET_FAILURE; + + return CMD_RET_SUCCESS; +} + +static char luks_help_text[] = + "detect <interface> <dev[:part]> - detect if partition is LUKS encrypted\n" + "luks info <interface> <dev[:part]> - show LUKS header information"; + +U_BOOT_CMD_WITH_SUBCMDS(luks, "LUKS (Linux Unified Key Setup) operations", + luks_help_text, + U_BOOT_SUBCMD_MKENT(detect, 3, 1, do_luks_detect), + U_BOOT_SUBCMD_MKENT(info, 3, 1, do_luks_info)); diff --git a/doc/usage/cmd/luks.rst b/doc/usage/cmd/luks.rst new file mode 100644 index 00000000000..b88fcd96439 --- /dev/null +++ b/doc/usage/cmd/luks.rst @@ -0,0 +1,139 @@ +.. SPDX-License-Identifier: GPL-2.0+: + +.. index:: + single: luks (command) + +luks command +============ + +Synopsis +-------- + +:: + + luks detect <interface> <dev[:part]> + luks info <interface> <dev[:part]> + +Description +----------- + +The *luks* command provides an interface to detect and inspect LUKS +(Linux Unified Key Setup) encrypted partitions. LUKS is a disk encryption +specification used for full disk encryption on Linux systems. + +This command supports: + +* Detection of LUKS encrypted partitions (LUKS1 and LUKS2) +* Display of LUKS header information +* Access to decrypted data via blkmap devices + +The LUKS format uses a distinctive header containing: + +* Magic bytes: "LUKS" followed by 0xBA 0xBE +* Version information (16-bit big-endian) +* Encryption metadata (cipher, mode, hash specification) + +luks detect +~~~~~~~~~~~ + +Detect whether a specified partition is LUKS encrypted and report its version. + +interface + The storage interface type (e.g., mmc, usb, scsi) + +dev[:part] + The device number and optional partition number. If partition is omitted, + defaults to the whole device. + +luks info +~~~~~~~~~ + +Display detailed header information for a LUKS encrypted partition. This +subcommand reads the LUKS header and displays format-specific metadata. + +For LUKS1 partitions, the following information is displayed: + +* Version number +* Cipher name (encryption algorithm) +* Cipher mode (e.g., xts-plain64) +* Hash specification (e.g., sha256) +* Payload offset (in sectors) +* Key bytes (key size) + +For LUKS2 partitions, the following information is displayed: + +* Version number +* Header size (in bytes) +* Sequence ID (metadata update counter) +* UUID (partition identifier) +* Label (optional partition label) +* Checksum algorithm +* Full JSON metadata containing: + + - keyslots: Encryption key slot information including KDF parameters + - tokens: Optional token metadata + - segments: Encrypted data segment descriptions + - digests: Master key digest information + - config: Configuration parameters + +interface + The storage interface type (e.g., mmc, usb, scsi) + +dev[:part] + The device number and optional partition number. If partition is omitted, + defaults to the whole device. + +Examples +-------- + +Check if MMC device 0 partition 2 is LUKS encrypted:: + + => luks detect mmc 0:2 + LUKS2 encrypted partition detected + +Check a USB device partition:: + + => luks detect usb 0:1 + Not a LUKS partition (error -2: No such file or directory) + +Display LUKS header information for a LUKS2 partition:: + + => luks info mmc 0:2 + Version: 2 + Header size: 16384 bytes + Sequence ID: 3 + UUID: 7640da3a-d0a2-4238-9813-4714e7f62203 + Label: + Checksum alg: sha256 + +Display LUKS header information for a LUKS1 partition:: + + => luks info mmc 1:1 + Version: 1 + Cipher name: aes + Cipher mode: cbc-essiv:sha256 + Hash spec: sha256 + Payload offset: 4096 sectors + Key bytes: 32 + +Configuration +------------- + +The luks command is available when CONFIG_CMD_LUKS is enabled. + +For LUKS detection and info commands:: + + CONFIG_BLK_LUKS=y + CONFIG_CMD_LUKS=y + +Return value +------------ + +For *detect* and *info*: The return value $? is 0 (true) on success, 1 (false) +on failure. + +See also +-------- + +* cryptsetup project: https://gitlab.com/cryptsetup/cryptsetup +* LUKS on-disk format specifications: https://gitlab.com/cryptsetup/cryptsetup/-/wikis/home diff --git a/doc/usage/index.rst b/doc/usage/index.rst index 2fff101868c..2bb01211227 100644 --- a/doc/usage/index.rst +++ b/doc/usage/index.rst @@ -93,6 +93,7 @@ Shell commands cmd/loads cmd/loadx cmd/loady + cmd/luks cmd/meminfo cmd/mbr cmd/md diff --git a/drivers/block/luks.c b/drivers/block/luks.c index 189226cb0ab..597359b98ff 100644 --- a/drivers/block/luks.c +++ b/drivers/block/luks.c @@ -22,15 +22,13 @@ int luks_get_version(struct udevice *blk, struct disk_partition *pinfo) { - struct blk_desc *desc; + struct blk_desc *desc = dev_get_uclass_plat(blk); + ALLOC_CACHE_ALIGN_BUFFER(unsigned char, buffer, desc->blksz); int version; - desc = dev_get_uclass_plat(blk); - - ALLOC_CACHE_ALIGN_BUFFER(unsigned char, buffer, desc->blksz); /* Read first block of the partition */ - if (blk_dread(desc, pinfo->start, 1, buffer) != 1) { + if (blk_read(blk, pinfo->start, 1, buffer) != 1) { log_debug("Error: failed to read LUKS header\n"); return -EIO; } @@ -61,3 +59,54 @@ int luks_detect(struct udevice *blk, struct disk_partition *pinfo) return 0; } + +int luks_show_info(struct udevice *blk, struct disk_partition *pinfo) +{ + struct blk_desc *desc = dev_get_uclass_plat(blk); + ALLOC_CACHE_ALIGN_BUFFER(unsigned char, buffer, desc->blksz); + int version; + + /* Read first block of the partition */ + if (blk_read(blk, pinfo->start, 1, buffer) != 1) { + printf("Error: failed to read LUKS header\n"); + return -EIO; + } + + /* Check for LUKS magic bytes */ + if (memcmp(buffer, LUKS_MAGIC, LUKS_MAGIC_LEN)) { + printf("Not a LUKS partition\n"); + return -ENOENT; + } + + /* Read version field */ + version = be16_to_cpu(*(__be16 *)(buffer + LUKS_MAGIC_LEN)); + + printf("Version: %d\n", version); + if (version == LUKS_VERSION_1) { + struct luks1_phdr *luks1_hdr = (struct luks1_phdr *)buffer; + + printf("Cipher name: %.32s\n", luks1_hdr->cipher_name); + printf("Cipher mode: %.32s\n", luks1_hdr->cipher_mode); + printf("Hash spec: %.32s\n", luks1_hdr->hash_spec); + printf("Payload offset: %u sectors\n", + be32_to_cpu(luks1_hdr->payload_offset)); + printf("Key bytes: %u\n", + be32_to_cpu(luks1_hdr->key_bytes)); + } else if (version == LUKS_VERSION_2) { + struct luks2_hdr *luks2_hdr = (struct luks2_hdr *)buffer; + u64 hdr_size; + + hdr_size = be64_to_cpu(luks2_hdr->hdr_size); + + printf("Header size: %llu bytes\n", hdr_size); + printf("Sequence ID: %llu\n", be64_to_cpu(luks2_hdr->seqid)); + printf("UUID: %.40s\n", luks2_hdr->uuid); + printf("Label: %.48s\n", luks2_hdr->label); + printf("Checksum alg: %.32s\n", luks2_hdr->csum_alg); + } else { + printf("Unknown LUKS version\n"); + return -EPROTONOSUPPORT; + } + + return 0; +} diff --git a/test/boot/luks.c b/test/boot/luks.c index 684fd643c3a..fadd3819ffe 100644 --- a/test/boot/luks.c +++ b/test/boot/luks.c @@ -65,3 +65,45 @@ static int bootstd_test_luks_detect(struct unit_test_state *uts) return 0; } BOOTSTD_TEST(bootstd_test_luks_detect, UTF_DM | UTF_SCAN_FDT | UTF_CONSOLE); + +/* Test LUKS command on mmc11 partitions */ +static int bootstd_test_luks_cmd(struct unit_test_state *uts) +{ + struct udevice *mmc; + + ut_assertok(setup_mmc11(uts, &mmc)); + + /* Test partition 1 - should NOT be LUKS */ + ut_asserteq(1, run_command("luks detect mmc b:1", 0)); + ut_assert_nextlinen("Not a LUKS partition (error -"); + ut_assert_console_end(); + + /* Test partition 2 - should BE LUKS */ + ut_assertok(run_command("luks detect mmc b:2", 0)); + ut_assert_nextline("LUKS1 encrypted partition detected"); + ut_assert_console_end(); + + return 0; +} +BOOTSTD_TEST(bootstd_test_luks_cmd, UTF_DM | UTF_SCAN_FDT | UTF_CONSOLE); + +/* Test LUKS info command on mmc11 partition 2 */ +static int bootstd_test_luks_info(struct unit_test_state *uts) +{ + struct udevice *mmc; + + ut_assertok(setup_mmc11(uts, &mmc)); + + /* Test partition 2 LUKS info */ + ut_assertok(run_command("luks info mmc b:2", 0)); + ut_assert_nextline("Version: 1"); + ut_assert_nextlinen("Cipher name:"); + ut_assert_nextlinen("Cipher mode:"); + ut_assert_nextlinen("Hash spec:"); + ut_assert_nextlinen("Payload offset:"); + ut_assert_nextlinen("Key bytes:"); + ut_assert_console_end(); + + return 0; +} +BOOTSTD_TEST(bootstd_test_luks_info, UTF_DM | UTF_SCAN_FDT | UTF_CONSOLE); -- 2.43.0
From: Simon Glass <sjg@chromium.org> LUKS version 2 uses JSON as a means of communicating the key information. Add a simple library which can print JSON in a human-readable format. Note that it does not fully parse the JSON fragment. That may be considered later, if needed. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- MAINTAINERS | 3 + include/json.h | 23 +++++ lib/Kconfig | 7 ++ lib/Makefile | 1 + lib/json.c | 122 +++++++++++++++++++++++++++ test/lib/Makefile | 1 + test/lib/json.c | 211 ++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 368 insertions(+) create mode 100644 include/json.h create mode 100644 lib/json.c create mode 100644 test/lib/json.c diff --git a/MAINTAINERS b/MAINTAINERS index 9b00829db93..a0374df1c4d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1297,7 +1297,10 @@ F: cmd/luks.c F: doc/usage/cmd/luks.rst F: drivers/block/luks.c F: include/luks.h +F: include/json.h +F: lib/json.c F: test/boot/luks.c +F: test/lib/json.c MALI DISPLAY PROCESSORS M: Liviu Dudau <liviu.dudau@foss.arm.com> diff --git a/include/json.h b/include/json.h new file mode 100644 index 00000000000..4d925e9db36 --- /dev/null +++ b/include/json.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * JSON utilities + * + * Copyright (C) 2025 Canonical Ltd + * Written by Simon Glass <simon.glass@canonical.com> + */ + +#ifndef __JSON_H__ +#define __JSON_H__ + +/** + * json_print_pretty() - Print JSON with indentation + * + * This function takes a JSON string and prints it with proper indentation, + * making it more human-readable. It handles nested objects and arrays. + * + * @json: JSON string to print (may be nul terminated before @len) + * @len: Length of JSON string + */ +void json_print_pretty(const char *json, int len); + +#endif /* __JSON_H__ */ diff --git a/lib/Kconfig b/lib/Kconfig index 9de4667731e..c8bf4b4b049 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -976,6 +976,13 @@ config GETOPT help This enables functions for parsing command-line options. +config JSON + bool "Enable JSON parsing and printing" + help + This enables JSON (JavaScript Object Notation) parsing and pretty- + printing functions. JSON is used for structured data representation, + such as LUKS2 metadata. + config OF_LIBFDT bool "Enable the FDT library" default y if OF_CONTROL diff --git a/lib/Makefile b/lib/Makefile index 43df5733ffd..71c9c0d1766 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -39,6 +39,7 @@ endif obj-y += crc8.o obj-$(CONFIG_ERRNO_STR) += errno_str.o +obj-$(CONFIG_JSON) += json.o obj-$(CONFIG_FIT) += fdtdec_common.o obj-$(CONFIG_TEST_FDTDEC) += fdtdec_test.o obj-$(CONFIG_GZIP_COMPRESSED) += gzip.o diff --git a/lib/json.c b/lib/json.c new file mode 100644 index 00000000000..8cce042d631 --- /dev/null +++ b/lib/json.c @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * JSON pretty-printer + * + * Copyright (C) 2025 Canonical Ltd + * Written by Simon Glass <simon.glass@canonical.com> + */ + +#include <ctype.h> +#include <log.h> + +/** + * print_indent() - Print indentation spaces + * + * @indent: Indentation level (each level is 2 spaces) + */ +static void print_indent(int indent) +{ + for (int i = 0; i < indent * 2; i++) + putc(' '); +} + +void json_print_pretty(const char *json, int len) +{ + int indent = 0; + bool in_string = false; + bool escaped = false; + bool after_open = false; + int i; + + for (i = 0; i < len && json[i]; i++) { + char c = json[i]; + + /* Handle escape sequences */ + if (escaped) { + putc(c); + escaped = false; + continue; + } + + if (c == '\\') { + putc(c); + escaped = true; + continue; + } + + /* Track whether we're inside a string */ + if (c == '"') { + in_string = !in_string; + if (after_open) { + print_indent(indent); + after_open = false; + } + putc(c); + continue; + } + + /* Don't format inside strings */ + if (in_string) { + putc(c); + continue; + } + + /* Format structural characters */ + switch (c) { + case '{': + case '[': + if (after_open) { + print_indent(indent); + after_open = false; + } + putc(c); + putc('\n'); + indent++; + after_open = true; + break; + + case '}': + case ']': + if (!after_open) { + putc('\n'); + indent--; + print_indent(indent); + } else { + indent--; + } + putc(c); + after_open = false; + break; + + case ',': + putc(c); + putc('\n'); + print_indent(indent); + after_open = false; + break; + + case ':': + putc(c); + putc(' '); + after_open = false; + break; + + case ' ': + case '\t': + case '\n': + case '\r': + /* Skip whitespace outside strings */ + break; + + default: + if (after_open) { + print_indent(indent); + after_open = false; + } + putc(c); + break; + } + } + + putc('\n'); +} diff --git a/test/lib/Makefile b/test/lib/Makefile index 5c89421918b..1d94d6604d5 100644 --- a/test/lib/Makefile +++ b/test/lib/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_EFI_LOADER) += efi_device_path.o obj-$(CONFIG_EFI_SECURE_BOOT) += efi_image_region.o obj-$(CONFIG_EFI_LOG) += efi_log.o obj-y += hexdump.o +obj-$(CONFIG_JSON) += json.o obj-$(CONFIG_SANDBOX) += kconfig.o obj-y += lmb.o obj-$(CONFIG_HAVE_SETJMP) += longjmp.o diff --git a/test/lib/json.c b/test/lib/json.c new file mode 100644 index 00000000000..76afeb9b241 --- /dev/null +++ b/test/lib/json.c @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Tests for JSON pretty-printer + * + * Copyright (C) 2025 Canonical Ltd + * Written by Simon Glass <simon.glass@canonical.com> + */ + +#include <json.h> +#include <test/lib.h> +#include <test/test.h> +#include <test/ut.h> + +static int lib_test_json_simple_object(struct unit_test_state *uts) +{ + const char *json = "{\"name\":\"value\"}"; + + json_print_pretty(json, strlen(json)); + ut_assert_nextline("{"); + ut_assert_nextline(" \"name\": \"value\""); + ut_assert_nextline("}"); + ut_assert_console_end(); + + return 0; +} +LIB_TEST(lib_test_json_simple_object, UTF_CONSOLE); + +static int lib_test_json_simple_array(struct unit_test_state *uts) +{ + const char *json = "[1,2,3]"; + + json_print_pretty(json, strlen(json)); + ut_assert_nextline("["); + ut_assert_nextline(" 1,"); + ut_assert_nextline(" 2,"); + ut_assert_nextline(" 3"); + ut_assert_nextline("]"); + ut_assert_console_end(); + + return 0; +} +LIB_TEST(lib_test_json_simple_array, UTF_CONSOLE); + +static int lib_test_json_nested_object(struct unit_test_state *uts) +{ + const char *json = "{\"outer\":{\"inner\":\"value\"}}"; + + json_print_pretty(json, strlen(json)); + ut_assert_nextline("{"); + ut_assert_nextline(" \"outer\": {"); + ut_assert_nextline(" \"inner\": \"value\""); + ut_assert_nextline(" }"); + ut_assert_nextline("}"); + ut_assert_console_end(); + + return 0; +} +LIB_TEST(lib_test_json_nested_object, UTF_CONSOLE); + +static int lib_test_json_nested_array(struct unit_test_state *uts) +{ + const char *json = "[[1,2],[3,4]]"; + + json_print_pretty(json, strlen(json)); + ut_assert_nextline("["); + ut_assert_nextline(" ["); + ut_assert_nextline(" 1,"); + ut_assert_nextline(" 2"); + ut_assert_nextline(" ],"); + ut_assert_nextline(" ["); + ut_assert_nextline(" 3,"); + ut_assert_nextline(" 4"); + ut_assert_nextline(" ]"); + ut_assert_nextline("]"); + ut_assert_console_end(); + + return 0; +} +LIB_TEST(lib_test_json_nested_array, UTF_CONSOLE); + +static int lib_test_json_mixed_nested(struct unit_test_state *uts) +{ + const char *json = "{\"array\":[1,{\"nested\":\"obj\"}]}"; + + json_print_pretty(json, strlen(json)); + ut_assert_nextline("{"); + ut_assert_nextline(" \"array\": ["); + ut_assert_nextline(" 1,"); + ut_assert_nextline(" {"); + ut_assert_nextline(" \"nested\": \"obj\""); + ut_assert_nextline(" }"); + ut_assert_nextline(" ]"); + ut_assert_nextline("}"); + ut_assert_console_end(); + + return 0; +} +LIB_TEST(lib_test_json_mixed_nested, UTF_CONSOLE); + +static int lib_test_json_string_with_colon(struct unit_test_state *uts) +{ + const char *json = "{\"url\":\"http://example.com\"}"; + + json_print_pretty(json, strlen(json)); + ut_assert_nextline("{"); + ut_assert_nextline(" \"url\": \"http://example.com\""); + ut_assert_nextline("}"); + ut_assert_console_end(); + + return 0; +} +LIB_TEST(lib_test_json_string_with_colon, UTF_CONSOLE); + +static int lib_test_json_string_with_comma(struct unit_test_state *uts) +{ + const char *json = "{\"name\":\"last, first\"}"; + + json_print_pretty(json, strlen(json)); + ut_assert_nextline("{"); + ut_assert_nextline(" \"name\": \"last, first\""); + ut_assert_nextline("}"); + ut_assert_console_end(); + + return 0; +} +LIB_TEST(lib_test_json_string_with_comma, UTF_CONSOLE); + +static int lib_test_json_string_with_braces(struct unit_test_state *uts) +{ + const char *json = "{\"text\":\"some {braces} here\"}"; + + json_print_pretty(json, strlen(json)); + ut_assert_nextline("{"); + ut_assert_nextline(" \"text\": \"some {braces} here\""); + ut_assert_nextline("}"); + ut_assert_console_end(); + + return 0; +} +LIB_TEST(lib_test_json_string_with_braces, UTF_CONSOLE); + +static int lib_test_json_escaped_quote(struct unit_test_state *uts) +{ + const char *json = "{\"quote\":\"He said \\\"hello\\\"\"}"; + + json_print_pretty(json, strlen(json)); + ut_assert_nextline("{"); + ut_assert_nextline(" \"quote\": \"He said \\\"hello\\\"\""); + ut_assert_nextline("}"); + ut_assert_console_end(); + + return 0; +} +LIB_TEST(lib_test_json_escaped_quote, UTF_CONSOLE); + +static int lib_test_json_multiple_fields(struct unit_test_state *uts) +{ + const char *json = "{\"name\":\"test\",\"age\":25,\"active\":true}"; + + json_print_pretty(json, strlen(json)); + ut_assert_nextline("{"); + ut_assert_nextline(" \"name\": \"test\","); + ut_assert_nextline(" \"age\": 25,"); + ut_assert_nextline(" \"active\": true"); + ut_assert_nextline("}"); + ut_assert_console_end(); + + return 0; +} +LIB_TEST(lib_test_json_multiple_fields, UTF_CONSOLE); + +static int lib_test_json_empty_object(struct unit_test_state *uts) +{ + const char *json = "{}"; + + json_print_pretty(json, strlen(json)); + ut_assert_nextline("{"); + ut_assert_nextline("}"); + ut_assert_console_end(); + + return 0; +} +LIB_TEST(lib_test_json_empty_object, UTF_CONSOLE); + +static int lib_test_json_empty_array(struct unit_test_state *uts) +{ + const char *json = "[]"; + + json_print_pretty(json, strlen(json)); + ut_assert_nextline("["); + ut_assert_nextline("]"); + ut_assert_console_end(); + + return 0; +} +LIB_TEST(lib_test_json_empty_array, UTF_CONSOLE); + +static int lib_test_json_whitespace(struct unit_test_state *uts) +{ + const char *json = "{ \"name\" : \"value\" , \"num\" : 42 }"; + + json_print_pretty(json, strlen(json)); + ut_assert_nextline("{"); + ut_assert_nextline(" \"name\": \"value\","); + ut_assert_nextline(" \"num\": 42"); + ut_assert_nextline("}"); + ut_assert_console_end(); + + return 0; +} +LIB_TEST(lib_test_json_whitespace, UTF_CONSOLE); -- 2.43.0
From: Simon Glass <sjg@chromium.org> Add a new mmc12 image which has a LUKS2-encrypted ext4 partition. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- arch/sandbox/dts/test.dts | 8 ++++++ test/py/img/common.py | 7 ++--- test/py/img/ubuntu.py | 4 +-- test/py/tests/fs_helper.py | 53 +++++++++++++++++++++++++++----------- test/py/tests/test_ut.py | 3 ++- 5 files changed, 54 insertions(+), 21 deletions(-) diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts index 9b0a5736cf8..1d0ebc33387 100644 --- a/arch/sandbox/dts/test.dts +++ b/arch/sandbox/dts/test.dts @@ -49,6 +49,7 @@ mmc9 = "/mmc9"; mmc10 = "/mmc10"; mmc11 = "/mmc11"; + mmc12 = "/mmc12"; pci0 = &pci0; pci1 = &pci1; pci2 = &pci2; @@ -1207,6 +1208,13 @@ filename = "mmc11.img"; }; + /* This is used for LUKS version 2 tests */ + mmc12 { + status = "disabled"; + compatible = "sandbox,mmc"; + filename = "mmc12.img"; + }; + pch { compatible = "sandbox,pch"; }; diff --git a/test/py/img/common.py b/test/py/img/common.py index 01745ce73b3..3b3fdb2734b 100644 --- a/test/py/img/common.py +++ b/test/py/img/common.py @@ -33,7 +33,7 @@ def copy_partition(ubman, fsfile, outname): def setup_extlinux_image(config, log, devnum, basename, vmlinux, initrd, dtbdir, - script, part2_size=1, use_fde=False): + script, part2_size=1, use_fde=0): """Create a 20MB disk image with a single FAT partition Args: @@ -46,7 +46,7 @@ def setup_extlinux_image(config, log, devnum, basename, vmlinux, initrd, dtbdir, dtbdir (str or None): Devicetree filename script (str): Script to place in the extlinux.conf file part2_size (int): Size of second partition in MB (default: 1) - use_fde (bool): True to encrypt the ext4 partition with LUKS1 + use_fde (int): LUKS version for full-disk encryption (0=none, 1=LUKS1, 2=LUKS2) """ fsh = FsHelper(config, 'vfat', 18, prefix=basename) fsh.setup() @@ -82,7 +82,8 @@ 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, - encrypt_passphrase='test' if use_fde else None) + encrypt_passphrase='test' if use_fde else None, + luks_version=use_fde if use_fde else 2) ext4.setup() bindir = os.path.join(ext4.srcdir, 'bin') diff --git a/test/py/img/ubuntu.py b/test/py/img/ubuntu.py index 58ee9f20277..b783f7eb3cf 100644 --- a/test/py/img/ubuntu.py +++ b/test/py/img/ubuntu.py @@ -7,7 +7,7 @@ from img.common import setup_extlinux_image def setup_ubuntu_image(config, log, devnum, basename, version='24.04.1 LTS', - use_fde=False): + use_fde=0): """Create a Ubuntu disk image with a FAT partition and ext4 partition This creates a FAT partition containing extlinux files, kernel, etc. and a @@ -18,7 +18,7 @@ def setup_ubuntu_image(config, log, devnum, basename, version='24.04.1 LTS', log (multiplexed_log.Logfile): Log to write to devnum (int): Device number to use, e.g. 1 basename (str): Base name to use in the filename, e.g. 'mmc' - use_fde (bool): True to set up full-disk encryption + use_fde (int): LUKS version for full-disk encryption (0=none, 1=LUKS1, 2=LUKS2) """ vmlinux = 'vmlinuz-6.8.0-53-generic' initrd = 'initrd.img-6.8.0-53-generic' diff --git a/test/py/tests/fs_helper.py b/test/py/tests/fs_helper.py index 49747be1788..914de09e381 100644 --- a/test/py/tests/fs_helper.py +++ b/test/py/tests/fs_helper.py @@ -38,12 +38,20 @@ class FsHelper: fsh.mk_fs() ... - To create an encrypted LUKS1 partition: + To create an encrypted LUKS2 partition (default): with FsHelper(ubman.config, 'ext4', 10, 'mmc1', encrypt_passphrase='test') as fsh: # create files in the fsh.srcdir directory - fsh.mk_fs() # Creates and encrypts the filesystem + fsh.mk_fs() # Creates and encrypts the filesystem with LUKS2 + ... + + To create an encrypted LUKS1 partition: + + with FsHelper(ubman.config, 'ext4', 10, 'mmc1', + encrypt_passphrase='test', luks_version=1) as fsh: + # create files in the fsh.srcdir directory + fsh.mk_fs() # Creates and encrypts the filesystem with LUKS1 ... Properties: @@ -51,7 +59,7 @@ class FsHelper: default value but can be overwritten """ def __init__(self, config, fs_type, size_mb, prefix, part_mb=None, - encrypt_passphrase=None): + encrypt_passphrase=None, luks_version=2): """Set up a new object Args: @@ -64,7 +72,8 @@ class FsHelper: to size_mb. This can be used to make the partition larger than the filesystem, to create space for disk-encryption metadata encrypt_passphrase (str, optional): If provided, encrypt the - filesystem with LUKS1 using this passphrase + filesystem with LUKS using this passphrase + luks_version (int): LUKS version to use (1 or 2). Defaults to 2. """ if ('fat' not in fs_type and 'ext' not in fs_type and fs_type not in ['exfat', 'fs_generic']): @@ -77,6 +86,7 @@ class FsHelper: self.prefix = prefix self.quiet = True self.encrypt_passphrase = encrypt_passphrase + self.luks_version = luks_version # Use a default filename; the caller can adjust it leaf = f'{prefix}.{fs_type}.img' @@ -166,11 +176,10 @@ class FsHelper: self.srcdir = self.tmpdir.name def encrypt_luks(self, passphrase): - """Encrypt the filesystem image with LUKS1 + """Encrypt the filesystem image with LUKS - This replaces the filesystem image with a LUKS1-encrypted version. - LUKS1 is used because U-Boot's unlock implementation currently only - supports LUKS version 1. + 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 @@ -180,11 +189,25 @@ class FsHelper: Raises: CalledProcessError: If cryptsetup is not available or fails + ValueError: If an unsupported LUKS version is specified """ - # LUKS1 encryption parameters - cipher = 'aes-cbc-essiv:sha256' - key_size = 256 - hash_alg = 'sha256' + # LUKS encryption parameters + if self.luks_version == 1: + # LUKS1 parameters + cipher = 'aes-cbc-essiv:sha256' + key_size = 256 + hash_alg = 'sha256' + luks_type = 'luks1' + elif self.luks_version == 2: + # LUKS2 parameters (modern defaults) + cipher = 'aes-xts-plain64' + key_size = 512 # XTS uses 512-bit keys (2x256) + hash_alg = 'sha256' + luks_type = 'luks2' + else: + raise ValueError(f"Unsupported LUKS version: {self.luks_version}") + + key_size_str = str(key_size) # Save the original filesystem image orig_fs_img = f'{self.fs_img}.orig' @@ -214,11 +237,11 @@ class FsHelper: stdout=DEVNULL, stderr=DEVNULL, check=False) try: - # Format as LUKS1 + # Format as LUKS (version determined by luks_type) run(['cryptsetup', 'luksFormat', - '--type', 'luks1', + '--type', luks_type, '--cipher', cipher, - '--key-size', str(key_size), + '--key-size', key_size_str, '--hash', hash_alg, '--iter-time', '10', # Very fast for testing (low security) luks_img], diff --git a/test/py/tests/test_ut.py b/test/py/tests/test_ut.py index 218f5a74ac1..94d98b3b73b 100644 --- a/test/py/tests/test_ut.py +++ b/test/py/tests/test_ut.py @@ -83,7 +83,8 @@ 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) - setup_ubuntu_image(u_boot_config, u_boot_log, 11, 'mmc', use_fde=True) + 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) def test_ut(ubman, ut_subtest): """Execute a "ut" subtest. -- 2.43.0
From: Simon Glass <sjg@chromium.org> Extract the full information for version 2, which is JSON format. Show this with the 'luks info' command. Use the mmc12 disk to check this. Require the JSON for LUKS. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- doc/usage/cmd/luks.rst | 29 ++++++++++ drivers/block/Kconfig | 1 + drivers/block/luks.c | 23 ++++++++ test/boot/luks.c | 118 ++++++++++++++++++++++++++++++++++++++--- 4 files changed, 165 insertions(+), 6 deletions(-) diff --git a/doc/usage/cmd/luks.rst b/doc/usage/cmd/luks.rst index b88fcd96439..c3b03eeff2e 100644 --- a/doc/usage/cmd/luks.rst +++ b/doc/usage/cmd/luks.rst @@ -106,6 +106,35 @@ Display LUKS header information for a LUKS2 partition:: Label: Checksum alg: sha256 + JSON metadata (12288 bytes): + { + "keyslots": { + "0": { + "type": "luks2", + "key_size": 64, + "kdf": { + "type": "argon2id", + "time": 6, + "memory": 1048576, + "cpus": 4, + ... + }, + ... + } + }, + "tokens": {}, + "segments": { + "0": { + "type": "crypt", + "offset": "16777216", + "encryption": "aes-xts-plain64", + ... + } + }, + "digests": { ... }, + "config": { ... } + } + Display LUKS header information for a LUKS1 partition:: => luks info mmc 1:1 diff --git a/drivers/block/Kconfig b/drivers/block/Kconfig index b07012ec7c9..34cd2ee8e59 100644 --- a/drivers/block/Kconfig +++ b/drivers/block/Kconfig @@ -278,6 +278,7 @@ config BLK_LUKS select SHA256 select PBKDF2 select PKCS5_MBEDTLS if MBEDTLS_LIB_CRYPTO + select JSON help This provides support for detecting and decrypting LUKS (Linux Unified Key Setup) encrypted partitions. LUKS is a disk encryption specification diff --git a/drivers/block/luks.c b/drivers/block/luks.c index 597359b98ff..c43cb9a3dd3 100644 --- a/drivers/block/luks.c +++ b/drivers/block/luks.c @@ -8,6 +8,7 @@ #include <blk.h> #include <dm.h> #include <hexdump.h> +#include <json.h> #include <log.h> #include <luks.h> #include <memalign.h> @@ -103,6 +104,28 @@ int luks_show_info(struct udevice *blk, struct disk_partition *pinfo) printf("UUID: %.40s\n", luks2_hdr->uuid); printf("Label: %.48s\n", luks2_hdr->label); printf("Checksum alg: %.32s\n", luks2_hdr->csum_alg); + + if (IS_ENABLED(CONFIG_JSON)) { + u64 json_size; + char *json_start; + int blocks; + + /* Read the full header to get JSON area */ + blocks = (hdr_size + desc->blksz - 1) / desc->blksz; + ALLOC_CACHE_ALIGN_BUFFER(unsigned char, full_hdr, blocks * desc->blksz); + + if (blk_read(blk, pinfo->start, blocks, full_hdr) != blocks) { + printf("Error: failed to read full LUKS2 header\n"); + return -EIO; + } + + /* JSON starts after the 4096-byte binary header */ + json_start = (char *)(full_hdr + 4096); + json_size = hdr_size - 4096; + + printf("\nJSON metadata (%llx bytes):\n", json_size); + json_print_pretty(json_start, (int)json_size); + } } else { printf("Unknown LUKS version\n"); return -EPROTONOSUPPORT; diff --git a/test/boot/luks.c b/test/boot/luks.c index fadd3819ffe..70ee0fb0824 100644 --- a/test/boot/luks.c +++ b/test/boot/luks.c @@ -20,19 +20,49 @@ DECLARE_GLOBAL_DATA_PTR; -/* Common function to setup mmc11 device */ -static int setup_mmc11(struct unit_test_state *uts, struct udevice **mmcp) +/** + * setup_mmc_device() - Set up an MMC device for testing + * + * This function binds and probes an MMC device specified by its device tree + * node name. It is used to prepare MMC devices containing test disk images + * with various configurations (e.g., LUKS1, LUKS2 encryption). + * + * @uts: Unit test state + * @node_name: Name of the device tree node (e.g., "mmc11", "mmc12") + * @mmcp: Returns pointer to the MMC device + * Return: 0 if OK, -ve on error + */ +static int setup_mmc_device(struct unit_test_state *uts, const char *node_name, + struct udevice **mmcp) { + struct udevice *mmc; ofnode root, node; - /* Enable the mmc11 node */ + /* Enable the specified mmc node */ root = oftree_root(oftree_default()); - node = ofnode_find_subnode(root, "mmc11"); + node = ofnode_find_subnode(root, node_name); ut_assert(ofnode_valid(node)); - ut_assertok(lists_bind_fdt(gd->dm_root, node, mmcp, NULL, false)); + ut_assertok(lists_bind_fdt(gd->dm_root, node, &mmc, NULL, false)); /* Probe the device */ - ut_assertok(device_probe(*mmcp)); + ut_assertok(device_probe(mmc)); + *mmcp = mmc; + + return 0; +} + +/* Setup mmc11 device */ +static int setup_mmc11(struct unit_test_state *uts, struct udevice **mmcp) +{ + ut_assertok(setup_mmc_device(uts, "mmc11", mmcp)); + + return 0; +} + +/* Setup mmc12 device */ +static int setup_mmc12(struct unit_test_state *uts, struct udevice **mmcp) +{ + ut_assertok(setup_mmc_device(uts, "mmc12", mmcp)); return 0; } @@ -107,3 +137,79 @@ static int bootstd_test_luks_info(struct unit_test_state *uts) return 0; } BOOTSTD_TEST(bootstd_test_luks_info, UTF_DM | UTF_SCAN_FDT | UTF_CONSOLE); + +/* Test LUKSv2 detection on mmc12 partitions */ +static int bootstd_test_luks2_detect(struct unit_test_state *uts) +{ + struct disk_partition info; + struct blk_desc *desc; + struct udevice *mmc; + int ret; + + ut_assertok(setup_mmc12(uts, &mmc)); + desc = blk_get_by_device(mmc); + ut_assertnonnull(desc); + ut_assertnonnull(desc->bdev); + + /* Check partition 1 - should NOT be LUKS */ + ut_assertok(part_get_info(desc, 1, &info)); + ret = luks_detect(desc->bdev, &info); + ut_assert(ret < 0); /* Should fail - not LUKS */ + + /* Check partition 2 - should BE LUKS */ + ut_assertok(part_get_info(desc, 2, &info)); + ut_assertok(luks_detect(desc->bdev, &info)); + + /* Verify it's LUKS version 2 */ + ut_asserteq(2, luks_get_version(desc->bdev, &info)); + + return 0; +} +BOOTSTD_TEST(bootstd_test_luks2_detect, UTF_DM | UTF_SCAN_FDT | UTF_CONSOLE); + +/* Test LUKSv2 command on mmc12 partitions */ +static int bootstd_test_luks2_cmd(struct unit_test_state *uts) +{ + struct udevice *mmc; + + ut_assertok(setup_mmc12(uts, &mmc)); + + /* Test partition 1 - should NOT be LUKS */ + ut_asserteq(1, run_command("luks detect mmc c:1", 0)); + ut_assert_nextlinen("Not a LUKS partition (error -"); + ut_assert_console_end(); + + /* Test partition 2 - should BE LUKS */ + ut_assertok(run_command("luks detect mmc c:2", 0)); + ut_assert_nextline("LUKS2 encrypted partition detected"); + ut_assert_console_end(); + + return 0; +} +BOOTSTD_TEST(bootstd_test_luks2_cmd, UTF_DM | UTF_SCAN_FDT | UTF_CONSOLE); + +/* Test LUKSv2 info command on mmc12 partition 2 */ +static int bootstd_test_luks2_info(struct unit_test_state *uts) +{ + struct udevice *mmc; + + ut_assertok(setup_mmc12(uts, &mmc)); + + /* Test partition 2 LUKS info */ + ut_assertok(run_command("luks info mmc c:2", 0)); + ut_assert_nextline("Version: 2"); + ut_assert_nextlinen("Header size:"); + ut_assert_nextlinen("Sequence ID:"); + ut_assert_nextlinen("UUID:"); + ut_assert_nextlinen("Label:"); + ut_assert_nextlinen("Checksum alg:"); + + /* Verify JSON metadata section is present (skip empty line first) */ + ut_assert_skip_to_line(""); + ut_assert_nextlinen("JSON metadata ("); + ut_assert_nextline("{"); + /* JSON output varies and there is little value in checking it here */ + + return 0; +} +BOOTSTD_TEST(bootstd_test_luks2_info, UTF_DM | UTF_SCAN_FDT | UTF_CONSOLE); -- 2.43.0
From: Simon Glass <sjg@chromium.org> Enhance blkmap to support decrypting a partition encrypted with LUKS version 1. This will allow filesystems to access files on the parition. This will be tested once filesystems support is plumbed in. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- drivers/block/blkmap.c | 152 +++++++++++++++++++++++++++++++++++++++++ include/blkmap.h | 24 +++++++ 2 files changed, 176 insertions(+) diff --git a/drivers/block/blkmap.c b/drivers/block/blkmap.c index 34eed1380dc..13a79001979 100644 --- a/drivers/block/blkmap.c +++ b/drivers/block/blkmap.c @@ -9,10 +9,13 @@ #include <dm.h> #include <malloc.h> #include <mapmem.h> +#include <memalign.h> #include <part.h> +#include <uboot_aes.h> #include <dm/device-internal.h> #include <dm/lists.h> #include <dm/root.h> +#include <linux/string.h> struct blkmap; @@ -290,6 +293,155 @@ int blkmap_map_pmem(struct udevice *dev, lbaint_t blknr, lbaint_t blkcnt, return err; } +/** + * struct blkmap_crypt - Encrypted device mapping + * + * @slice: Common map data + * @blk: Target encrypted block device + * @blknr: Start block number of encrypted data on target device + * @master_key: Decrypted master key for this mapping + * @key_size: Size of master key in bytes + * @payload_offset: LUKS payload offset in sectors + * @use_essiv: True if ESSIV mode is used for IV generation + * @essiv_key: ESSIV key (SHA256 hash of master key) + */ +struct blkmap_crypt { + struct blkmap_slice slice; + struct udevice *blk; + lbaint_t blknr; + u8 master_key[128]; + u32 key_size; + u32 payload_offset; + bool use_essiv; + u8 essiv_key[32]; +}; + +static ulong blkmap_crypt_read(struct blkmap *bm, struct blkmap_slice *bms, + lbaint_t blknr, lbaint_t blkcnt, void *buffer) +{ + struct blkmap_crypt *bmc = container_of(bms, struct blkmap_crypt, slice); + struct blk_desc *bd = dev_get_uclass_plat(bm->blk); + struct blk_desc *src_bd = dev_get_uclass_plat(bmc->blk); + lbaint_t src_blknr, blocks_read; + u8 *encrypted_buf, *dest = buffer; + u8 expkey[AES256_EXPAND_KEY_LENGTH]; + u8 iv[AES_BLOCK_LENGTH]; + u64 sector; + lbaint_t i; + + /* Allocate buffer for encrypted data */ + encrypted_buf = malloc_cache_aligned(blkcnt * src_bd->blksz); + if (!encrypted_buf) + return 0; + + /* + * Calculate source block number (LUKS payload offset + requested + * block) + */ + src_blknr = bmc->blknr + bmc->payload_offset + blknr; + + /* Read encrypted data from underlying device */ + blocks_read = blk_read(bmc->blk, src_blknr, blkcnt, encrypted_buf); + if (blocks_read != blkcnt) { + free(encrypted_buf); + return 0; + } + + /* Expand AES key */ + aes_expand_key(bmc->master_key, bmc->key_size * 8, expkey); + + /* Decrypt each sector */ + for (i = 0; i < blkcnt; i++) { + /* Calculate sector number for IV */ + sector = blknr + i; + + if (bmc->use_essiv) { + /* + * ESSIV mode: + * IV = AES_encrypt(sector_number, SHA256(master_key)) + */ + u8 essiv_expkey[AES256_EXPAND_KEY_LENGTH]; + u8 sector_iv[AES_BLOCK_LENGTH]; + + /* Create sector number as IV input (little-endian) */ + memset(sector_iv, 0, sizeof(sector_iv)); + *(u64 *)sector_iv = cpu_to_le64(sector); + + /* Expand ESSIV key */ + aes_expand_key(bmc->essiv_key, 256, essiv_expkey); + + /* Encrypt sector number with ESSIV key to get IV */ + aes_encrypt(256, sector_iv, essiv_expkey, iv); + } else { + /* + * Plain64 mode: + * IV is sector number in little-endian format + */ + memset(iv, '\0', sizeof(iv)); + *(u64 *)iv = cpu_to_le64(sector); + } + + /* Decrypt sector using AES-CBC */ + aes_cbc_decrypt_blocks(bmc->key_size * 8, expkey, iv, + encrypted_buf + i * bd->blksz, + dest + i * bd->blksz, + bd->blksz / AES_BLOCK_LENGTH); + } + free(encrypted_buf); + + return blkcnt; +} + +static void blkmap_crypt_destroy(struct blkmap *bm, struct blkmap_slice *bms) +{ + struct blkmap_crypt *bmc = container_of(bms, struct blkmap_crypt, slice); + + /* Securely wipe master key before freeing */ + memset(bmc->master_key, 0, sizeof(bmc->master_key)); + free(bmc); +} + +int blkmap_map_crypt(struct udevice *dev, lbaint_t blknr, lbaint_t blkcnt, + struct udevice *lblk, lbaint_t lblknr, + const u8 *master_key, u32 key_size, u32 payload_offset, + bool use_essiv, const u8 *essiv_key) +{ + struct blkmap *bm = dev_get_plat(dev); + struct blkmap_crypt *bmc; + int err; + + if (key_size > 128) + return -EINVAL; + + bmc = malloc(sizeof(*bmc)); + if (!bmc) + return -ENOMEM; + + bmc->blk = lblk; + bmc->blknr = lblknr; + bmc->key_size = key_size; + bmc->payload_offset = payload_offset; + bmc->use_essiv = use_essiv; + memcpy(bmc->master_key, master_key, key_size); + + if (use_essiv && essiv_key) + memcpy(bmc->essiv_key, essiv_key, sizeof(bmc->essiv_key)); + else + memset(bmc->essiv_key, 0, sizeof(bmc->essiv_key)); + + bmc->slice.blknr = blknr; + bmc->slice.blkcnt = blkcnt; + bmc->slice.read = blkmap_crypt_read; + bmc->slice.write = NULL; /* Read-only for now */ + bmc->slice.destroy = blkmap_crypt_destroy; + + err = blkmap_slice_add(bm, &bmc->slice); + if (err) + free(bmc); + + return err; +} + static ulong blkmap_blk_read_slice(struct blkmap *bm, struct blkmap_slice *bms, lbaint_t blknr, lbaint_t blkcnt, void *buffer) diff --git a/include/blkmap.h b/include/blkmap.h index d53095437fa..a0f46748b92 100644 --- a/include/blkmap.h +++ b/include/blkmap.h @@ -65,6 +65,30 @@ int blkmap_map_mem(struct udevice *dev, lbaint_t blknr, lbaint_t blkcnt, int blkmap_map_pmem(struct udevice *dev, lbaint_t blknr, lbaint_t blkcnt, phys_addr_t paddr); +/** + * blkmap_map_crypt() - Map region of encrypted device + * + * Creates a mapping that performs on-the-fly decryption of the specified + * region from another block device. Suitable for LUKS and other encrypted + * block devices. + * + * @dev: Blkmap to create the mapping on + * @blknr: Start block number of the mapping + * @blkcnt: Number of blocks to map + * @lblk: The target block device containing encrypted data + * @lblknr: The start block number of the encrypted partition + * @master_key: Decrypted master key for decryption + * @key_size: Size of the master key in bytes (must be <= 128) + * @payload_offset: Offset in sectors from lblknr to actual encrypted payload + * @use_essiv: True to use ESSIV mode, false for plain64 mode + * @essiv_key: ESSIV key (SHA256 of master key), or NULL if use_essiv is false + * Returns: 0 on success, negative error code on failure + */ +int blkmap_map_crypt(struct udevice *dev, lbaint_t blknr, lbaint_t blkcnt, + struct udevice *lblk, lbaint_t lblknr, + const u8 *master_key, u32 key_size, u32 payload_offset, + bool use_essiv, const u8 *essiv_key); + /** * blkmap_from_label() - Find blkmap from label * -- 2.43.0
From: Simon Glass <sjg@chromium.org> Add the logic to unlock a partition and set up a blkmap for use with it. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- drivers/block/luks.c | 521 +++++++++++++++++++++++++++++++++++++++++++ include/luks.h | 35 +++ 2 files changed, 556 insertions(+) diff --git a/drivers/block/luks.c b/drivers/block/luks.c index c43cb9a3dd3..4400f1cfd84 100644 --- a/drivers/block/luks.c +++ b/drivers/block/luks.c @@ -6,7 +6,9 @@ */ #include <blk.h> +#include <blkmap.h> #include <dm.h> +#include <hash.h> #include <hexdump.h> #include <json.h> #include <log.h> @@ -14,12 +16,15 @@ #include <memalign.h> #include <part.h> #include <uboot_aes.h> +#include <asm/unaligned.h> #include <linux/byteorder/generic.h> #include <linux/err.h> #include <linux/errno.h> #include <linux/string.h> #include <mbedtls/md.h> #include <mbedtls/pkcs5.h> +#include <u-boot/sha256.h> +#include <u-boot/sha512.h> int luks_get_version(struct udevice *blk, struct disk_partition *pinfo) { @@ -133,3 +138,519 @@ int luks_show_info(struct udevice *blk, struct disk_partition *pinfo) return 0; } + +/** + * af_hash() - Apply anti-forensic diffusion by hashing each block + * + * This applies the LUKS AF-hash diffusion function to a buffer. Each + * digest-sized chunk is replaced with H(counter || chunk), where H is + * the specified hash function. + * + * @algo: Hash algorithm to use + * @key_size: Size of the buffer to diffuse + * @block_buf: Buffer to diffuse in-place + * Return: 0 on success, -ve on error + */ +static int af_hash(struct hash_algo *algo, size_t key_size, u8 *block_buf) +{ + uint hashcount, finallen, i, digest_size = algo->digest_size; + u8 input_buf[sizeof(u32) + HASH_MAX_DIGEST_SIZE]; + u8 hash_buf[HASH_MAX_DIGEST_SIZE]; + + if (digest_size > HASH_MAX_DIGEST_SIZE) + return -EINVAL; + + /* Calculate how many full digest blocks fit */ + hashcount = key_size / digest_size; + finallen = key_size % digest_size; + if (finallen) + hashcount++; + else + finallen = digest_size; + + /* Hash each chunk with a counter prefix */ + for (i = 0; i < hashcount; i++) { + size_t chunk_size, input_len; + u32 iv = cpu_to_be32(i); + + chunk_size = (i == hashcount - 1) ? finallen : digest_size; + input_len = sizeof(iv) + chunk_size; + + /* Build input: counter || block_chunk */ + memcpy(input_buf, &iv, sizeof(iv)); + memcpy(input_buf + sizeof(iv), + block_buf + (i * digest_size), chunk_size); + + /* Hash: H(counter || block_chunk) */ + algo->hash_func_ws(input_buf, input_len, hash_buf, + algo->chunk_size); + + /* Replace chunk with its hash */ + memcpy(block_buf + (i * digest_size), hash_buf, chunk_size); + } + + return 0; +} + +/** + * af_merge() - Merge anti-forensic split key into original key + * + * This performs the LUKS AF-merge operation to recover the original key from its + * AF-split representation. The algorithm XORs all stripes together, applying + * diffusion between each stripe. + * + * @src: AF-split key material (key_size * stripes bytes) + * @dst: Output buffer for merged key (key_size bytes) + * @key_size: Size of the original key + * @stripes: Number of anti-forensic stripes + * @hash_spec: Hash algorithm name (e.g., "sha256") + * Return: 0 on success, -ve on error + */ +static int af_merge(const u8 *src, u8 *dst, size_t key_size, uint stripes, + const char *hash_spec) +{ + struct hash_algo *algo; + u8 block_buf[128]; + int ret; + uint i; + + /* Look up hash algorithm */ + ret = hash_lookup_algo(hash_spec, &algo); + if (ret) { + log_debug("Unsupported hash algorithm: %s\n", hash_spec); + return -ENOTSUPP; + } + + if (key_size > sizeof(block_buf)) + return -E2BIG; + + memset(block_buf, '\0', key_size); + + /* Standard LUKS AF-merge algorithm */ + for (i = 0; i < stripes - 1; i++) { + uint j; + + /* XOR stripe into block_buf */ + for (j = 0; j < key_size; j++) + block_buf[j] ^= src[i * key_size + j]; + + /* Diffuse by hashing */ + ret = af_hash(algo, key_size, block_buf); + if (ret) + return ret; + } + + /* Final XOR with last stripe */ + for (i = 0; i < key_size; i++) + dst[i] = block_buf[i] ^ src[(stripes - 1) * key_size + i]; + + return 0; +} + +/** + * try_keyslot() - Unlock a LUKS key slot with a passphrase + * + * @blk: Block device + * @pinfo: Partition information + * @hdr: LUKS header + * @slot_idx: Key slot index to try + * @passphrase: Passphrase to try + * @md_type: Hash algorithm type + * @key_size: Size of the key + * @derived_key: Buffer for derived key (key_size bytes) + * @km: Buffer for encrypted key material + * @km_blocks: Size of km buffer in blocks + * @split_key: Buffer for AF-split key + * @candidate_key: Buffer to receive decrypted master key + * + * Return: 0 on success (correct passphrase), -EPROTO on mbedtls error, -ve on + * other error + */ +/** + * essiv_decrypt() - Decrypt key material using ESSIV mode + * + * ESSIV (Encrypted Salt-Sector Initialization Vector) mode generates a unique + * IV for each sector by encrypting the sector number with a key derived from + * hashing the encryption key. + * + * @derived_key: Key derived from passphrase + * @key_size: Size of the encryption key in bytes + * @expkey: Expanded AES key for decryption + * @km: Encrypted key material buffer + * @split_key: Output buffer for decrypted key material + * @km_blocks: Number of blocks of key material + * @blksz: Block size in bytes + */ +static void essiv_decrypt(u8 *derived_key, uint key_size, u8 *expkey, + u8 *km, u8 *split_key, uint km_blocks, uint blksz) +{ + u8 essiv_expkey[AES256_EXPAND_KEY_LENGTH]; + u8 essiv_key_material[SHA256_SUM_LEN]; + u8 iv[AES_BLOCK_LENGTH]; + u32 num_sectors = km_blocks; + uint rel_sect; + + /* Generate ESSIV key by hashing the encryption key */ + log_debug("using ESSIV mode\n"); + sha256_csum_wd(derived_key, key_size, essiv_key_material, + CHUNKSZ_SHA256); + + log_debug_hex("ESSIV key[0-7]:", essiv_key_material, 8); + + /* Expand ESSIV key for AES */ + aes_expand_key(essiv_key_material, 256, essiv_expkey); + + /* + * Decrypt each sector with its own IV + * NOTE: sector number is relative to the key material buffer, + * not an absolute disk sector + */ + for (rel_sect = 0; rel_sect < num_sectors; rel_sect++) { + u8 sector_iv[AES_BLOCK_LENGTH]; + + /* + * Create IV: little-endian sector number padded to + * 16 bytes + */ + memset(sector_iv, '\0', AES_BLOCK_LENGTH); + put_unaligned_le32(rel_sect, sector_iv); + + /* Encrypt sector number with ESSIV key to get IV */ + aes_encrypt(256, sector_iv, essiv_expkey, iv); + + /* Show the first sector for debugging */ + if (!rel_sect) { + log_debug("rel_sect %x, ", rel_sect); + log_debug_hex("IV[0-7]:", iv, 8); + } + + /* Decrypt this sector */ + aes_cbc_decrypt_blocks(key_size * 8, expkey, iv, + km + (rel_sect * blksz), + split_key + (rel_sect * blksz), + blksz / AES_BLOCK_LENGTH); + } +} + +static int try_keyslot(struct udevice *blk, struct disk_partition *pinfo, + struct luks1_phdr *hdr, int slot_idx, + const char *passphrase, mbedtls_md_type_t md_type, + uint key_size, u8 *derived_key, u8 *km, uint km_blocks, + u8 *split_key, u8 *candidate_key) +{ + struct luks1_keyslot *slot = &hdr->key_slot[slot_idx]; + uint iterations, km_offset, stripes, split_key_size; + struct blk_desc *desc = dev_get_uclass_plat(blk); + u8 expkey[AES256_EXPAND_KEY_LENGTH]; + u8 key_digest[LUKS_DIGESTSIZE]; + u8 iv[AES_BLOCK_LENGTH]; + int ret; + + /* Check if slot is active */ + if (be32_to_cpu(slot->active) != LUKS_KEY_ENABLED) + return -ENOENT; + + log_debug("trying key slot %d...\n", slot_idx); + + iterations = be32_to_cpu(slot->iterations); + km_offset = be32_to_cpu(slot->key_material_offset); + stripes = be32_to_cpu(slot->stripes); + split_key_size = key_size * stripes; + + /* Derive key from passphrase using PBKDF2 */ + log_debug("PBKDF2(pass '%s'[len %zu], ", passphrase, + strlen(passphrase)); + log_debug_hex("salt[0-7]", (u8 *)slot->salt, 8); + log_debug("iter %u, keylen %u)\n", iterations, key_size); + ret = mbedtls_pkcs5_pbkdf2_hmac_ext(md_type, (const u8 *)passphrase, + strlen(passphrase), + (const u8 *)slot->salt, + LUKS_SALTSIZE, iterations, + key_size, derived_key); + if (ret) { + log_debug("PBKDF2 failed: %d\n", ret); + return -EPROTO; + } + + log_debug_hex("derived_key[0-7]", derived_key, 8); + + /* Read encrypted key material */ + ret = blk_read(blk, pinfo->start + km_offset, km_blocks, km); + if (ret != km_blocks) { + log_debug("Failed to read key material\n"); + return -EIO; + } + + log_debug_hex("km[0-7]", km, 8); + + /* Decrypt key material using derived key */ + log_debug("expand key with key_size*8 %u bits\n", key_size * 8); + log_debug_hex("input key (derived_key) full:", derived_key, key_size); + + aes_expand_key(derived_key, key_size * 8, expkey); + + log_debug_hex("expanded key [0-15]:", expkey, 16); + + /* Decrypt with CBC mode: first check if ESSIV is used */ + if (strstr(hdr->cipher_mode, "essiv")) { + essiv_decrypt(derived_key, key_size, expkey, km, split_key, + km_blocks, desc->blksz); + } else { + /* Plain CBC with zero IV */ + memset(iv, '\0', sizeof(iv)); + log_debug("using plain CBC with zero IV\n"); + log_debug("decrypting %u blocks\n", + split_key_size / AES_BLOCK_LENGTH); + aes_cbc_decrypt_blocks(key_size * 8, expkey, iv, km, split_key, + split_key_size / AES_BLOCK_LENGTH); + } + + log_debug_hex("split_key[0-7]", split_key, 8); + + /* Merge AF-split key */ + ret = af_merge(split_key, candidate_key, key_size, stripes, + hdr->hash_spec); + if (ret) { + log_debug("af_merge() failed\n"); + return ret; + } + + log_debug_hex("candidate_key[0-7]", candidate_key, 8); + + /* Verify master key by checking its digest */ + ret = mbedtls_pkcs5_pbkdf2_hmac_ext(md_type, candidate_key, key_size, + (const u8 *)hdr->mk_digest_salt, + LUKS_SALTSIZE, + be32_to_cpu(hdr->mk_digest_iter), + LUKS_DIGESTSIZE, key_digest); + if (ret) { + log_debug("Master key digest derivation failed\n"); + return EPROTO; + } + + log_debug_hex("key_digest[0-7]", key_digest, 8); + log_debug_hex("mk_digest[0-7]", (u8 *)hdr->mk_digest, 8); + + /* Check if the digest matches */ + if (memcmp(key_digest, hdr->mk_digest, LUKS_DIGESTSIZE) == 0) { + log_debug("Uunlocked with key slot %d\n", slot_idx); + return 0; + } + log_debug("key slot %d: wrong passphrase\n", slot_idx); + + return -EACCES; +} + +int luks_unlock(struct udevice *blk, struct disk_partition *pinfo, + const char *passphrase, u8 *master_key, u32 *key_size) +{ + uint version, split_key_size, km_blocks, hdr_blocks; + struct hash_algo *hash_algo; + mbedtls_md_type_t md_type; + struct luks1_phdr *hdr; + struct blk_desc *desc; + u8 candidate_key[128]; + u8 *split_key = NULL; + u8 *derived_key = NULL; + u8 *km = NULL; + int i, ret = -EINVAL; + + if (!blk || !pinfo || !passphrase || !master_key || !key_size) + return -EINVAL; + + desc = dev_get_uclass_plat(blk); + + /* LUKS1 header is 592 bytes, calculate blocks needed */ + hdr_blocks = (sizeof(struct luks1_phdr) + desc->blksz - 1) / + desc->blksz; + + /* Allocate buffer for LUKS header */ + ALLOC_CACHE_ALIGN_BUFFER(u8, buffer, hdr_blocks * desc->blksz); + + /* Read LUKS header */ + if (blk_read(blk, pinfo->start, hdr_blocks, buffer) != hdr_blocks) { + log_debug("failed to read LUKS header\n"); + return -EIO; + } + + /* Verify it's LUKS */ + if (memcmp(buffer, LUKS_MAGIC, LUKS_MAGIC_LEN) != 0) { + log_debug("not a LUKS partition\n"); + return -ENOENT; + } + + version = be16_to_cpu(*(__be16 *)(buffer + LUKS_MAGIC_LEN)); + if (version != LUKS_VERSION_1) { + log_debug("only LUKS1 decryption is currently supported\n"); + return -ENOTSUPP; + } + + hdr = (struct luks1_phdr *)buffer; + + /* Debug: show what we read from header */ + log_debug("Read header at sector %llu, mk_digest[0-7] ", (unsigned long long)pinfo->start); + log_debug_hex("", (u8 *)hdr->mk_digest, 8); + + /* Verify cipher mode - only CBC supported */ + if (strncmp(hdr->cipher_mode, "cbc", 3) != 0) { + log_debug("only CBC mode is currently supported (got: %.32s)\n", + hdr->cipher_mode); + return -ENOTSUPP; + } + + /* Look up hash algorithm */ + ret = hash_lookup_algo(hdr->hash_spec, &hash_algo); + if (ret) { + log_debug("unsupported hash: %.32s\n", hdr->hash_spec); + return -ENOTSUPP; + } + + md_type = hash_mbedtls_type(hash_algo); + + *key_size = be32_to_cpu(hdr->key_bytes); + + /* Find the first active slot to get the stripes value */ + u32 stripes = 0; + for (i = 0; i < LUKS_NUMKEYS; i++) { + if (be32_to_cpu(hdr->key_slot[i].active) == LUKS_KEY_ENABLED) { + stripes = be32_to_cpu(hdr->key_slot[i].stripes); + break; + } + } + if (stripes == 0) { + log_debug("no active key slots found\n"); + return -ENOENT; + } + + split_key_size = *key_size * stripes; + + log_debug("Trying to unlock LUKS partition: key size: %u bytes\n", + *key_size); + + /* Allocate buffers */ + derived_key = malloc(*key_size); + split_key = malloc(split_key_size); + km_blocks = (split_key_size + desc->blksz - 1) / desc->blksz; + km = malloc_cache_aligned(km_blocks * desc->blksz); + + if (!derived_key || !split_key || !km) { + ret = -ENOMEM; + goto out; + } + + /* Try each key slot */ + for (i = 0; i < LUKS_NUMKEYS; i++) { + ret = try_keyslot(blk, pinfo, hdr, i, passphrase, md_type, + *key_size, derived_key, km, km_blocks, + split_key, candidate_key); + + if (!ret) { + /* Successfully unlocked */ + memcpy(master_key, candidate_key, *key_size); + goto out; + } + /* Continue trying other slots on failure */ + } + + log_debug("Failed to unlock: wrong passphrase or no active key slots\n"); + ret = -EACCES; + +out: + if (derived_key) { + memset(derived_key, '\0', *key_size); + free(derived_key); + } + if (split_key) { + memset(split_key, '\0', split_key_size); + free(split_key); + } + if (km) { + memset(km, '\0', km_blocks * desc->blksz); + free(km); + } + memset(candidate_key, '\0', sizeof(candidate_key)); + + return ret; +} + +/** + * luks_create_blkmap() - Create a blkmap device for a LUKS partition + * + * This creates and configures a blkmap device to provide access to the + * decrypted contents of a LUKS partition. The master key must already be + * unlocked using luks_unlock(). + * + * @blk: Block device containing the LUKS partition + * @pinfo: Partition information + * @master_key: Unlocked master key + * @key_size: Size of the master key in bytes + * @label: Label for the blkmap device + * @blkmap_dev: Output pointer for created blkmap device + * Return: 0 on success, -ve on error + */ +int luks_create_blkmap(struct udevice *blk, struct disk_partition *pinfo, + const u8 *master_key, u32 key_size, const char *label, + struct udevice **blkmap_dev) +{ + u8 essiv_key[SHA256_SUM_LEN]; /* SHA-256 output */ + struct luks1_phdr *hdr; + struct blk_desc *desc; + struct udevice *dev; + uint payload_offset; + bool use_essiv; + int ret; + + if (!blk || !pinfo || !master_key || !label || !blkmap_dev) + return -EINVAL; + + desc = dev_get_uclass_plat(blk); + + /* Read LUKS header to get payload offset and cipher mode */ + ALLOC_CACHE_ALIGN_BUFFER(u8, buf, desc->blksz); + if (blk_read(blk, pinfo->start, 1, buf) != 1) { + log_debug("failed to read LUKS header\n"); + return -EIO; + } + hdr = (struct luks1_phdr *)buf; + + /* Create blkmap device */ + ret = blkmap_create(label, &dev); + if (ret) { + log_debug("failed to create blkmap device\n"); + return ret; + } + + /* Check if ESSIV mode is used */ + use_essiv = strstr(hdr->cipher_mode, "essiv"); + + if (use_essiv) { + int hash_size = SHA256_SUM_LEN; + + if (hash_block("sha256", master_key, key_size, essiv_key, + &hash_size)) { + log_debug("SHA256 hash algorithm not available\n"); + blkmap_destroy(dev); + return -ENOTSUPP; + } + } + + /* Map the encrypted partition to the blkmap device */ + payload_offset = be32_to_cpu(hdr->payload_offset); + log_debug("mapping blkmap: blknr 0 blkcnt %lx payload_offset %x essiv %d\n", + (ulong)pinfo->size, payload_offset, use_essiv); + ret = blkmap_map_crypt(dev, 0, pinfo->size, blk, pinfo->start, + master_key, key_size, payload_offset, + use_essiv, use_essiv ? essiv_key : NULL); + if (ret) { + log_debug("failed to map encrypted partition\n"); + blkmap_destroy(dev); + return ret; + } + + /* Wipe ESSIV key from stack */ + if (use_essiv) + memset(essiv_key, '\0', sizeof(essiv_key)); + *blkmap_dev = dev; + + return 0; +} diff --git a/include/luks.h b/include/luks.h index ea6dd510c53..f8fda27e132 100644 --- a/include/luks.h +++ b/include/luks.h @@ -137,4 +137,39 @@ int luks_get_version(struct udevice *blk, struct disk_partition *pinfo); */ int luks_show_info(struct udevice *blk, struct disk_partition *pinfo); +/** + * luks_unlock() - Unlock a LUKS partition with a passphrase + * + * This attempts to decrypt the master key using the provided passphrase. + * Currently only supports LUKS1 with PBKDF2 and AES-CBC. + * + * @blk: Block device + * @pinfo: Partition information + * @passphrase: Passphrase to unlock the partition + * @master_key: Buffer to receive the decrypted master key + * @key_size: Size of the master_key buffer + * Return: 0 on success, -ve on error + */ +int luks_unlock(struct udevice *blk, struct disk_partition *pinfo, + const char *passphrase, u8 *master_key, u32 *key_size); + +/** + * luks_create_blkmap() - Create a blkmap device for a LUKS partition + * + * This creates and configures a blkmap device to provide access to the + * decrypted contents of a LUKS partition. The master key must already be + * unlocked using luks_unlock(). + * + * @blk: Block device containing the LUKS partition + * @pinfo: Partition information + * @master_key: Unlocked master key + * @key_size: Size of the master key in bytes + * @label: Label for the blkmap device + * @blkmap_dev: Output pointer for created blkmap device + * Return: 0 on success, -ve on error + */ +int luks_create_blkmap(struct udevice *blk, struct disk_partition *pinfo, + const u8 *master_key, u32 key_size, const char *label, + struct udevice **blkmap_dev); + #endif /* __LUKS_H__ */ -- 2.43.0
From: Simon Glass <sjg@chromium.org> Provide a new 'luks unlock' command which can unlock a LUKS1 partition, given a passphrase. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- cmd/luks.c | 70 ++++++++++++++++++++++++++++++++- doc/usage/cmd/luks.rst | 87 +++++++++++++++++++++++++++++++++++++++++- test/boot/luks.c | 26 +++++++++++++ 3 files changed, 180 insertions(+), 3 deletions(-) diff --git a/cmd/luks.c b/cmd/luks.c index 04ddcdb1d46..19f909be96b 100644 --- a/cmd/luks.c +++ b/cmd/luks.c @@ -57,11 +57,77 @@ static int do_luks_info(struct cmd_tbl *cmdtp, int flag, int argc, return CMD_RET_SUCCESS; } +static int do_luks_unlock(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct blk_desc *dev_desc; + struct disk_partition info; + struct udevice *blkmap_dev; + const char *passphrase; + int part, ret, version; + u8 master_key[128]; + char label[64]; + u32 key_size; + + if (argc != 4) + return CMD_RET_USAGE; + + part = blk_get_device_part_str(argv[1], argv[2], &dev_desc, &info, 1); + if (part < 0) + return CMD_RET_FAILURE; + + passphrase = argv[3]; + + /* Verify it's a LUKS partition */ + version = luks_get_version(dev_desc->bdev, &info); + if (version < 0) { + printf("Not a LUKS partition\n"); + return CMD_RET_FAILURE; + } + + if (version != LUKS_VERSION_1) { + printf("Only LUKS1 is currently supported\n"); + return CMD_RET_FAILURE; + } + + /* Unlock the partition to get the master key */ + ret = luks_unlock(dev_desc->bdev, &info, passphrase, master_key, + &key_size); + if (ret) { + printf("Failed to unlock LUKS partition (err %dE)\n", ret); + return CMD_RET_FAILURE; + } + + /* Create blkmap device with label based on source device */ + snprintf(label, sizeof(label), "luks-%s-%s", argv[1], argv[2]); + + /* Create and map the blkmap device */ + ret = luks_create_blkmap(dev_desc->bdev, &info, master_key, key_size, + label, &blkmap_dev); + if (ret) { + printf("Failed to create blkmap device (err %dE)\n", ret); + ret = CMD_RET_FAILURE; + goto cleanup; + } + + printf("Unlocked LUKS partition as blkmap device '%s'\n", label); + + ret = CMD_RET_SUCCESS; + +cleanup: + /* Wipe master key from stack */ + memset(master_key, '\0', sizeof(master_key)); + + return ret; +} + static char luks_help_text[] = "detect <interface> <dev[:part]> - detect if partition is LUKS encrypted\n" - "luks info <interface> <dev[:part]> - show LUKS header information"; + "luks info <interface> <dev[:part]> - show LUKS header information\n" + "luks unlock <interface> <dev[:part]> <passphrase> - unlock LUKS partition\n"; U_BOOT_CMD_WITH_SUBCMDS(luks, "LUKS (Linux Unified Key Setup) operations", luks_help_text, U_BOOT_SUBCMD_MKENT(detect, 3, 1, do_luks_detect), - U_BOOT_SUBCMD_MKENT(info, 3, 1, do_luks_info)); + U_BOOT_SUBCMD_MKENT(info, 3, 1, do_luks_info), + U_BOOT_SUBCMD_MKENT(unlock, 4, 1, do_luks_unlock)); diff --git a/doc/usage/cmd/luks.rst b/doc/usage/cmd/luks.rst index c3b03eeff2e..8dbcb8549c7 100644 --- a/doc/usage/cmd/luks.rst +++ b/doc/usage/cmd/luks.rst @@ -13,11 +13,12 @@ Synopsis luks detect <interface> <dev[:part]> luks info <interface> <dev[:part]> + luks unlock <interface> <dev[:part]> <passphrase> Description ----------- -The *luks* command provides an interface to detect and inspect LUKS +The *luks* command provides an interface to detect, inspect, and unlock LUKS (Linux Unified Key Setup) encrypted partitions. LUKS is a disk encryption specification used for full disk encryption on Linux systems. @@ -25,6 +26,7 @@ This command supports: * Detection of LUKS encrypted partitions (LUKS1 and LUKS2) * Display of LUKS header information +* Unlocking LUKS1 partitions with passphrase-based authentication * Access to decrypted data via blkmap devices The LUKS format uses a distinctive header containing: @@ -83,6 +85,42 @@ dev[:part] The device number and optional partition number. If partition is omitted, defaults to the whole device. +luks unlock +~~~~~~~~~~~ + +Unlock a LUKS1 encrypted partition using a passphrase. This command: + +1. Verifies the partition is LUKS1 encrypted +2. Derives the encryption key using PBKDF2 with the provided passphrase +3. Attempts to unlock each active key slot +4. Verifies the master key against the stored digest +5. Creates a blkmap device providing on-the-fly decryption + +After successful unlock, the decrypted data is accessible through a blkmap +device (typically ``blkmap 0``). Standard U-Boot filesystem commands can then +be used to access files on the unlocked partition. + +**Currently only LUKS1 is supported for unlocking. LUKS2 unlock is not yet +implemented.** + +Supported cipher modes: + +* aes-cbc-essiv:sha256 (AES in CBC mode with ESSIV) + +interface + The storage interface type (e.g., mmc, usb, scsi) + +dev[:part] + The device number and optional partition number + +passphrase + The passphrase to unlock the LUKS partition. Note that the passphrase is + passed as a command-line argument and may be visible in command history. + Consider using environment variables to minimize exposure. + +The unlocked data remains accessible until U-Boot exits or the blkmap device +is explicitly destroyed. + Examples -------- @@ -145,6 +183,40 @@ Display LUKS header information for a LUKS1 partition:: Payload offset: 4096 sectors Key bytes: 32 +Unlock a LUKS1 partition and access files:: + + => luks unlock mmc 0:2 mypassword + Trying to unlock LUKS partition... + Key size: 32 bytes + Trying key slot 0... + Successfully unlocked with key slot 0! + Unlocked LUKS partition as blkmap device 'luks-mmc-0:2' + Access decrypted data via: blkmap 0 + + => ls blkmap 0 / + ./ + ../ + lost+found/ + 221 README.md + 17 hello.txt + subdir/ + 20 test.txt + + 3 file(s), 4 dir(s) + + => cat blkmap 0 /hello.txt + Hello from LUKS! + +Unlock and load a kernel from encrypted partition:: + + => luks unlock mmc 0:2 ${rootfs_password} + Successfully unlocked with key slot 0! + + => ext4load blkmap 0 ${kernel_addr_r} /boot/vmlinuz + 5242880 bytes read in 123 ms (40.6 MiB/s) + + => bootz ${kernel_addr_r} - ${fdt_addr_r} + Configuration ------------- @@ -155,14 +227,27 @@ For LUKS detection and info commands:: CONFIG_BLK_LUKS=y CONFIG_CMD_LUKS=y +For LUKS unlock functionality, additional options are required:: + + CONFIG_BLK_LUKS=y + CONFIG_CMD_LUKS=y + CONFIG_BLKMAP=y # For blkmap device support + CONFIG_AES=y # For AES encryption + CONFIG_SHA256=y # For SHA-256 hashing + CONFIG_PBKDF2=y # For PBKDF2 key derivation + Return value ------------ For *detect* and *info*: The return value $? is 0 (true) on success, 1 (false) on failure. +For *unlock*: The return value $? is 0 (true) if unlock succeeded, 1 (false) +if unlock failed (wrong passphrase, unsupported format, etc.). + See also -------- +* :doc:`blkmap` - Blkmap device documentation * cryptsetup project: https://gitlab.com/cryptsetup/cryptsetup * LUKS on-disk format specifications: https://gitlab.com/cryptsetup/cryptsetup/-/wikis/home diff --git a/test/boot/luks.c b/test/boot/luks.c index 70ee0fb0824..afa8c9f172e 100644 --- a/test/boot/luks.c +++ b/test/boot/luks.c @@ -213,3 +213,29 @@ static int bootstd_test_luks2_info(struct unit_test_state *uts) return 0; } BOOTSTD_TEST(bootstd_test_luks2_info, UTF_DM | UTF_SCAN_FDT | UTF_CONSOLE); + +/* Test LUKS unlock command with LUKS1 encrypted partition */ +static int bootstd_test_luks_unlock(struct unit_test_state *uts) +{ + struct udevice *mmc; + + ut_assertok(setup_mmc11(uts, &mmc)); + + /* Test that unlock command exists and handles errors properly */ + /* Should fail because partition 1 is not LUKS */ + ut_asserteq(1, run_command("luks unlock mmc b:1 test", 0)); + ut_assert_nextline("Not a LUKS partition"); + ut_assert_console_end(); + + /* Test unlocking partition 2 with correct passphrase */ + ut_assertok(run_command("luks unlock mmc b:2 test", 0)); + ut_assert_nextline("Unlocked LUKS partition as blkmap device 'luks-mmc-b:2'"); + ut_assert_console_end(); + + /* Test unlocking with wrong passphrase */ + ut_asserteq(1, run_command("luks unlock mmc b:2 wrongpass", 0)); + ut_assert_skip_to_line("Failed to unlock LUKS partition (err -13: Permission denied)"); + + return 0; +} +BOOTSTD_TEST(bootstd_test_luks_unlock, UTF_DM | UTF_SCAN_FDT | UTF_CONSOLE); -- 2.43.0
From: Simon Glass <sjg@chromium.org> Provide a function which can detect a LUKS partition. Add a test, using mmc11 Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- MAINTAINERS | 1 + doc/usage/cmd/luks.rst | 1 + doc/usage/index.rst | 1 + doc/usage/luks.rst | 340 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 343 insertions(+) create mode 100644 doc/usage/luks.rst diff --git a/MAINTAINERS b/MAINTAINERS index a0374df1c4d..83f7a2ae1e9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1295,6 +1295,7 @@ S: Maintained T: git https://concept.u-boot.org/u-boot/u-boot.git F: cmd/luks.c F: doc/usage/cmd/luks.rst +F: doc/usage/luks.rst F: drivers/block/luks.c F: include/luks.h F: include/json.h diff --git a/doc/usage/cmd/luks.rst b/doc/usage/cmd/luks.rst index 8dbcb8549c7..6c262e66200 100644 --- a/doc/usage/cmd/luks.rst +++ b/doc/usage/cmd/luks.rst @@ -248,6 +248,7 @@ if unlock failed (wrong passphrase, unsupported format, etc.). See also -------- +* :doc:`../luks` - Comprehensive LUKS feature documentation * :doc:`blkmap` - Blkmap device documentation * cryptsetup project: https://gitlab.com/cryptsetup/cryptsetup * LUKS on-disk format specifications: https://gitlab.com/cryptsetup/cryptsetup/-/wikis/home diff --git a/doc/usage/index.rst b/doc/usage/index.rst index 2bb01211227..e8dbabfa9d2 100644 --- a/doc/usage/index.rst +++ b/doc/usage/index.rst @@ -11,6 +11,7 @@ Use U-Boot environment fdt_overlays fit/index + luks netconsole partitions cmdline diff --git a/doc/usage/luks.rst b/doc/usage/luks.rst new file mode 100644 index 00000000000..a87abe140db --- /dev/null +++ b/doc/usage/luks.rst @@ -0,0 +1,340 @@ +.. SPDX-License-Identifier: GPL-2.0+ + +LUKS (Linux Unified Key Setup) +=============================== + +Overview +-------- + +U-Boot provides support for accessing LUKS-encrypted partitions through the +``luks`` command and the blkmap device infrastructure. This allows U-Boot to +unlock and read data from LUKS1-encrypted block devices. + +LUKS is a standard for disk encryption that stores encryption metadata in a +header on the encrypted device. It supports multiple key slots, allowing +different passphrases to unlock the same encrypted data. + +Currently, U-Boot supports: + +* LUKS version 1 (LUKS1) +* AES-256-CBC encryption with ESSIV (Encrypted Salt-Sector IV) mode +* Passphrase-based unlocking via PBKDF2 key derivation +* Reading ext4 and other filesystems from unlocked devices + +Supported Cipher Modes +----------------------- + +The following LUKS1 cipher configurations are supported: + +* **Cipher**: aes +* **Mode**: cbc-essiv:sha256 +* **Key sizes**: 128-bit, 192-bit, 256-bit +* **Hash**: sha256 (for PBKDF2) + +Commands +-------- + +See the :doc:`cmd/luks` for full details. + +luks detect +~~~~~~~~~~~ + +Detect whether a partition is LUKS-encrypted:: + + luks detect <interface> <dev[:part]> + +Example:: + + => luks detect mmc 0:2 + LUKS1 encrypted partition detected + +This command checks for the LUKS magic bytes and validates the header structure. + +luks info +~~~~~~~~~ + +Display information about a LUKS partition:: + + luks info <interface> <dev[:part]> + +Example:: + + => luks info mmc 0:2 + Version: 1 + Cipher name: aes + Cipher mode: cbc-essiv:sha256 + Hash spec: sha256 + Payload offset: 4096 + Key bytes: 32 + +This shows the LUKS header metadata including encryption parameters and the +offset to the encrypted data payload. + +luks unlock +~~~~~~~~~~~ + +Unlock a LUKS partition with a passphrase:: + + luks unlock <interface> <dev[:part]> <passphrase> + +Example:: + + => luks unlock mmc 0:2 mypassword + Trying to unlock LUKS partition... + Key size: 32 bytes + Trying key slot 0... + Successfully unlocked with key slot 0! + Unlocked LUKS partition as blkmap device 'luks-mmc-0:2' + Access decrypted data via: blkmap 0 + +This command: + +1. Reads the LUKS header from the specified partition +2. Derives the encryption key from the passphrase using PBKDF2 +3. Attempts to unlock each active key slot +4. Verifies the unlocked key against the master key digest +5. Creates a blkmap device that provides on-the-fly decryption + +Accessing Unlocked Data +------------------------ + +After unlocking, the decrypted data is accessible through a blkmap device. +The device number is shown in the unlock output (typically ``blkmap 0``). + +Listing Files +~~~~~~~~~~~~~ + +Use standard filesystem commands to access the unlocked device:: + + => ls blkmap 0 / + ./ + ../ + lost+found/ + 221 README.md + 17 hello.txt + subdir/ + 20 test.txt + + 3 file(s), 4 dir(s) + +Reading Files +~~~~~~~~~~~~~ + +Read file contents:: + + => cat blkmap 0 /hello.txt + Hello from LUKS! + +Loading Files to Memory +~~~~~~~~~~~~~~~~~~~~~~~~ + +Load files for further processing:: + + => ext4load blkmap 0 ${loadaddr} /boot/vmlinuz + 5242880 bytes read in 123 ms (40.6 MiB/s) + +Using with Boot Flows +~~~~~~~~~~~~~~~~~~~~~ + +LUKS-encrypted partitions can be integrated into boot flows:: + + => bootflow scan + => bootflow list + +The bootflow will detect filesystems on unlocked blkmap devices. + +Implementation Details +---------------------- + +Key Derivation +~~~~~~~~~~~~~~ + +LUKS uses PBKDF2 (Password-Based Key Derivation Function 2) to derive +encryption keys from passphrases. The process: + +1. Passphrase + salt → PBKDF2 → derived key +2. Derived key decrypts the AF-split key material +3. AF-merge combines the split key material → master key +4. Master key digest is verified against stored value + +Anti-Forensic (AF) Splitter +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +LUKS uses an anti-forensic information splitter to protect key material: + +* The master key is split into 4000 stripes +* Each stripe is XORed and hashed with a counter +* All stripes must be recovered to reconstruct the key +* This makes recovery from partial key material extremely difficult + +ESSIV Mode +~~~~~~~~~~ + +ESSIV (Encrypted Salt-Sector IV) generates unique initialization vectors +for each encrypted sector: + +1. ESSIV key = SHA256(master_key) +2. For each sector: IV = AES_encrypt(sector_number, ESSIV_key) +3. Data is encrypted: ciphertext = AES-CBC-encrypt(plaintext, master_key, IV) + +This prevents watermarking attacks where identical plaintext blocks would +produce identical ciphertext blocks. + +Blkmap Device Mapping +~~~~~~~~~~~~~~~~~~~~~ + +When a LUKS partition is unlocked, U-Boot creates a blkmap device that: + +* Maps to the underlying encrypted device +* Performs on-the-fly AES-CBC decryption with ESSIV +* Presents decrypted data as a standard block device +* Supports whole-disk filesystems (no partition table required) + +The blkmap device number can be used with any U-Boot command that accepts +block device specifications. + +Creating LUKS Partitions for Testing +------------------------------------- + +Using cryptsetup on Linux:: + + # Create a 60MB file + $ dd if=/dev/zero of=test.img bs=1M count=60 + + # Format as LUKS1 + $ cryptsetup luksFormat --type luks1 \ + --cipher aes-cbc-essiv:sha256 \ + --key-size 256 \ + --hash sha256 \ + test.img + + # Open the LUKS device + $ cryptsetup open test.img testluks + + # Create filesystem + $ mkfs.ext4 /dev/mapper/testluks + + # Mount and add files + $ mount /dev/mapper/testluks /mnt + $ echo "Hello from LUKS!" > /mnt/hello.txt + $ umount /mnt + + # Close the LUKS device + $ cryptsetup close testluks + +Using Python with U-Boot Test Infrastructure +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +See ``test/py/tests/fs_helper.py`` for the ``FsHelper`` class:: + + from fs_helper import FsHelper, DiskHelper + + # Create encrypted filesystem + with FsHelper(config, 'ext4', 30, 'test', + part_mb=60, + encrypt_passphrase='mypassword') as fsh: + # Add files to fsh.srcdir + with open(os.path.join(fsh.srcdir, 'hello.txt'), 'w') as f: + f.write('Hello from LUKS!\n') + + # Create encrypted filesystem + fsh.mk_fs() + +Configuration Options +--------------------- + +CONFIG_CMD_LUKS + Enable the ``luks`` command + +CONFIG_BLKMAP + Enable blkmap device support (required for LUKS unlock) + +CONFIG_AES + Enable AES encryption support + +CONFIG_SHA256 + Enable SHA-256 hashing support + +CONFIG_PBKDF2 + Enable PBKDF2 key derivation + +Limitations +----------- + +* Only LUKS1 is currently supported (LUKS2 not yet implemented) +* Only AES-CBC-ESSIV cipher mode is supported +* Only passphrase-based unlocking is supported (no key files) +* Unlocked devices are read-only (write support not yet implemented) +* Master key remains in memory after unlocking (not securely erased on exit) + +Security Considerations +----------------------- + +Passphrase Handling +~~~~~~~~~~~~~~~~~~~ + +* Passphrases are passed as command-line arguments +* They may be visible in command history +* Consider using environment variables or scripts to minimize exposure + +Memory Security +~~~~~~~~~~~~~~~ + +* Master keys are wiped from stack after use where possible +* Keys remain in blkmap device structure while device is active +* No secure memory protection or key erasure on warm reboot + +Intended Use Cases +~~~~~~~~~~~~~~~~~~ + +U-Boot's LUKS support is intended for: + +* Boot-time access to encrypted root filesystems +* Reading configuration from encrypted partitions +* Testing and development of encrypted disk support + +It is **not** a replacement for full disk encryption solutions in production +operating systems. + +Example Use Cases +----------------- + +Encrypted Root Filesystem +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Boot from an encrypted root partition:: + + => luks unlock mmc 0:2 ${rootfs_password} + => setenv bootargs root=/dev/blkmap0 rootfstype=ext4 + => ext4load blkmap 0 ${kernel_addr_r} /boot/vmlinuz + => ext4load blkmap 0 ${ramdisk_addr_r} /boot/initrd.img + => bootz ${kernel_addr_r} ${ramdisk_addr_r} ${fdt_addr_r} + +Encrypted Configuration Storage +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Read configuration from encrypted partition:: + + => luks unlock mmc 0:3 ${config_password} + => ext4load blkmap 0 ${loadaddr} /config/boot.conf + => env import -t ${loadaddr} ${filesize} + +Testing +------- + +See ``test/boot/luks.c`` for tests: + +* LUKS detection on encrypted and non-encrypted partitions +* LUKS header information parsing +* Successful unlocking with correct passphrase +* Rejection of incorrect passphrases +* Blkmap device creation and filesystem access + +See Also +-------- + +* :doc:`cmd/luks` - LUKS command reference +* :doc:`cmd/blkmap` - Blkmap command reference +* :doc:`blkmap` - Blkmap device documentation +* ``test/py/tests/fs_helper.py`` - Filesystem helper for creating test images +* Linux ``cryptsetup`` documentation for LUKS disk format specification -- 2.43.0
participants (1)
- 
                
Simon Glass