[Concept,21/24] luks: Enhance blkmap to support LUKSv1

Message ID 20251031065439.3251464-22-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>

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(+)
  

Patch

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
  *