[PATCH 00/14] luks: Integrate support for a TKey
This series illustrates how to use a Tillitis TKey to unlock an encrypted disk. This has the advantage that the key depends on a physical key in the user's posession as well as the usual passphrase. The TKey handles the key derivation, so this series includes logic to skip that step when a TKey is used. The 'luks unlock' command provides a -t flag to use a TKey. It also provides a small pytest fix to ease conflicts with Labgrid integration. Simon Glass (14): test/py: Avoid pulling in Labgrid with tests luks: Make essiv_decrypt() a shared function luks: Tidy up debugging of unlock luks: Update try_keyslot() to allow a bytestring luks: Update unlock_luks2() to take binary passphrase luks: Update luks_unlock() to take binary passphrase luks: Extract PBKDF2 key derivation into separate function luks: Move key derivation to the caller of try_keyslot() luks: Split LUKSv1 unlock code into a separate function luks: Support a pre-derived key with LUKSv1 luks: Support a pre-derived key with LUKSv2 luks: Support a pre-derived key luks: Check for out-of-memory with Argon2 luks: Support disk unlock using a TKey cmd/luks.c | 94 +++++++++- doc/usage/cmd/luks.rst | 36 +++- drivers/block/luks.c | 316 ++++++++++++++++++++-------------- drivers/block/luks2.c | 141 ++++++--------- drivers/block/luks_internal.h | 28 ++- include/luks.h | 4 +- test/boot/luks.c | 4 +- test/py/pytest.ini | 1 + 8 files changed, 384 insertions(+), 240 deletions(-) -- 2.43.0 base-commit: 826b435241a60310f663a88b148912a9ab727b76 branch: secg
When Labgrid is installed as an editable package (pip install -e), pytest automatically loads it as a plugin. This can interfere with U-Boot's test suite. Disable automatic loading of the Labgrid pytest plugin to prevent conflicts. Signed-off-by: Simon Glass <simon.glass@canonical.com> --- test/py/pytest.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/test/py/pytest.ini b/test/py/pytest.ini index 361be0178ee..0a0268ec247 100644 --- a/test/py/pytest.ini +++ b/test/py/pytest.ini @@ -5,6 +5,7 @@ # Static configuration data for pytest. pytest reads this at startup time. [pytest] +addopts = -p no:labgrid markers = boardspec: U-Boot: Describes the set of boards a test can/can't run on. buildconfigspec: U-Boot: Describes Kconfig/config-header constraints. -- 2.43.0
Both luks.c and luks2.c have similar implementations of essiv_decrypt(). Drop the version in the later to reduce code duplication. Drop the duplicate function comments while we are here, since exported functions should have the information in the header file. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- drivers/block/luks.c | 48 ++------------------------ drivers/block/luks2.c | 63 ----------------------------------- drivers/block/luks_internal.h | 18 ++++++++++ 3 files changed, 20 insertions(+), 109 deletions(-) diff --git a/drivers/block/luks.c b/drivers/block/luks.c index 923932c0dad..a7f5b436e12 100644 --- a/drivers/block/luks.c +++ b/drivers/block/luks.c @@ -195,20 +195,6 @@ static int af_hash(struct hash_algo *algo, size_t key_size, u8 *block_buf) 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 - */ int af_merge(const u8 *src, u8 *dst, size_t key_size, uint stripes, const char *hash_spec) { @@ -250,23 +236,8 @@ int af_merge(const u8 *src, u8 *dst, size_t key_size, uint stripes, return 0; } -/** - * 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) +void essiv_decrypt(const 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]; @@ -576,21 +547,6 @@ out: 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 - * @blkmapp: 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 **blkmapp) diff --git a/drivers/block/luks2.c b/drivers/block/luks2.c index 4720f9d92ce..6836c372de2 100644 --- a/drivers/block/luks2.c +++ b/drivers/block/luks2.c @@ -454,69 +454,6 @@ out: return ret; } -/** - * 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]; - u32 num_sectors = km_blocks; - u8 iv[AES_BLOCK_LENGTH]; - 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); - } -} - /** * decrypt_km_xts() - Decrypt key material using XTS mode * diff --git a/drivers/block/luks_internal.h b/drivers/block/luks_internal.h index 14d3839fe6a..3bc572cdfd9 100644 --- a/drivers/block/luks_internal.h +++ b/drivers/block/luks_internal.h @@ -27,6 +27,24 @@ int af_merge(const u8 *src, u8 *dst, size_t key_size, uint stripes, const char *hash_spec); +/** + * 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. Used by both LUKS1 and LUKS2. + * + * @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 + */ +void essiv_decrypt(const u8 *derived_key, uint key_size, u8 *expkey, u8 *km, + u8 *split_key, uint km_blocks, uint blksz); + /** * unlock_luks2() - Unlock a LUKS2 partition with a passphrase * -- 2.43.0
Name the derived key as such to avoid confusion. Fix a typo in nearby log_debug() statement. Update essiv_decrypt() to have a const * for its first argument. Signed-off-by: Simon Glass <simon.glass@canonical.com> --- drivers/block/luks.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/block/luks.c b/drivers/block/luks.c index a7f5b436e12..94139b4f56d 100644 --- a/drivers/block/luks.c +++ b/drivers/block/luks.c @@ -358,7 +358,7 @@ static int try_keyslot(struct udevice *blk, struct disk_partition *pinfo, /* 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); + log_debug_hex("derived_key", derived_key, key_size); aes_expand_key(derived_key, key_size * 8, expkey); log_debug_hex("expanded key [0-15]:", expkey, 16); @@ -405,7 +405,7 @@ static int try_keyslot(struct udevice *blk, struct disk_partition *pinfo, /* Check if the digest matches */ if (!memcmp(key_digest, hdr->mk_digest, LUKS_DIGESTSIZE)) { - log_debug("Uunlocked with key slot %d\n", slot_idx); + log_debug("Unlocked with key slot %d\n", slot_idx); return 0; } log_debug("key slot %d: wrong passphrase\n", slot_idx); -- 2.43.0
In preparation for supporting unlock using binary data, update try_keyslot() to accept an array of bytes instead of a string. Signed-off-by: Simon Glass <simon.glass@canonical.com> --- drivers/block/luks.c | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/drivers/block/luks.c b/drivers/block/luks.c index 94139b4f56d..6a1e6f9a3ca 100644 --- a/drivers/block/luks.c +++ b/drivers/block/luks.c @@ -295,6 +295,7 @@ void essiv_decrypt(const u8 *derived_key, uint key_size, u8 *expkey, * @hdr: LUKS header * @slot_idx: Key slot index to try * @pass: Passphrase to try + * @pass_len: Length of passphrase * @md_type: Hash algorithm type * @key_size: Size of the key * @derived_key: Buffer for derived key (key_size bytes) @@ -308,9 +309,10 @@ void essiv_decrypt(const u8 *derived_key, uint key_size, u8 *expkey, */ static int try_keyslot(struct udevice *blk, struct disk_partition *pinfo, struct luks1_phdr *hdr, int slot_idx, - const char *pass, mbedtls_md_type_t md_type, - uint key_size, u8 *derived_key, u8 *km, uint km_blocks, - u8 *split_key, u8 *candidate_key) + const u8 *pass, size_t pass_len, + 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 iters, km_offset, stripes, split_key_size; @@ -324,7 +326,7 @@ static int try_keyslot(struct udevice *blk, struct disk_partition *pinfo, if (be32_to_cpu(slot->active) != LUKS_KEY_ENABLED) return -ENOENT; - log_debug("trying key slot %d...\n", slot_idx); + log_debug("trying key slot %d (pass len=%zu)...\n", slot_idx, pass_len); iters = be32_to_cpu(slot->iterations); km_offset = be32_to_cpu(slot->key_material_offset); @@ -332,14 +334,13 @@ static int try_keyslot(struct udevice *blk, struct disk_partition *pinfo, split_key_size = key_size * stripes; /* Derive key from passphrase using PBKDF2 */ - log_debug("PBKDF2(pass '%s'[len %zu], ", pass, strlen(pass)); + log_debug("PBKDF2(pass len=%zu, ", pass_len); log_debug_hex("salt[0-7]", (u8 *)slot->salt, 8); log_debug("iter %u, keylen %u)\n", iters, key_size); - ret = mbedtls_pkcs5_pbkdf2_hmac_ext(md_type, (const u8 *)pass, - strlen(pass), + ret = mbedtls_pkcs5_pbkdf2_hmac_ext(md_type, pass, pass_len, (const u8 *)slot->salt, - LUKS_SALTSIZE, iters, key_size, - derived_key); + LUKS_SALTSIZE, iters, + key_size, derived_key); if (ret) { log_debug("PBKDF2 failed: %d\n", ret); return -EPROTO; @@ -360,7 +361,9 @@ static int try_keyslot(struct udevice *blk, struct disk_partition *pinfo, log_debug("expand key with key_size*8 %u bits\n", key_size * 8); log_debug_hex("derived_key", derived_key, key_size); + /* Decrypt key material */ 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 */ @@ -369,10 +372,8 @@ static int try_keyslot(struct udevice *blk, struct disk_partition *pinfo, km_blocks, desc->blksz); } else { /* Plain CBC with zero IV */ + log_debug("using plain CBC mode\n"); 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); } @@ -514,9 +515,10 @@ int luks_unlock(struct udevice *blk, struct disk_partition *pinfo, /* Try each key slot */ for (i = 0; i < LUKS_NUMKEYS; i++) { - ret = try_keyslot(blk, pinfo, hdr, i, pass, md_type, - *key_size, derived_key, km, km_blocks, - split_key, candidate_key); + ret = try_keyslot(blk, pinfo, hdr, i, (const u8 *)pass, + strlen(pass), md_type, *key_size, + derived_key, km, km_blocks, split_key, + candidate_key); if (!ret) { /* Successfully unlocked */ -- 2.43.0
Update unlock_luks2() and related functions to accept a binary passphrase instead of a string passphrase. This will allow unlocking using hashed data. For now this is internal to the luks implementation. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- drivers/block/luks.c | 3 ++- drivers/block/luks2.c | 41 ++++++++++++++++++++--------------- drivers/block/luks_internal.h | 4 +++- 3 files changed, 28 insertions(+), 20 deletions(-) diff --git a/drivers/block/luks.c b/drivers/block/luks.c index 6a1e6f9a3ca..93b50dd105a 100644 --- a/drivers/block/luks.c +++ b/drivers/block/luks.c @@ -452,7 +452,8 @@ int luks_unlock(struct udevice *blk, struct disk_partition *pinfo, version = be16_to_cpu(*(__be16 *)(buffer + LUKS_MAGIC_LEN)); if (version == LUKS_VERSION_2) - return unlock_luks2(blk, pinfo, pass, master_key, key_size); + return unlock_luks2(blk, pinfo, (const u8 *)pass, strlen(pass), + master_key, key_size); if (version != LUKS_VERSION_1) { log_debug("unsupported LUKS version %d\n", version); diff --git a/drivers/block/luks2.c b/drivers/block/luks2.c index 6836c372de2..57e6b9f37d9 100644 --- a/drivers/block/luks2.c +++ b/drivers/block/luks2.c @@ -586,8 +586,9 @@ static int decrypt_km_cbc(u8 *derived_key, uint key_size, const char *encrypt, * Return: 0 on success, negative error code on failure */ static int try_keyslot_pbkdf2(struct udevice *blk, struct disk_partition *pinfo, - const struct luks2_keyslot *ks, const char *pass, - mbedtls_md_type_t md_type, u8 *cand_key) + const struct luks2_keyslot *ks, const u8 *pass, + size_t pass_len, mbedtls_md_type_t md_type, + u8 *cand_key) { struct blk_desc *desc = dev_get_uclass_plat(blk); int ret, km_blocks, size; @@ -597,10 +598,10 @@ static int try_keyslot_pbkdf2(struct udevice *blk, struct disk_partition *pinfo, log_debug("LUKS2: trying keyslot with %u iters\n", ks->kdf.iters); /* Derive key from passphrase */ - ret = mbedtls_pkcs5_pbkdf2_hmac_ext(md_type, (const u8 *)pass, - strlen(pass), ks->kdf.salt, - ks->kdf.salt_len, ks->kdf.iters, - ks->area.key_size, derived_key); + ret = mbedtls_pkcs5_pbkdf2_hmac_ext(md_type, pass, pass_len, + ks->kdf.salt, ks->kdf.salt_len, + ks->kdf.iters, ks->area.key_size, + derived_key); if (ret) return -EPROTO; @@ -655,8 +656,8 @@ out: /* Unlock using Argon2 keyslot */ static int try_keyslot_argon2(struct udevice *blk, struct disk_partition *pinfo, - const struct luks2_keyslot *ks, const char *pass, - u8 *cand_key) + const struct luks2_keyslot *ks, const u8 *pass, + size_t pass_len, u8 *cand_key) { struct blk_desc *desc = dev_get_uclass_plat(blk); int ret, km_blocks, size; @@ -667,11 +668,11 @@ static int try_keyslot_argon2(struct udevice *blk, struct disk_partition *pinfo, ks->kdf.time, ks->kdf.memory, ks->kdf.cpus); /* Derive key from passphrase using Argon2id */ - log_debug("LUKS2 Argon2: passphrase='%s', t=%u, m=%u, p=%u, saltlen=%d, keylen=%u\n", - pass, ks->kdf.time, ks->kdf.memory, ks->kdf.cpus, + log_debug("LUKS2 Argon2: pass_len=%zu, t=%u, m=%u, p=%u, saltlen=%d, keylen=%u\n", + pass_len, ks->kdf.time, ks->kdf.memory, ks->kdf.cpus, ks->kdf.salt_len, ks->area.key_size); ret = argon2id_hash_raw(ks->kdf.time, ks->kdf.memory, ks->kdf.cpus, - pass, strlen(pass), ks->kdf.salt, + pass, pass_len, ks->kdf.salt, ks->kdf.salt_len, derived_key, ks->area.key_size); if (ret) { @@ -826,8 +827,9 @@ static int verify_master_key(const struct luks2_digest *digest, static int try_unlock_keyslot(struct udevice *blk, struct disk_partition *pinfo, ofnode keyslot_node, const struct luks2_digest *digest, - mbedtls_md_type_t md_type, const char *pass, - u8 *master_key, uint *key_sizep) + mbedtls_md_type_t md_type, const u8 *pass, + size_t pass_len, u8 *master_key, + uint *key_sizep) { struct luks2_keyslot keyslot; u8 cand_key[128]; @@ -846,12 +848,13 @@ static int try_unlock_keyslot(struct udevice *blk, struct disk_partition *pinfo, /* Try the keyslot using the appropriate KDF */ if (keyslot.kdf.type == LUKS2_KDF_PBKDF2) { log_debug("LUKS2: calling try_keyslot_pbkdf2\n"); - ret = try_keyslot_pbkdf2(blk, pinfo, &keyslot, pass, md_type, - cand_key); + ret = try_keyslot_pbkdf2(blk, pinfo, &keyslot, pass, pass_len, + md_type, cand_key); } else { /* Argon2 (already checked for CONFIG_ARGON2 support) */ log_debug("LUKS2: calling try_keyslot_argon2\n"); - ret = try_keyslot_argon2(blk, pinfo, &keyslot, pass, cand_key); + ret = try_keyslot_argon2(blk, pinfo, &keyslot, pass, pass_len, + cand_key); } log_debug("LUKS2: keyslot try returned %d\n", ret); @@ -874,7 +877,8 @@ static int try_unlock_keyslot(struct udevice *blk, struct disk_partition *pinfo, } int unlock_luks2(struct udevice *blk, struct disk_partition *pinfo, - const char *pass, u8 *master_key, uint *key_sizep) + const u8 *pass, size_t pass_len, u8 *master_key, + uint *key_sizep) { ofnode keyslots_node, keyslot_node; struct luks2_digest digest; @@ -892,7 +896,8 @@ int unlock_luks2(struct udevice *blk, struct disk_partition *pinfo, ret = -EACCES; ofnode_for_each_subnode(keyslot_node, keyslots_node) { ret = try_unlock_keyslot(blk, pinfo, keyslot_node, &digest, - md_type, pass, master_key, key_sizep); + md_type, pass, pass_len, master_key, + key_sizep); if (!ret) /* Successfully unlocked! */ break; diff --git a/drivers/block/luks_internal.h b/drivers/block/luks_internal.h index 3bc572cdfd9..6b0e41267ff 100644 --- a/drivers/block/luks_internal.h +++ b/drivers/block/luks_internal.h @@ -51,11 +51,13 @@ void essiv_decrypt(const u8 *derived_key, uint key_size, u8 *expkey, u8 *km, * @blk: Block device * @pinfo: Partition information * @pass: Passphrase to unlock the partition + * @pass_len: Length of the passphrase in bytes * @master_key: Buffer to receive the decrypted master key * @key_sizep: Returns the key size * Return: 0 on success, -ve on error */ int unlock_luks2(struct udevice *blk, struct disk_partition *pinfo, - const char *pass, u8 *master_key, uint *key_sizep); + const u8 *pass, size_t pass_len, u8 *master_key, + uint *key_sizep); #endif /* __LUKS_INTERNAL_H__ */ -- 2.43.0
Update luks_unlock() to accept a binary passphrase, to match the LUKS2 implementation. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- cmd/luks.c | 4 ++-- drivers/block/luks.c | 14 +++++++------- include/luks.h | 4 +++- test/boot/luks.c | 4 ++-- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/cmd/luks.c b/cmd/luks.c index c1e8035e685..defd987776d 100644 --- a/cmd/luks.c +++ b/cmd/luks.c @@ -88,8 +88,8 @@ static int do_luks_unlock(struct cmd_tbl *cmdtp, int flag, int argc, printf("Unlocking LUKS%d partition...\n", version); /* Unlock the partition to get the master key */ - ret = luks_unlock(dev_desc->bdev, &info, passphrase, master_key, - &key_size); + ret = luks_unlock(dev_desc->bdev, &info, (const u8 *)passphrase, + strlen(passphrase), master_key, &key_size); if (ret) { printf("Failed to unlock LUKS partition (err %dE)\n", ret); return CMD_RET_FAILURE; diff --git a/drivers/block/luks.c b/drivers/block/luks.c index 93b50dd105a..96180d39b4e 100644 --- a/drivers/block/luks.c +++ b/drivers/block/luks.c @@ -415,7 +415,8 @@ static int try_keyslot(struct udevice *blk, struct disk_partition *pinfo, } int luks_unlock(struct udevice *blk, struct disk_partition *pinfo, - const char *pass, u8 *master_key, u32 *key_size) + const u8 *pass, size_t pass_len, u8 *master_key, + u32 *key_size) { uint version, split_key_size, km_blocks, hdr_blocks; u8 *split_key, *derived_key; @@ -452,8 +453,8 @@ int luks_unlock(struct udevice *blk, struct disk_partition *pinfo, version = be16_to_cpu(*(__be16 *)(buffer + LUKS_MAGIC_LEN)); if (version == LUKS_VERSION_2) - return unlock_luks2(blk, pinfo, (const u8 *)pass, strlen(pass), - master_key, key_size); + return unlock_luks2(blk, pinfo, pass, pass_len, master_key, + key_size); if (version != LUKS_VERSION_1) { log_debug("unsupported LUKS version %d\n", version); @@ -516,10 +517,9 @@ int luks_unlock(struct udevice *blk, struct disk_partition *pinfo, /* Try each key slot */ for (i = 0; i < LUKS_NUMKEYS; i++) { - ret = try_keyslot(blk, pinfo, hdr, i, (const u8 *)pass, - strlen(pass), md_type, *key_size, - derived_key, km, km_blocks, split_key, - candidate_key); + ret = try_keyslot(blk, pinfo, hdr, i, pass, pass_len, md_type, + *key_size, derived_key, km, km_blocks, + split_key, candidate_key); if (!ret) { /* Successfully unlocked */ diff --git a/include/luks.h b/include/luks.h index 6c39db7a2d2..8826fa96507 100644 --- a/include/luks.h +++ b/include/luks.h @@ -146,6 +146,7 @@ int luks_show_info(struct udevice *blk, struct disk_partition *pinfo); * @blk: Block device * @pinfo: Partition information * @pass: Passphrase to unlock the partition + * @pass_len: Length of the passphrase in bytes * @master_key: Buffer to receive the decrypted master key * @key_size: Size of the master_key buffer * Return: 0 on success, @@ -157,7 +158,8 @@ int luks_show_info(struct udevice *blk, struct disk_partition *pinfo); * -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); + const u8 *pass, size_t pass_len, u8 *master_key, + u32 *key_size); /** * luks_create_blkmap() - Create a blkmap device for a LUKS partition diff --git a/test/boot/luks.c b/test/boot/luks.c index 6bf613f3b08..93cc8ce6681 100644 --- a/test/boot/luks.c +++ b/test/boot/luks.c @@ -274,8 +274,8 @@ static int bootstd_test_luks2_unlock(struct unit_test_state *uts) /* 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)); + ut_asserteq(-ENOENT, luks_unlock(desc->bdev, &info, (const u8 *)"test", + 4, master_key, &key_size)); /* Test unlocking partition 2 with correct passphrase */ ut_assertok(run_command("luks unlock mmc c:2 test", 0)); -- 2.43.0
Create a new derive_key_pbkdf2() function to handle key derivation, to allow this be called from other places and to reduce the size of try_keyslot() Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- drivers/block/luks.c | 56 ++++++++++++++++++++++++++++++++------------ 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/drivers/block/luks.c b/drivers/block/luks.c index 96180d39b4e..48f281ef77c 100644 --- a/drivers/block/luks.c +++ b/drivers/block/luks.c @@ -287,6 +287,42 @@ void essiv_decrypt(const u8 *derived_key, uint key_size, u8 *expkey, } } +/** + * derive_key_pbkdf2() - Derive key from passphrase using PBKDF2 + * + * @slot: LUKS keyslot containing salt and iteration count + * @pass: Passphrase + * @pass_len: Length of passphrase + * @md_type: Hash algorithm type + * @key_size: Size of the key to derive + * @derived_key: Buffer for derived key (key_size bytes) + * Return: 0 on success, -EPROTO on error + */ +static int derive_key_pbkdf2(struct luks1_keyslot *slot, const u8 *pass, + size_t pass_len, mbedtls_md_type_t md_type, + uint key_size, u8 *derived_key) +{ + uint iters = be32_to_cpu(slot->iterations); + int ret; + + /* Derive key from passphrase using PBKDF2 */ + log_debug("PBKDF2(pass len=%zu, ", pass_len); + log_debug_hex("salt[0-7]", (u8 *)slot->salt, 8); + log_debug("iter %u, keylen %u)\n", iters, key_size); + ret = mbedtls_pkcs5_pbkdf2_hmac_ext(md_type, pass, pass_len, + (const u8 *)slot->salt, + LUKS_SALTSIZE, iters, + 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); + + return 0; +} + /** * try_keyslot() - Unlock a LUKS key slot with a passphrase * @@ -315,7 +351,7 @@ static int try_keyslot(struct udevice *blk, struct disk_partition *pinfo, uint km_blocks, u8 *split_key, u8 *candidate_key) { struct luks1_keyslot *slot = &hdr->key_slot[slot_idx]; - uint iters, km_offset, stripes, split_key_size; + uint 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]; @@ -328,25 +364,15 @@ static int try_keyslot(struct udevice *blk, struct disk_partition *pinfo, log_debug("trying key slot %d (pass len=%zu)...\n", slot_idx, pass_len); - iters = 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 len=%zu, ", pass_len); - log_debug_hex("salt[0-7]", (u8 *)slot->salt, 8); - log_debug("iter %u, keylen %u)\n", iters, key_size); - ret = mbedtls_pkcs5_pbkdf2_hmac_ext(md_type, pass, pass_len, - (const u8 *)slot->salt, - LUKS_SALTSIZE, iters, - 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); + ret = derive_key_pbkdf2(slot, pass, pass_len, md_type, key_size, + derived_key); + if (ret) + return ret; /* Read encrypted key material */ ret = blk_read(blk, pinfo->start + km_offset, km_blocks, km); -- 2.43.0
Move the derive_key_pbkdf2() call from inside try_keyslot() to the caller, luks_unlock() With this change luks_unlock() deals with key derivation and try_keyslot() only handles the decryption part, using a supplied derived key. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- drivers/block/luks.c | 49 ++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/drivers/block/luks.c b/drivers/block/luks.c index 48f281ef77c..c23b6f50671 100644 --- a/drivers/block/luks.c +++ b/drivers/block/luks.c @@ -324,31 +324,27 @@ static int derive_key_pbkdf2(struct luks1_keyslot *slot, const u8 *pass, } /** - * try_keyslot() - Unlock a LUKS key slot with a passphrase + * try_keyslot() - Try to unlock a LUKS key slot with a derived key * * @blk: Block device * @pinfo: Partition information * @hdr: LUKS header * @slot_idx: Key slot index to try - * @pass: Passphrase to try - * @pass_len: Length of passphrase - * @md_type: Hash algorithm type + * @md_type: Hash algorithm type for master key verification * @key_size: Size of the key - * @derived_key: Buffer for derived key (key_size bytes) + * @derived_key: Pre-derived key from PBKDF2 (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 + * Return: 0 on success (correct key), -ve on error */ static int try_keyslot(struct udevice *blk, struct disk_partition *pinfo, struct luks1_phdr *hdr, int slot_idx, - const u8 *pass, size_t pass_len, - mbedtls_md_type_t md_type, - uint key_size, u8 *derived_key, u8 *km, - uint km_blocks, u8 *split_key, u8 *candidate_key) + mbedtls_md_type_t md_type, uint key_size, + const u8 *derived_key, u8 *km, uint km_blocks, + u8 *split_key, u8 *candidate_key) { struct luks1_keyslot *slot = &hdr->key_slot[slot_idx]; uint km_offset, stripes, split_key_size; @@ -358,22 +354,12 @@ static int try_keyslot(struct udevice *blk, struct disk_partition *pinfo, 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 (pass len=%zu)...\n", slot_idx, pass_len); + log_debug("trying key slot %d with derived key\n", slot_idx); 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 */ - ret = derive_key_pbkdf2(slot, pass, pass_len, md_type, key_size, - derived_key); - if (ret) - return ret; - /* Read encrypted key material */ ret = blk_read(blk, pinfo->start + km_offset, km_blocks, km); if (ret != km_blocks) { @@ -543,9 +529,22 @@ int luks_unlock(struct udevice *blk, struct disk_partition *pinfo, /* Try each key slot */ for (i = 0; i < LUKS_NUMKEYS; i++) { - ret = try_keyslot(blk, pinfo, hdr, i, pass, pass_len, md_type, - *key_size, derived_key, km, km_blocks, - split_key, candidate_key); + struct luks1_keyslot *slot = &hdr->key_slot[i]; + + /* Skip inactive slots */ + if (be32_to_cpu(slot->active) != LUKS_KEY_ENABLED) + continue; + + /* Derive key for this slot */ + ret = derive_key_pbkdf2(slot, pass, pass_len, md_type, + *key_size, derived_key); + if (ret) + continue; + + /* Try to unlock with the derived key */ + ret = try_keyslot(blk, pinfo, hdr, i, md_type, *key_size, + derived_key, km, km_blocks, split_key, + candidate_key); if (!ret) { /* Successfully unlocked */ -- 2.43.0
Move the LUKSv1-specific unlock logic from luks_unlock() into a new unlock_luks1() function, lining up with the structure used for LUKSv2. Also update unlock_luks1() to use a local key_size variable and only set the output parameter on success. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- drivers/block/luks.c | 153 ++++++++++++++++++++++++++++--------------- 1 file changed, 102 insertions(+), 51 deletions(-) diff --git a/drivers/block/luks.c b/drivers/block/luks.c index c23b6f50671..a3c86c5a197 100644 --- a/drivers/block/luks.c +++ b/drivers/block/luks.c @@ -323,6 +323,23 @@ static int derive_key_pbkdf2(struct luks1_keyslot *slot, const u8 *pass, return 0; } +/** + * unlock_luks1() - Unlock a LUKSv1 partition + * + * @blk: Block device + * @pinfo: Partition information + * @hdr: LUKS1 header (already read) + * @pass: Passphrase + * @pass_len: Length of passphrase + * @master_key: Buffer to receive master key + * @key_size: Output for key size + * + * Return: 0 on success, -ve on error + */ +static int unlock_luks1(struct udevice *blk, struct disk_partition *pinfo, + struct luks1_phdr *hdr, const u8 *pass, size_t pass_len, + u8 *master_key, u32 *key_size); + /** * try_keyslot() - Try to unlock a LUKS key slot with a derived key * @@ -426,62 +443,44 @@ static int try_keyslot(struct udevice *blk, struct disk_partition *pinfo, return -EACCES; } -int luks_unlock(struct udevice *blk, struct disk_partition *pinfo, - const u8 *pass, size_t pass_len, u8 *master_key, - u32 *key_size) +/** + * unlock_luks1() - Unlock a LUKSv1 partition + * + * Attempts to unlock a LUKSv1 encrypted partition by trying each active + * key slot with the provided passphrase. Uses PBKDF2 for key derivation + * and supports CBC cipher mode with optional ESSIV. + * + * @blk: Block device containing the partition + * @pinfo: Partition information + * @hdr: LUKSv1 header (already read and validated) + * @pass: Passphrase (binary data) + * @pass_len: Length of passphrase in bytes + * @master_key: Buffer to receive unlocked master key (min 128 bytes) + * @key_sizep: Output for master key size in bytes (set only on success) + * + * Return: 0 on success, -ve on error + */ +static int unlock_luks1(struct udevice *blk, struct disk_partition *pinfo, + struct luks1_phdr *hdr, const u8 *pass, size_t pass_len, + u8 *master_key, u32 *key_sizep) { - uint version, split_key_size, km_blocks, hdr_blocks; + uint split_key_size, km_blocks, key_size; u8 *split_key, *derived_key; struct hash_algo *hash_algo; u8 candidate_key[128], *km; mbedtls_md_type_t md_type; - struct luks1_phdr *hdr; struct blk_desc *desc; int i, ret; - if (!blk || !pinfo || !pass || !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_2) - return unlock_luks2(blk, pinfo, pass, pass_len, master_key, - key_size); - - if (version != LUKS_VERSION_1) { - log_debug("unsupported LUKS version %d\n", version); - 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) { + if (strncmp(hdr->cipher_mode, "cbc", 3)) { log_debug("only CBC mode is currently supported (got: %.32s)\n", hdr->cipher_mode); return -ENOTSUPP; @@ -495,11 +494,11 @@ int luks_unlock(struct udevice *blk, struct disk_partition *pinfo, } md_type = hash_mbedtls_type(hash_algo); - - *key_size = be32_to_cpu(hdr->key_bytes); + 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); @@ -511,13 +510,11 @@ int luks_unlock(struct udevice *blk, struct disk_partition *pinfo, return -ENOENT; } - split_key_size = *key_size * stripes; - - log_debug("Trying to unlock LUKS partition: key size: %u bytes\n", - *key_size); + split_key_size = key_size * stripes; + log_debug("Unlocking LUKS partition: key size: %u bytes\n", key_size); /* Allocate buffers */ - derived_key = malloc(*key_size); + 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); @@ -537,18 +534,19 @@ int luks_unlock(struct udevice *blk, struct disk_partition *pinfo, /* Derive key for this slot */ ret = derive_key_pbkdf2(slot, pass, pass_len, md_type, - *key_size, derived_key); + key_size, derived_key); if (ret) continue; /* Try to unlock with the derived key */ - ret = try_keyslot(blk, pinfo, hdr, i, md_type, *key_size, + ret = try_keyslot(blk, pinfo, hdr, i, md_type, key_size, derived_key, km, km_blocks, split_key, candidate_key); if (!ret) { /* Successfully unlocked */ - memcpy(master_key, candidate_key, *key_size); + memcpy(master_key, candidate_key, key_size); + *key_sizep = key_size; goto out; } /* Continue trying other slots on failure */ @@ -559,7 +557,7 @@ int luks_unlock(struct udevice *blk, struct disk_partition *pinfo, out: if (derived_key) { - memset(derived_key, '\0', *key_size); + memset(derived_key, '\0', key_size); free(derived_key); } if (split_key) { @@ -575,6 +573,59 @@ out: return ret; } +int luks_unlock(struct udevice *blk, struct disk_partition *pinfo, + const u8 *pass, size_t pass_len, u8 *master_key, u32 *key_sizep) +{ + uint version, hdr_blocks; + struct luks1_phdr *hdr; + struct blk_desc *desc; + int ret; + + if (!blk || !pinfo || !pass || !master_key || !key_sizep) + 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)); + switch (version) { + case LUKS_VERSION_1: + hdr = (struct luks1_phdr *)buffer; + ret = unlock_luks1(blk, pinfo, hdr, pass, pass_len, master_key, + key_sizep); + break; + case LUKS_VERSION_2: + ret = unlock_luks2(blk, pinfo, pass, pass_len, master_key, + key_sizep); + break; + default: + log_debug("unsupported LUKS version %d\n", version); + return -ENOTSUPP; + } + if (ret) + return ret; + + return 0; +} + int luks_create_blkmap(struct udevice *blk, struct disk_partition *pinfo, const u8 *master_key, u32 key_size, const char *label, struct udevice **blkmapp) -- 2.43.0
In some cases we may wish to provide a pre-derived key, e.g. obtained from a TKey. Provide an option for this with LUKSv1. For now it is not exported. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- drivers/block/luks.c | 40 ++++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/drivers/block/luks.c b/drivers/block/luks.c index a3c86c5a197..3fc54dbbb9a 100644 --- a/drivers/block/luks.c +++ b/drivers/block/luks.c @@ -329,8 +329,9 @@ static int derive_key_pbkdf2(struct luks1_keyslot *slot, const u8 *pass, * @blk: Block device * @pinfo: Partition information * @hdr: LUKS1 header (already read) - * @pass: Passphrase + * @pass: Passphrase or pre-derived key * @pass_len: Length of passphrase + * @pre_derived: True if pass is a pre-derived key, false for passphrase * @master_key: Buffer to receive master key * @key_size: Output for key size * @@ -338,7 +339,7 @@ static int derive_key_pbkdf2(struct luks1_keyslot *slot, const u8 *pass, */ static int unlock_luks1(struct udevice *blk, struct disk_partition *pinfo, struct luks1_phdr *hdr, const u8 *pass, size_t pass_len, - u8 *master_key, u32 *key_size); + bool pre_derived, u8 *master_key, u32 *key_size); /** * try_keyslot() - Try to unlock a LUKS key slot with a derived key @@ -447,14 +448,16 @@ static int try_keyslot(struct udevice *blk, struct disk_partition *pinfo, * unlock_luks1() - Unlock a LUKSv1 partition * * Attempts to unlock a LUKSv1 encrypted partition by trying each active - * key slot with the provided passphrase. Uses PBKDF2 for key derivation - * and supports CBC cipher mode with optional ESSIV. + * key slot with the provided passphrase or pre-derived key. When pre_derived + * is false, uses PBKDF2 for key derivation. When true, uses the pass data + * directly as the derived key. Supports CBC cipher mode with optional ESSIV. * * @blk: Block device containing the partition * @pinfo: Partition information * @hdr: LUKSv1 header (already read and validated) - * @pass: Passphrase (binary data) + * @pass: Passphrase (binary data) or pre-derived key * @pass_len: Length of passphrase in bytes + * @pre_derived: True if pass is a pre-derived key, false for passphrase * @master_key: Buffer to receive unlocked master key (min 128 bytes) * @key_sizep: Output for master key size in bytes (set only on success) * @@ -462,7 +465,7 @@ static int try_keyslot(struct udevice *blk, struct disk_partition *pinfo, */ static int unlock_luks1(struct udevice *blk, struct disk_partition *pinfo, struct luks1_phdr *hdr, const u8 *pass, size_t pass_len, - u8 *master_key, u32 *key_sizep) + bool pre_derived, u8 *master_key, u32 *key_sizep) { uint split_key_size, km_blocks, key_size; u8 *split_key, *derived_key; @@ -524,6 +527,17 @@ static int unlock_luks1(struct udevice *blk, struct disk_partition *pinfo, goto out; } + /* If using pre-derived key, use it directly */ + if (pre_derived) { + if (pass_len != key_size) { + log_debug("Pre-derived key size mismatch: got %zu, need %u\n", + pass_len, key_size); + ret = -EINVAL; + goto out; + } + memcpy(derived_key, pass, key_size); + } + /* Try each key slot */ for (i = 0; i < LUKS_NUMKEYS; i++) { struct luks1_keyslot *slot = &hdr->key_slot[i]; @@ -532,11 +546,13 @@ static int unlock_luks1(struct udevice *blk, struct disk_partition *pinfo, if (be32_to_cpu(slot->active) != LUKS_KEY_ENABLED) continue; - /* Derive key for this slot */ - ret = derive_key_pbkdf2(slot, pass, pass_len, md_type, - key_size, derived_key); - if (ret) - continue; + /* Derive key for this slot if not pre-derived */ + if (!pre_derived) { + ret = derive_key_pbkdf2(slot, pass, pass_len, md_type, + key_size, derived_key); + if (ret) + continue; + } /* Try to unlock with the derived key */ ret = try_keyslot(blk, pinfo, hdr, i, md_type, key_size, @@ -610,7 +626,7 @@ int luks_unlock(struct udevice *blk, struct disk_partition *pinfo, case LUKS_VERSION_1: hdr = (struct luks1_phdr *)buffer; ret = unlock_luks1(blk, pinfo, hdr, pass, pass_len, master_key, - key_sizep); + false, key_sizep); break; case LUKS_VERSION_2: ret = unlock_luks2(blk, pinfo, pass, pass_len, master_key, -- 2.43.0
In some cases we may wish to provide a pre-derived key, e.g. obtained from a TKey. Provide an option for this with LUKSv2. For now it is not exported. Improve the error-return documentation while we are here. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- drivers/block/luks2.c | 54 ++++++++++++++++++++++------------- drivers/block/luks_internal.h | 10 ++++--- 2 files changed, 40 insertions(+), 24 deletions(-) diff --git a/drivers/block/luks2.c b/drivers/block/luks2.c index 57e6b9f37d9..e3f3ac28c34 100644 --- a/drivers/block/luks2.c +++ b/drivers/block/luks2.c @@ -544,9 +544,9 @@ static int decrypt_km_xts(const u8 *derived_key, uint key_size, const u8 *km, * @blksz: Block size in bytes * Return: 0 on success, negative error code on failure */ -static int decrypt_km_cbc(u8 *derived_key, uint key_size, const char *encrypt, - u8 *km, u8 *split_key, int size, int km_blocks, - int blksz) +static int decrypt_km_cbc(const u8 *derived_key, uint key_size, + const char *encrypt, u8 *km, u8 *split_key, + int size, int km_blocks, int blksz) { u8 expkey[AES256_EXPAND_KEY_LENGTH]; @@ -811,7 +811,8 @@ static int verify_master_key(const struct luks2_digest *digest, * * This function attempts to unlock one keyslot by: * 1. Reading keyslot metadata from ofnode - * 2. Deriving the candidate master key using the appropriate KDF + * 2. Deriving the candidate master key using the appropriate KDF (or using + * pre-derived key directly) * 3. Verifying the candidate key against the stored digest * * @blk: Block device containing the LUKS partition @@ -819,7 +820,9 @@ static int verify_master_key(const struct luks2_digest *digest, * @keyslot_node: ofnode for this specific keyslot * @digest: Digest information for verification * @md_type: mbedtls message digest type (for PBKDF2) - * @pass: User-provided passphrase + * @pass: User-provided passphrase or pre-derived key + * @pass_len: Length of passphrase + * @pre_derived: True if pass is a pre-derived key, false for passphrase * @master_key: Output buffer for verified master key * @key_sizep: Returns the key size * Return: 0 if unlocked successfully, -EAGAIN to continue trying, -ve on error @@ -828,8 +831,8 @@ static int try_unlock_keyslot(struct udevice *blk, struct disk_partition *pinfo, ofnode keyslot_node, const struct luks2_digest *digest, mbedtls_md_type_t md_type, const u8 *pass, - size_t pass_len, u8 *master_key, - uint *key_sizep) + size_t pass_len, bool pre_derived, + u8 *master_key, uint *key_sizep) { struct luks2_keyslot keyslot; u8 cand_key[128]; @@ -845,16 +848,27 @@ static int try_unlock_keyslot(struct udevice *blk, struct disk_partition *pinfo, log_debug("LUKS2: trying keyslot (type=%d)\n", keyslot.kdf.type); - /* Try the keyslot using the appropriate KDF */ - if (keyslot.kdf.type == LUKS2_KDF_PBKDF2) { - log_debug("LUKS2: calling try_keyslot_pbkdf2\n"); - ret = try_keyslot_pbkdf2(blk, pinfo, &keyslot, pass, pass_len, - md_type, cand_key); + /* If using pre-derived key, use it directly */ + if (pre_derived) { + if (pass_len != keyslot.key_size) { + log_debug("Pre-derived key size mismatch: got %zu, need %u\n", + pass_len, keyslot.key_size); + return -EAGAIN; + } + memcpy(cand_key, pass, pass_len); + ret = 0; } else { - /* Argon2 (already checked for CONFIG_ARGON2 support) */ - log_debug("LUKS2: calling try_keyslot_argon2\n"); - ret = try_keyslot_argon2(blk, pinfo, &keyslot, pass, pass_len, - cand_key); + /* Try the keyslot using the appropriate KDF */ + if (keyslot.kdf.type == LUKS2_KDF_PBKDF2) { + log_debug("LUKS2: calling try_keyslot_pbkdf2\n"); + ret = try_keyslot_pbkdf2(blk, pinfo, &keyslot, pass, pass_len, + md_type, cand_key); + } else { + /* Argon2 (already checked for CONFIG_ARGON2 support) */ + log_debug("LUKS2: calling try_keyslot_argon2\n"); + ret = try_keyslot_argon2(blk, pinfo, &keyslot, pass, pass_len, + cand_key); + } } log_debug("LUKS2: keyslot try returned %d\n", ret); @@ -877,8 +891,8 @@ static int try_unlock_keyslot(struct udevice *blk, struct disk_partition *pinfo, } int unlock_luks2(struct udevice *blk, struct disk_partition *pinfo, - const u8 *pass, size_t pass_len, u8 *master_key, - uint *key_sizep) + const u8 *pass, size_t pass_len, bool pre_derived, + u8 *master_key, uint *key_sizep) { ofnode keyslots_node, keyslot_node; struct luks2_digest digest; @@ -896,8 +910,8 @@ int unlock_luks2(struct udevice *blk, struct disk_partition *pinfo, ret = -EACCES; ofnode_for_each_subnode(keyslot_node, keyslots_node) { ret = try_unlock_keyslot(blk, pinfo, keyslot_node, &digest, - md_type, pass, pass_len, master_key, - key_sizep); + md_type, pass, pass_len, pre_derived, + master_key, key_sizep); if (!ret) /* Successfully unlocked! */ break; diff --git a/drivers/block/luks_internal.h b/drivers/block/luks_internal.h index 6b0e41267ff..33f6ce12689 100644 --- a/drivers/block/luks_internal.h +++ b/drivers/block/luks_internal.h @@ -50,14 +50,16 @@ void essiv_decrypt(const u8 *derived_key, uint key_size, u8 *expkey, u8 *km, * * @blk: Block device * @pinfo: Partition information - * @pass: Passphrase to unlock the partition + * @pass: Passphrase to unlock the partition or pre-derived key * @pass_len: Length of the passphrase in bytes + * @pre_derived: True if pass is a pre-derived key, false for passphrase * @master_key: Buffer to receive the decrypted master key * @key_sizep: Returns the key size - * Return: 0 on success, -ve on error + * Return: 0 on success, -EACCES if no keyslots matched, other -ve on other + * error */ int unlock_luks2(struct udevice *blk, struct disk_partition *pinfo, - const u8 *pass, size_t pass_len, u8 *master_key, - uint *key_sizep); + const u8 *pass, size_t pass_len, bool pre_derived, + u8 *master_key, uint *key_sizep); #endif /* __LUKS_INTERNAL_H__ */ -- 2.43.0
Update luks_unlock() to support a pre-derived key, such as that obtained from a TKey. This must match the key_size of the LUKS partition, otherwise it will fail to unlock. Signed-off-by: Simon Glass <simon.glass@canonical.com> --- cmd/luks.c | 2 +- drivers/block/luks.c | 11 ++++++----- include/luks.h | 4 ++-- test/boot/luks.c | 2 +- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/cmd/luks.c b/cmd/luks.c index defd987776d..47d3e5bed7b 100644 --- a/cmd/luks.c +++ b/cmd/luks.c @@ -89,7 +89,7 @@ static int do_luks_unlock(struct cmd_tbl *cmdtp, int flag, int argc, /* Unlock the partition to get the master key */ ret = luks_unlock(dev_desc->bdev, &info, (const u8 *)passphrase, - strlen(passphrase), master_key, &key_size); + strlen(passphrase), false, master_key, &key_size); if (ret) { printf("Failed to unlock LUKS partition (err %dE)\n", ret); return CMD_RET_FAILURE; diff --git a/drivers/block/luks.c b/drivers/block/luks.c index 3fc54dbbb9a..10ef4a2e31a 100644 --- a/drivers/block/luks.c +++ b/drivers/block/luks.c @@ -590,7 +590,8 @@ out: } int luks_unlock(struct udevice *blk, struct disk_partition *pinfo, - const u8 *pass, size_t pass_len, u8 *master_key, u32 *key_sizep) + const u8 *pass, size_t pass_len, bool pre_derived, + u8 *master_key, u32 *key_sizep) { uint version, hdr_blocks; struct luks1_phdr *hdr; @@ -625,12 +626,12 @@ int luks_unlock(struct udevice *blk, struct disk_partition *pinfo, switch (version) { case LUKS_VERSION_1: hdr = (struct luks1_phdr *)buffer; - ret = unlock_luks1(blk, pinfo, hdr, pass, pass_len, master_key, - false, key_sizep); + ret = unlock_luks1(blk, pinfo, hdr, pass, pass_len, + pre_derived, master_key, key_sizep); break; case LUKS_VERSION_2: - ret = unlock_luks2(blk, pinfo, pass, pass_len, master_key, - key_sizep); + ret = unlock_luks2(blk, pinfo, pass, pass_len, pre_derived, + master_key, key_sizep); break; default: log_debug("unsupported LUKS version %d\n", version); diff --git a/include/luks.h b/include/luks.h index 8826fa96507..da9be12e2a3 100644 --- a/include/luks.h +++ b/include/luks.h @@ -158,8 +158,8 @@ int luks_show_info(struct udevice *blk, struct disk_partition *pinfo); * -EIO if failed to read from block device */ int luks_unlock(struct udevice *blk, struct disk_partition *pinfo, - const u8 *pass, size_t pass_len, u8 *master_key, - u32 *key_size); + const u8 *pass, size_t pass_len, bool pre_derived, + u8 *master_key, u32 *key_size); /** * luks_create_blkmap() - Create a blkmap device for a LUKS partition diff --git a/test/boot/luks.c b/test/boot/luks.c index 93cc8ce6681..dfd6f7b411c 100644 --- a/test/boot/luks.c +++ b/test/boot/luks.c @@ -275,7 +275,7 @@ static int bootstd_test_luks2_unlock(struct unit_test_state *uts) /* 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, (const u8 *)"test", - 4, master_key, &key_size)); + 4, false, master_key, &key_size)); /* Test unlocking partition 2 with correct passphrase */ ut_assertok(run_command("luks unlock mmc c:2 test", 0)); -- 2.43.0
This algorithm can use a lot of memory, so add a check for this condition and return the correct error. Signed-off-by: Simon Glass <simon.glass@canonical.com> --- drivers/block/luks2.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/block/luks2.c b/drivers/block/luks2.c index e3f3ac28c34..db5f13badd9 100644 --- a/drivers/block/luks2.c +++ b/drivers/block/luks2.c @@ -676,7 +676,10 @@ static int try_keyslot_argon2(struct udevice *blk, struct disk_partition *pinfo, ks->kdf.salt_len, derived_key, ks->area.key_size); if (ret) { - log_err("Argon2id failed: %s\n", argon2_error_message(ret)); + log_err("Argon2id failed: %s (code=%d)\n", + argon2_error_message(ret), ret); + if (ret == ARGON2_MEMORY_ALLOCATION_ERROR) + return -ENOMEM; return -EPROTO; } log_debug("LUKS2 Argon2: key derivation succeeded\n"); -- 2.43.0
Add a -t option to the 'luks unlock' command to allow a TKey to be used to unlock a disk. The password is used as the user-supplied secret (USS) in this case. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- cmd/luks.c | 94 +++++++++++++++++++++++++++++++++++++++--- doc/usage/cmd/luks.rst | 36 +++++++++++++--- 2 files changed, 119 insertions(+), 11 deletions(-) diff --git a/cmd/luks.c b/cmd/luks.c index 47d3e5bed7b..ec4d400b44e 100644 --- a/cmd/luks.c +++ b/cmd/luks.c @@ -8,8 +8,11 @@ #include <blk.h> #include <command.h> #include <dm.h> +#include <hexdump.h> #include <luks.h> #include <part.h> +#include <tkey.h> +#include <u-boot/sha256.h> static int do_luks_detect(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) @@ -57,18 +60,86 @@ static int do_luks_info(struct cmd_tbl *cmdtp, int flag, int argc, return CMD_RET_SUCCESS; } +/** + * unlock_with_tkey() - Unlock LUKS partition using TKey-derived key + * + * This function uses TKey to derive a disk encryption key from the + * provided passphrase (used as USS) and uses it to unlock the LUKS partition. + * + * @dev_desc: Block device descriptor + * @info: Partition information + * @passphrase: Passphrase to use as USS for TKey + * @master_key: Buffer to receive unlocked master key + * @key_size: Pointer to receive key size + * Return: 0 on success, -ve on error + */ +static int unlock_with_tkey(struct blk_desc *dev_desc, + struct disk_partition *info, const char *passphrase, + u8 *master_key, u32 *key_size) +{ + u8 tkey_disk_key[TKEY_DISK_KEY_SIZE]; + u8 pubkey[TKEY_PUBKEY_SIZE]; + struct udevice *tkey_dev; + int ret; + + printf("Using TKey for disk encryption key\n"); + + /* Find TKey device */ + ret = uclass_first_device_err(UCLASS_TKEY, &tkey_dev); + if (ret) { + printf("Failed to find TKey device (err %dE)\n", ret); + return ret; + } + + /* Derive disk key using TKey with passphrase as USS */ + printf("Loading TKey signer app (%lx bytes) with USS...\n", + TKEY_SIGNER_SIZE); + ret = tkey_derive_disk_key(tkey_dev, (const u8 *)__signer_1_0_0_begin, + TKEY_SIGNER_SIZE, (const u8 *)passphrase, + strlen(passphrase), tkey_disk_key, pubkey, + NULL); + if (ret) { + printf("Failed to derive TKey disk key (err %dE)\n", ret); + return ret; + } + + printf("TKey public key: "); + print_hex_dump(" ", DUMP_PREFIX_NONE, 16, 1, pubkey, + TKEY_PUBKEY_SIZE, false); + + printf("TKey disk key derived successfully\n"); + printf("TKey derived disk key: "); + print_hex_dump(" ", DUMP_PREFIX_NONE, 16, 1, tkey_disk_key, + TKEY_DISK_KEY_SIZE, false); + + ret = luks_unlock(dev_desc->bdev, info, tkey_disk_key, + TKEY_DISK_KEY_SIZE, true, master_key, key_size); + + /* Wipe TKey disk key */ + memset(tkey_disk_key, '\0', sizeof(tkey_disk_key)); + + return ret; +} + 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; + const char *passphrase = NULL; + bool use_tkey = false; int part, ret, version; u8 master_key[128]; char label[64]; u32 key_size; + /* Check for -t flag */ + if (!strcmp(argv[1], "-t")) { + use_tkey = true; + argc--; + argv++; + } if (argc != 4) return CMD_RET_USAGE; @@ -78,6 +149,10 @@ static int do_luks_unlock(struct cmd_tbl *cmdtp, int flag, int argc, passphrase = argv[3]; + log_debug("Partition start %llx blks %llx blksz%lx\n", + (unsigned long long)info.start, (unsigned long long)info.size, + (ulong)dev_desc->blksz); + /* Verify it's a LUKS partition */ version = luks_get_version(dev_desc->bdev, &info); if (version < 0) { @@ -87,9 +162,15 @@ static int do_luks_unlock(struct cmd_tbl *cmdtp, int flag, int argc, printf("Unlocking LUKS%d partition...\n", version); - /* Unlock the partition to get the master key */ - ret = luks_unlock(dev_desc->bdev, &info, (const u8 *)passphrase, - strlen(passphrase), false, master_key, &key_size); + if (use_tkey) { + ret = unlock_with_tkey(dev_desc, &info, passphrase, master_key, + &key_size); + } else { + /* Unlock with passphrase */ + ret = luks_unlock(dev_desc->bdev, &info,(const u8 *)passphrase, + strlen(passphrase), false, master_key, + &key_size); + } if (ret) { printf("Failed to unlock LUKS partition (err %dE)\n", ret); return CMD_RET_FAILURE; @@ -121,10 +202,11 @@ cleanup: 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\n" - "luks unlock <interface> <dev[:part]> <passphrase> - unlock LUKS partition\n"; + "luks unlock [-t] <interface> <dev[:part]> <passphrase> - unlock LUKS partition\n" + " -t: Use TKey hardware security token with passphrase as USS\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(unlock, 4, 1, do_luks_unlock)); + U_BOOT_SUBCMD_MKENT(unlock, 5, 1, do_luks_unlock)); diff --git a/doc/usage/cmd/luks.rst b/doc/usage/cmd/luks.rst index 3c8576dc8a1..ccf915f5844 100644 --- a/doc/usage/cmd/luks.rst +++ b/doc/usage/cmd/luks.rst @@ -13,7 +13,7 @@ Synopsis luks detect <interface> <dev[:part]> luks info <interface> <dev[:part]> - luks unlock <interface> <dev[:part]> <passphrase> + luks unlock [-t] <interface> <dev[:part]> <passphrase> Description ----------- @@ -88,12 +88,17 @@ dev[:part] luks unlock ~~~~~~~~~~~ -Unlock a LUKS encrypted partition using a passphrase. This command: +Unlock a LUKS encrypted partition using a passphrase or TKey hardware token. +This command: 1. Verifies the partition is LUKS encrypted (LUKS1 or LUKS2) 2. Parses LUKS2 JSON metadata (if LUKS2) using FDT conversion -3. Derives the encryption key using PBKDF2 or Argon2id with the provided - passphrase +3. Derives the encryption key: + + - **Without -t**: Uses PBKDF2 or Argon2id with the provided passphrase + - **With -t**: Uses TKey hardware token with passphrase as USS (User-Supplied + Secret) to derive a disk encryption key + 4. Attempts to unlock each active key slot 5. Verifies the master key against the stored digest 6. Creates a blkmap device providing on-the-fly decryption @@ -118,6 +123,11 @@ be used to access files on the unlocked partition. * **Argon2id**: Memory-hard KDF resistant to GPU attacks (LUKS2 only, requires CONFIG_ARGON2) +-t + Optional flag to use TKey hardware security token. When specified, the + passphrase is used as the USS (User-Supplied Secret) to derive a disk + encryption key from the TKey's public key. + interface The storage interface type (e.g., mmc, usb, scsi) @@ -125,7 +135,8 @@ dev[:part] The device number and optional partition number passphrase - The passphrase to unlock the LUKS partition. Note that the passphrase is + The passphrase to unlock the LUKS partition. When using -t flag, this is + used as the USS for TKey key derivation. 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. @@ -228,6 +239,17 @@ Unlock and load a kernel from encrypted partition:: => bootz ${kernel_addr_r} - ${fdt_addr_r} +Unlock using TKey hardware token:: + + => luks unlock -t mmc 0:2 mypassword + Using TKey for disk encryption key + Loading TKey signer app (7168 bytes) with USS... + TKey public key: 3a b2 c4 ... (32 bytes) + TKey disk key derived successfully + Unlocking LUKS2 partition... + Successfully unlocked with key slot 0! + Unlocked LUKS partition as blkmap device 'luks-mmc-0:2' + Configuration ------------- @@ -254,6 +276,10 @@ For Argon2id support (modern LUKS2 KDF):: CONFIG_ARGON2=y # Argon2 password hashing (adds ~50KB to binary) +For TKey hardware token support (requires -t flag):: + + CONFIG_TKEY=y # TKey hardware security token support + Return value ------------ -- 2.43.0
participants (1)
-
Simon Glass