[PATCH 0/5] luks: Support the AES-XTS cipher mode
From: Simon Glass <simon.glass@canonical.com> This series finishes off the implementation of LUKSv2, adding support for the common cipher mode and testing that files can be read from the disk. It includes a fix for using the correct size when mapping the crypt, as well as some refactoring to split up the code a little better. Simon Glass (5): luks: Exclude the payload from the size calculation luks: test: Check file access in the decryption tests luks: Move blkmap crypt code into a separate file luks: Split out crypt reading into its own function luks: Add XTS cipher mode support for LUKS2 drivers/block/Makefile | 1 + drivers/block/blkmap.c | 204 +---------------- drivers/block/blkmap_crypt.c | 394 ++++++++++++++++++++++++++++++++ drivers/block/blkmap_internal.h | 74 ++++++ drivers/block/luks.c | 50 +++- include/blkmap.h | 13 +- include/luks.h | 10 +- test/boot/luks.c | 41 +++- 8 files changed, 568 insertions(+), 219 deletions(-) create mode 100644 drivers/block/blkmap_crypt.c create mode 100644 drivers/block/blkmap_internal.h -- 2.43.0 base-commit: b68c8fb93b11ce80b683e06d09de74266e4e53f6 branch: sece-fix
From: Simon Glass <simon.glass@canonical.com> Fix the blkmap-size calculation to exclude the LUKS header/payload offset. This was missed in the initial implementation. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> Fixes: 0cbfb2d4900 ("luks: Provide a way to unlock and map encrypted..") --- drivers/block/luks.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/block/luks.c b/drivers/block/luks.c index 3bdfd7dba61..c7e5a3da154 100644 --- a/drivers/block/luks.c +++ b/drivers/block/luks.c @@ -596,6 +596,7 @@ int luks_create_blkmap(struct udevice *blk, struct disk_partition *pinfo, struct udevice **blkmapp) { u8 essiv_key[SHA256_SUM_LEN]; /* SHA-256 output */ + lbaint_t decrypted_size; struct luks1_phdr *hdr; struct luks2_hdr *hdr2; struct blk_desc *desc; @@ -736,10 +737,15 @@ int luks_create_blkmap(struct udevice *blk, struct disk_partition *pinfo, } } - /* Map the encrypted partition to the blkmap device */ - 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, + /* + * Map the encrypted partition to the blkmap device. The decrypted size + * is the partition size minus the payload offset + */ + decrypted_size = pinfo->size - payload_offset; + log_debug("mapping blkmap: blknr 0 blkcnt %llx payload_offset %x essiv %d\n", + (unsigned long long)decrypted_size, payload_offset, + use_essiv); + ret = blkmap_map_crypt(dev, 0, decrypted_size, blk, pinfo->start, master_key, key_size, payload_offset, use_essiv, use_essiv ? essiv_key : NULL); if (ret) { -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> The current tests check that decryption happens but don't go so far as reading a file. Add the logic for this. Since this currently fails for LUKSv2, leave the last part of that test out for now. Use direct filesystem calls for LUKSv2 since it is easier to check for the current error. Add more information on the errors returned by luks_unlock() so we can check for the correct one. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- include/luks.h | 8 +++++++- test/boot/luks.c | 44 +++++++++++++++++++++++++++++++++++++++----- 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/include/luks.h b/include/luks.h index 2c52cc48689..4f65f3029f8 100644 --- a/include/luks.h +++ b/include/luks.h @@ -148,7 +148,13 @@ int luks_show_info(struct udevice *blk, struct disk_partition *pinfo); * @pass: 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 + * Return: 0 on success, + * -EINVAL if null parameters, + * -EACCES if passphrase is incorrect, + * -ENOENT if not a LUKS partition or no active key slots, + * -ENOTSUPP if unsupported version/cipher/hash, + * -ENOMEM if memory allocation failed, + * -EIO if failed to read from block device */ int luks_unlock(struct udevice *blk, struct disk_partition *pinfo, const char *pass, u8 *master_key, u32 *key_size); diff --git a/test/boot/luks.c b/test/boot/luks.c index 4ee6081a790..ec95b241d8a 100644 --- a/test/boot/luks.c +++ b/test/boot/luks.c @@ -7,6 +7,8 @@ #include <blk.h> #include <dm.h> +#include <errno.h> +#include <fs_legacy.h> #include <luks.h> #include <mmc.h> #include <part.h> @@ -217,7 +219,9 @@ 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 blk_desc *desc; struct udevice *mmc; + loff_t file_size; ut_assertok(setup_mmc11(uts, &mmc)); @@ -233,6 +237,18 @@ static int bootstd_test_luks_unlock(struct unit_test_state *uts) ut_assert_nextline("Unlocked LUKS partition as blkmap device 'luks-mmc-b:2'"); ut_assert_console_end(); + /* Get the blkmap device number */ + ut_assertok(run_command("blkmap get luks-mmc-b:2 dev devnum", 0)); + ut_assert_console_end(); + + /* Verify that a file can be read from the decrypted filesystem */ + desc = blk_get_devnum_by_uclass_idname("blkmap", 0); + ut_assertnonnull(desc); + + ut_assertok(fs_set_blk_dev_with_part(desc, 0)); + ut_assertok(fs_size("/bin/bash", &file_size)); + ut_asserteq(5, file_size); + /* Test unlocking with wrong 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)"); @@ -244,15 +260,22 @@ BOOTSTD_TEST(bootstd_test_luks_unlock, UTF_DM | UTF_SCAN_FDT | UTF_CONSOLE); /* Test LUKS2 unlock command with LUKS2 encrypted partition */ static int bootstd_test_luks2_unlock(struct unit_test_state *uts) { + struct disk_partition info; + struct blk_desc *desc; struct udevice *mmc; + u8 master_key[512]; + loff_t file_size; + u32 key_size; ut_assertok(setup_mmc12(uts, &mmc)); + desc = blk_get_by_device(mmc); + ut_assertnonnull(desc); + ut_assertnonnull(desc->bdev); - /* 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 c:1 test", 0)); - ut_assert_nextline("Not a LUKS partition"); - ut_assert_console_end(); + /* Test that unlock fails for partition 1 (not LUKS) */ + ut_assertok(part_get_info(desc, 1, &info)); + ut_asserteq(-ENOENT, luks_unlock(desc->bdev, &info, "test", master_key, + &key_size)); /* Test unlocking partition 2 with correct passphrase */ ut_assertok(run_command("luks unlock mmc c:2 test", 0)); @@ -260,6 +283,17 @@ static int bootstd_test_luks2_unlock(struct unit_test_state *uts) ut_assert_nextline("Unlocked LUKS partition as blkmap device 'luks-mmc-c:2'"); ut_assert_console_end(); + /* Verify that a file can be read from the decrypted filesystem */ + desc = blk_get_devnum_by_uclass_idname("blkmap", 0); + ut_assertnonnull(desc); + + /* at present this fails due to incorrect decryption */ + if (0) { + ut_assertok(fs_set_blk_dev_with_part(desc, 0)); + ut_assertok(fs_size("/bin/bash", &file_size)); + ut_asserteq(5, file_size); + } + /* Test unlocking with wrong passphrase */ ut_asserteq(1, run_command("luks unlock mmc c:2 wrongpass", 0)); ut_assert_nextline("Unlocking LUKS2 partition..."); -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Create a new blkmap_crypt.c file to hold the LUKS code, since it is fairly large. Add an internal header for blkmap as well. Signed-off-by: Simon Glass <simon.glass@canonical.com> --- drivers/block/Makefile | 1 + drivers/block/blkmap.c | 204 +------------------------------- drivers/block/blkmap_crypt.c | 172 +++++++++++++++++++++++++++ drivers/block/blkmap_internal.h | 74 ++++++++++++ 4 files changed, 249 insertions(+), 202 deletions(-) create mode 100644 drivers/block/blkmap_crypt.c create mode 100644 drivers/block/blkmap_internal.h diff --git a/drivers/block/Makefile b/drivers/block/Makefile index 538b602790d..0735efea719 100644 --- a/drivers/block/Makefile +++ b/drivers/block/Makefile @@ -17,6 +17,7 @@ endif obj-$(CONFIG_SANDBOX) += sandbox.o host-uclass.o host_dev.o obj-$(CONFIG_$(PHASE_)BLOCK_CACHE) += blkcache.o obj-$(CONFIG_$(PHASE_)BLKMAP) += blkmap.o +obj-$(CONFIG_BLK_LUKS) += blkmap_crypt.o obj-$(CONFIG_$(PHASE_)BLKMAP) += blkmap_helper.o obj-$(CONFIG_EFI_MEDIA) += efi-media-uclass.o diff --git a/drivers/block/blkmap.c b/drivers/block/blkmap.c index 13a79001979..14aa1e4d088 100644 --- a/drivers/block/blkmap.c +++ b/drivers/block/blkmap.c @@ -11,62 +11,11 @@ #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; - -/** - * struct blkmap_slice - Region mapped to a blkmap - * - * Common data for a region mapped to a blkmap, specialized by each - * map type. - * - * @node: List node used to associate this slice with a blkmap - * @blknr: Start block number of the mapping - * @blkcnt: Number of blocks covered by this mapping - */ -struct blkmap_slice { - struct list_head node; - - lbaint_t blknr; - lbaint_t blkcnt; - - /** - * @read: - Read from slice - * - * @read.bm: Blkmap to which this slice belongs - * @read.bms: This slice - * @read.blknr: Start block number to read from - * @read.blkcnt: Number of blocks to read - * @read.buffer: Buffer to store read data to - */ - ulong (*read)(struct blkmap *bm, struct blkmap_slice *bms, - lbaint_t blknr, lbaint_t blkcnt, void *buffer); - - /** - * @write: - Write to slice - * - * @write.bm: Blkmap to which this slice belongs - * @write.bms: This slice - * @write.blknr: Start block number to write to - * @write.blkcnt: Number of blocks to write - * @write.buffer: Data to be written - */ - ulong (*write)(struct blkmap *bm, struct blkmap_slice *bms, - lbaint_t blknr, lbaint_t blkcnt, const void *buffer); - - /** - * @destroy: - Tear down slice - * - * @read.bm: Blkmap to which this slice belongs - * @read.bms: This slice - */ - void (*destroy)(struct blkmap *bm, struct blkmap_slice *bms); -}; +#include "blkmap_internal.h" static bool blkmap_slice_contains(struct blkmap_slice *bms, lbaint_t blknr) { @@ -92,7 +41,7 @@ static bool blkmap_slice_available(struct blkmap *bm, struct blkmap_slice *new) return true; } -static int blkmap_slice_add(struct blkmap *bm, struct blkmap_slice *new) +int blkmap_slice_add(struct blkmap *bm, struct blkmap_slice *new) { struct blk_desc *bd = dev_get_uclass_plat(bm->blk); struct list_head *insert = &bm->slices; @@ -293,155 +242,6 @@ 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/drivers/block/blkmap_crypt.c b/drivers/block/blkmap_crypt.c new file mode 100644 index 00000000000..bc77ddc2751 --- /dev/null +++ b/drivers/block/blkmap_crypt.c @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2025 Canonical Ltd + * Author: Simon Glass <simon.glass@canonical.com> + * + * LUKS encryption/decryption support + */ + +#include <blk.h> +#include <blkmap.h> +#include <dm.h> +#include <malloc.h> +#include <memalign.h> +#include <uboot_aes.h> +#include <mbedtls/aes.h> +#include <dm/device-internal.h> +#include <linux/string.h> +#include "blkmap_internal.h" + +/** + * struct blkmap_crypt - Encrypted mapping + * + * Data associated with an encrypted region of a block device (e.g., LUKS). + * Provides on-the-fly decryption of data using AES-CBC or AES-XTS modes. + * + * @slice: Common slice data (must be first member) + * @blk: Underlying block device containing encrypted data + * @blknr: Start block of the underlying block device + * @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 + * @sector_size: Sector size for IV calculation (typically 512 or 4096) + * @use_essiv: True if ESSIV mode is used for IV generation (CBC only) + * @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; + u32 sector_size; + 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; +} diff --git a/drivers/block/blkmap_internal.h b/drivers/block/blkmap_internal.h new file mode 100644 index 00000000000..07947c7ef0b --- /dev/null +++ b/drivers/block/blkmap_internal.h @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2023 Addiva Elektronik + * Author: Tobias Waldekranz <tobias@waldekranz.com> + * + * Internal blkmap structures and functions + */ + +#ifndef _BLKMAP_INTERNAL_H +#define _BLKMAP_INTERNAL_H + +#include <dm/lists.h> + +struct blkmap; + +/** + * struct blkmap_slice - Region mapped to a blkmap + * + * Common data for a region mapped to a blkmap, specialized by each + * map type. + * + * @node: List node used to associate this slice with a blkmap + * @blknr: Start block number of the mapping + * @blkcnt: Number of blocks covered by this mapping + */ +struct blkmap_slice { + struct list_head node; + + lbaint_t blknr; + lbaint_t blkcnt; + + /** + * @read: - Read from slice + * + * @read.bm: Blkmap to which this slice belongs + * @read.bms: This slice + * @read.blknr: Start block number to read from + * @read.blkcnt: Number of blocks to read + * @read.buffer: Buffer to store read data to + */ + ulong (*read)(struct blkmap *bm, struct blkmap_slice *bms, + lbaint_t blknr, lbaint_t blkcnt, void *buffer); + + /** + * @write: - Write to slice + * + * @write.bm: Blkmap to which this slice belongs + * @write.bms: This slice + * @write.blknr: Start block number to write to + * @write.blkcnt: Number of blocks to write + * @write.buffer: Data to be written + */ + ulong (*write)(struct blkmap *bm, struct blkmap_slice *bms, + lbaint_t blknr, lbaint_t blkcnt, const void *buffer); + + /** + * @destroy: - Tear down slice + * + * @read.bm: Blkmap to which this slice belongs + * @read.bms: This slice + */ + void (*destroy)(struct blkmap *bm, struct blkmap_slice *bms); +}; + +/** + * blkmap_slice_add() - Add a slice to a blkmap + * + * @bm: Blkmap to add the slice to + * @new: New slice to add + * Returns: 0 on success, negative error code on failure + */ +int blkmap_slice_add(struct blkmap *bm, struct blkmap_slice *new); + +#endif /* _BLKMAP_INTERNAL_H */ -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> In preparation for adding support for a new algorithm, move the decryption part of blkmap_crypt_read() into its own function. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- drivers/block/blkmap_crypt.c | 67 ++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 25 deletions(-) diff --git a/drivers/block/blkmap_crypt.c b/drivers/block/blkmap_crypt.c index bc77ddc2751..2de6be23662 100644 --- a/drivers/block/blkmap_crypt.c +++ b/drivers/block/blkmap_crypt.c @@ -45,37 +45,17 @@ struct blkmap_crypt { u8 essiv_key[32]; }; -static ulong blkmap_crypt_read(struct blkmap *bm, struct blkmap_slice *bms, - lbaint_t blknr, lbaint_t blkcnt, void *buffer) +static ulong crypt_read_cbc(struct blkmap *bm, struct blkmap_crypt *bmc, + lbaint_t blknr, lbaint_t blkcnt, + u8 *encrypted_buf, 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]; + u8 *dest = buffer; 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); @@ -116,11 +96,48 @@ static ulong blkmap_crypt_read(struct blkmap *bm, struct blkmap_slice *bms, dest + i * bd->blksz, bd->blksz / AES_BLOCK_LENGTH); } - free(encrypted_buf); return blkcnt; } +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 *src_bd = dev_get_uclass_plat(bmc->blk); + lbaint_t src_blknr, blocks_read; + u8 *encrypted_buf; + ulong result; + + /* 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; + } + + if (blknr == 0 && blkcnt >= 1) { + log_debug("First 32 bytes of ENCRYPTED data:\n"); + log_debug_hex("", encrypted_buf, 32); + } + + result = crypt_read_cbc(bm, bmc, blknr, blkcnt, encrypted_buf, buffer); + free(encrypted_buf); + + return result; +} + static void blkmap_crypt_destroy(struct blkmap *bm, struct blkmap_slice *bms) { struct blkmap_crypt *bmc = container_of(bms, struct blkmap_crypt, slice); -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add support for AES-XTS cipher mode in addition to the existing AES-CBC-ESSIV support. This is the default cipher for LUKS2 volumes. The cipher mode (CBC/XTS) is obtained from the LUKS1 cipher_mode or LUKS2 encryption metadata. XTS mode uses 512-byte block numbers for IV generation (plain64), matching dm-crypt behavior. LUKS2 typically uses 4096-byte sectors for XTS encryption but the IV is based on 512-byte block numbers. Fix the blkmap-size calculation to exclude the LUKS header/payload offset. Update the LUKSv2 test to check reading a file. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- drivers/block/blkmap_crypt.c | 205 +++++++++++++++++++++++++++++++++++ drivers/block/luks.c | 36 +++++- include/blkmap.h | 13 ++- include/luks.h | 2 +- test/boot/luks.c | 9 +- 5 files changed, 252 insertions(+), 13 deletions(-) diff --git a/drivers/block/blkmap_crypt.c b/drivers/block/blkmap_crypt.c index 2de6be23662..ff01768e05a 100644 --- a/drivers/block/blkmap_crypt.c +++ b/drivers/block/blkmap_crypt.c @@ -29,6 +29,7 @@ * @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 + * @cipher_mode: Cipher mode (CBC or XTS) * @sector_size: Sector size for IV calculation (typically 512 or 4096) * @use_essiv: True if ESSIV mode is used for IV generation (CBC only) * @essiv_key: ESSIV key (SHA256 hash of master key) @@ -40,11 +41,202 @@ struct blkmap_crypt { u8 master_key[128]; u32 key_size; u32 payload_offset; + enum blkmap_crypt_mode cipher_mode; u32 sector_size; bool use_essiv; u8 essiv_key[32]; }; +/** + * process_xts_sector() - Read, decrypt and copy one XTS sector + * + * This reads an encrypted sector from disk, decrypts it using AES-XTS with + * the plain64 IV mode, and copies the requested portion to the output buffer. + * Handles partial sector reads at the start and end of the requested range. + * + * @bmc: Blkmap crypt context with key and device info + * @ctx: Initialized AES-XTS context + * @cur_sector: Current XTS sector number being processed + * @start_sector: First XTS sector in the requested range + * @end_sector: Last XTS sector in the requested range + * @blks_per_sect: Number of 512-byte blocks per XTS sector + * @offset_in_first_sector: Byte offset within first sector to start copying + * @blkcnt: Total number of blocks requested + * @buf: Buffer for reading/decrypting one full sector + * @dest: Output buffer for decrypted data + * @blksz: Block-device block size (typically 512 bytes) + * @blocks_donep: Count of blocks copied so far (updated) + * Return: 0 on success, -EIO on read failure, other negative on decrypt failure + */ +static int process_xts_sector(struct blkmap_crypt *bmc, + mbedtls_aes_xts_context *ctx, lbaint_t cur_sector, + lbaint_t start_sector, lbaint_t end_sector, + uint blks_per_sect, uint offset_in_first_sector, + lbaint_t blkcnt, u8 *buf, u8 *dest, uint blksz, + lbaint_t *blocks_donep) +{ + lbaint_t start_blk = cur_sector * blks_per_sect; + lbaint_t src_blk = bmc->blknr + bmc->payload_offset + start_blk; + uint copy_offset = 0; + lbaint_t iv_sector; + u8 data_unit[16]; + uint copy_len; + lbaint_t j; + int ret; + + log_debug("XTS: cur_sector=%lu bmc->blknr=%lu bmc->payload_offset=%u src_blk=%lu\n", + cur_sector, bmc->blknr, bmc->payload_offset, src_blk); + + /* Read entire sector from disk */ + if (blk_read(bmc->blk, src_blk, blks_per_sect, buf) != + blks_per_sect) { + log_err("Failed to read sector %lu\n", cur_sector); + return -EIO; + } + + /* + * Prepare data_unit (IV) for XTS decryption. + * For plain64 IV mode, the IV is the 512-byte sector number, + * not the larger XTS sector number. This matches dm-crypt behavior. + */ + iv_sector = start_blk; + memset(data_unit, '\0', sizeof(data_unit)); + for (j = 0; j < 8; j++) + data_unit[j] = (iv_sector >> (j * 8)) & 0xff; + + /* Decrypt entire sector */ + ret = mbedtls_aes_crypt_xts(ctx, MBEDTLS_AES_DECRYPT, bmc->sector_size, + data_unit, buf, buf); + if (ret) { + log_err("XTS decrypt sector %lu failed: %d\n", cur_sector, ret); + return ret; + } + + /* Calculate which portion of this sector to copy */ + if (cur_sector == start_sector) + copy_offset = offset_in_first_sector; + + if (cur_sector == end_sector) { + /* Last sector: copy only up to the end of requested data */ + uint remaining = (blkcnt - *blocks_donep) * blksz; + + copy_len = remaining; + } else { + /* Not the last sector: copy from offset to end of sector */ + copy_len = bmc->sector_size - copy_offset; + } + + /* Copy decrypted data to output buffer */ + memcpy(dest + *blocks_donep * blksz, buf + copy_offset, copy_len); + *blocks_donep += copy_len / blksz; + + return 0; +} + +/** + * crypt_read_xts() - Decrypt data using AES-XTS cipher mode + * + * Decrypts blocks from an encrypted device using AES-XTS with plain64 IV mode. + * Handles requests that span multiple XTS sectors and partial sector reads. + * The IV for each XTS sector is the 512-byte block number (not the larger + * XTS sector number), matching dm-crypt's plain64 IV generation. + * + * @bm: Blkmap device context + * @bmc: Blkmap crypt context with encryption parameters + * @blknr: Starting block number (relative to decrypted device) + * @blkcnt: Number of blocks to read + * @buffer: Output buffer for decrypted data + * Return: number of blocks successfully decrypted, or negative error code + * (-ENOMEM if buffer allocation failed, -EINVAL if key setup failed, + * -EIO or other negative on sector read/decrypt failure) + */ +static ulong crypt_read_xts(struct blkmap *bm, struct blkmap_crypt *bmc, + lbaint_t blknr, lbaint_t blkcnt, void *out_buf) +{ + struct blk_desc *bd = dev_get_uclass_plat(bm->blk); + lbaint_t start_sector, end_sector, cur_sect; + uint offset_in_first_sector; + mbedtls_aes_xts_context ctx; + lbaint_t blocks_done; + uint blks_per_sect; + u8 *buf; + int ret; + + blks_per_sect = bmc->sector_size / bd->blksz; + + log_debug("key_size=%u blkcnt=%lu\n", bmc->key_size, blkcnt); + log_debug("XTS: sector_size=%u blocks_per_sector=%u\n", + bmc->sector_size, blks_per_sect); + log_debug("Master key (all %u bytes):\n", bmc->key_size); + log_debug_hex("", bmc->master_key, bmc->key_size); + + /* Calculate which encryption sectors we need */ + start_sector = blknr / blks_per_sect; + end_sector = (blknr + blkcnt - 1) / blks_per_sect; + offset_in_first_sector = (blknr % blks_per_sect) * bd->blksz; + + log_debug("XTS: blknr=%lu blkcnt=%lu start_sector=%lu end_sector=%lu offset=%u\n", + blknr, blkcnt, start_sector, end_sector, + offset_in_first_sector); + + /* Allocate buffer for one full sector */ + buf = malloc_cache_aligned(bmc->sector_size); + if (!buf) { + log_err("Failed to allocate sector buffer\n"); + return -ENOMEM; + } + + mbedtls_aes_xts_init(&ctx); + ret = mbedtls_aes_xts_setkey_dec(&ctx, bmc->master_key, + bmc->key_size * 8); + if (ret) { + log_err("XTS setkey_dec failed: %d\n", ret); + mbedtls_aes_xts_free(&ctx); + free(buf); + return -EINVAL; + } + + /* Process each sector */ + blocks_done = 0; + for (cur_sect = start_sector; cur_sect <= end_sector; cur_sect++) { + ret = process_xts_sector(bmc, &ctx, cur_sect, start_sector, + end_sector, blks_per_sect, + offset_in_first_sector, blkcnt, + buf, out_buf, bd->blksz, &blocks_done); + if (ret) { + mbedtls_aes_xts_free(&ctx); + free(buf); + return ret; + } + } + + free(buf); + mbedtls_aes_xts_free(&ctx); + + log_debug("XTS decryption completed successfully for %lu blocks\n", blkcnt); + if (blknr == 0 && blkcnt >= 1) { + log_debug("First 32 bytes of decrypted data:\n"); + log_debug_hex("", out_buf, 32); + } + + return blkcnt; +} + +/** + * crypt_read_cbc() - Decrypt data using AES-CBC cipher mode + * + * Decrypts blocks from an encrypted device using AES-CBC. Supports both + * plain64 mode (IV = sector number) and ESSIV mode (IV = AES_encrypt(sector + * number, SHA256(master_key))). Used for LUKS1 volumes. + * + * @bm: Blkmap device context + * @bmc: Blkmap crypt context with encryption parameters and ESSIV key + * @blknr: Starting block number (relative to decrypted device) + * @blkcnt: Number of blocks to decrypt + * @encrypted_buf: Buffer containing encrypted data (already read from disk) + * @buffer: Output buffer for decrypted data + * Return: number of blocks successfully decrypted + */ static ulong crypt_read_cbc(struct blkmap *bm, struct blkmap_crypt *bmc, lbaint_t blknr, lbaint_t blkcnt, u8 *encrypted_buf, void *buffer) @@ -132,6 +324,16 @@ static ulong blkmap_crypt_read(struct blkmap *bm, struct blkmap_slice *bms, log_debug_hex("", encrypted_buf, 32); } + if (bmc->cipher_mode == BLKMAP_CRYPT_MODE_XTS) { + result = crypt_read_xts(bm, bmc, blknr, blkcnt, buffer); + /* XTS reads its own data, so free encrypted_buf early */ + free(encrypted_buf); + /* Check for error - result will be negative on failure */ + if ((long)result < 0) + return 0; + return result; + } + result = crypt_read_cbc(bm, bmc, blknr, blkcnt, encrypted_buf, buffer); free(encrypted_buf); @@ -150,6 +352,7 @@ static void blkmap_crypt_destroy(struct blkmap *bm, struct blkmap_slice *bms) 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, + enum blkmap_crypt_mode cipher_mode, u32 sector_size, bool use_essiv, const u8 *essiv_key) { struct blkmap *bm = dev_get_plat(dev); @@ -167,6 +370,8 @@ int blkmap_map_crypt(struct udevice *dev, lbaint_t blknr, lbaint_t blkcnt, bmc->blknr = lblknr; bmc->key_size = key_size; bmc->payload_offset = payload_offset; + bmc->cipher_mode = cipher_mode; + bmc->sector_size = sector_size; bmc->use_essiv = use_essiv; memcpy(bmc->master_key, master_key, key_size); diff --git a/drivers/block/luks.c b/drivers/block/luks.c index c7e5a3da154..923932c0dad 100644 --- a/drivers/block/luks.c +++ b/drivers/block/luks.c @@ -596,6 +596,7 @@ int luks_create_blkmap(struct udevice *blk, struct disk_partition *pinfo, struct udevice **blkmapp) { u8 essiv_key[SHA256_SUM_LEN]; /* SHA-256 output */ + enum blkmap_crypt_mode cipher_mode; lbaint_t decrypted_size; struct luks1_phdr *hdr; struct luks2_hdr *hdr2; @@ -603,6 +604,7 @@ int luks_create_blkmap(struct udevice *blk, struct disk_partition *pinfo, struct udevice *dev; uint payload_offset; int ret, version; + u32 sector_size; bool use_essiv; if (!blk || !pinfo || !master_key || !label || !blkmapp) @@ -698,12 +700,26 @@ int luks_create_blkmap(struct udevice *blk, struct disk_partition *pinfo, /* Convert byte offset to sectors */ payload_offset = segment_offset / desc->blksz; - /* Check if ESSIV mode is used */ + /* Parse cipher mode from encryption string */ encryption = ofnode_read_string(segment0_node, "encryption"); - if (encryption) + if (encryption) { use_essiv = strstr(encryption, "essiv"); - else + /* Check if XTS mode is used */ + if (strstr(encryption, "xts")) + cipher_mode = BLKMAP_CRYPT_MODE_XTS; + else + cipher_mode = BLKMAP_CRYPT_MODE_CBC; + } else { use_essiv = false; + cipher_mode = BLKMAP_CRYPT_MODE_CBC; + } + + /* Read sector_size if present */ + if (ofnode_read_u32(segment0_node, "sector_size", §or_size)) { + /* If not found, default to 512 */ + sector_size = 512; + } + log_debug("LUKS2: sector_size=%u\n", sector_size); abuf_uninit(&fdt_buf); free(json_data); @@ -711,11 +727,20 @@ int luks_create_blkmap(struct udevice *blk, struct disk_partition *pinfo, /* LUKS1 */ hdr = (struct luks1_phdr *)buf; - /* Check if ESSIV mode is used */ + /* Parse cipher mode from cipher_mode string */ use_essiv = strstr(hdr->cipher_mode, "essiv"); + /* Check if XTS mode is used */ + if (strstr(hdr->cipher_mode, "xts")) + cipher_mode = BLKMAP_CRYPT_MODE_XTS; + else + cipher_mode = BLKMAP_CRYPT_MODE_CBC; /* Get payload offset */ payload_offset = be32_to_cpu(hdr->payload_offset); + + /* LUKS1 always uses 512-byte sectors */ + sector_size = 512; + log_debug("LUKS1: sector_size=%u\n", sector_size); } /* Create blkmap device */ @@ -747,7 +772,8 @@ int luks_create_blkmap(struct udevice *blk, struct disk_partition *pinfo, use_essiv); ret = blkmap_map_crypt(dev, 0, decrypted_size, blk, pinfo->start, master_key, key_size, payload_offset, - use_essiv, use_essiv ? essiv_key : NULL); + cipher_mode, sector_size, use_essiv, + use_essiv ? essiv_key : NULL); if (ret) { log_debug("failed to map encrypted partition\n"); blkmap_destroy(dev); diff --git a/include/blkmap.h b/include/blkmap.h index a0f46748b92..8a664edc33f 100644 --- a/include/blkmap.h +++ b/include/blkmap.h @@ -9,6 +9,14 @@ #include <dm/lists.h> +/** + * enum blkmap_crypt_mode - Cipher mode for encrypted block devices + */ +enum blkmap_crypt_mode { + BLKMAP_CRYPT_MODE_CBC = 0, /* AES-CBC */ + BLKMAP_CRYPT_MODE_XTS = 1, /* AES-XTS */ +}; + /** * struct blkmap - Block map * @@ -80,13 +88,16 @@ int blkmap_map_pmem(struct udevice *dev, lbaint_t blknr, lbaint_t blkcnt, * @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 + * @cipher_mode: Cipher mode (CBC or XTS) + * @sector_size: Sector size for IV calculation (typically 512 or 4096) + * @use_essiv: True to use ESSIV mode, false for plain64 mode (CBC only) * @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, + enum blkmap_crypt_mode cipher_mode, u32 sector_size, bool use_essiv, const u8 *essiv_key); /** diff --git a/include/luks.h b/include/luks.h index 4f65f3029f8..6c39db7a2d2 100644 --- a/include/luks.h +++ b/include/luks.h @@ -141,7 +141,7 @@ 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. + * Supports LUKS1 (PBKDF2, AES-CBC/XTS) and LUKS2 (PBKDF2/Argon2, AES-XTS). * * @blk: Block device * @pinfo: Partition information diff --git a/test/boot/luks.c b/test/boot/luks.c index ec95b241d8a..6bf613f3b08 100644 --- a/test/boot/luks.c +++ b/test/boot/luks.c @@ -287,12 +287,9 @@ static int bootstd_test_luks2_unlock(struct unit_test_state *uts) desc = blk_get_devnum_by_uclass_idname("blkmap", 0); ut_assertnonnull(desc); - /* at present this fails due to incorrect decryption */ - if (0) { - ut_assertok(fs_set_blk_dev_with_part(desc, 0)); - ut_assertok(fs_size("/bin/bash", &file_size)); - ut_asserteq(5, file_size); - } + ut_assertok(fs_set_blk_dev_with_part(desc, 0)); + ut_assertok(fs_size("/bin/bash", &file_size)); + ut_asserteq(5, file_size); /* Test unlocking with wrong passphrase */ ut_asserteq(1, run_command("luks unlock mmc c:2 wrongpass", 0)); -- 2.43.0
participants (1)
-
Simon Glass