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