[Concept,23/24] luks: Add a subcommand to unlock an encrypted partition

Message ID 20251031065439.3251464-24-sjg@u-boot.org
State New
Headers
Series luks: Provide basic support for unlocking a LUKS1 partition |

Commit Message

Simon Glass Oct. 31, 2025, 6:54 a.m. UTC
  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(-)
  

Patch

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