diff --git a/cmd/luks.c b/cmd/luks.c index c1e8035e685..ec4d400b44e 100644 --- a/cmd/luks.c +++ b/cmd/luks.c @@ -8,8 +8,11 @@ #include #include #include +#include #include #include +#include +#include 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, passphrase, 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 - detect if partition is LUKS encrypted\n" "luks info - show LUKS header information\n" - "luks unlock - unlock LUKS partition\n"; + "luks unlock [-t] - 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 luks info - luks unlock + luks unlock [-t] 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 ------------ diff --git a/drivers/block/luks.c b/drivers/block/luks.c index 923932c0dad..10ef4a2e31a 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]; @@ -317,58 +288,31 @@ static void essiv_decrypt(u8 *derived_key, uint key_size, u8 *expkey, u8 *km, } /** - * try_keyslot() - Unlock a LUKS key slot with a passphrase + * derive_key_pbkdf2() - Derive key from passphrase using PBKDF2 * - * @blk: Block device - * @pinfo: Partition information - * @hdr: LUKS header - * @slot_idx: Key slot index to try - * @pass: Passphrase to try + * @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 + * @key_size: Size of the key to derive * @derived_key: Buffer for derived key (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, -EPROTO on error */ -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) +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) { - struct luks1_keyslot *slot = &hdr->key_slot[slot_idx]; - uint iters, 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]; - u8 iv[AES_BLOCK_LENGTH]; + uint iters = be32_to_cpu(slot->iterations); int ret; - /* Check if slot is active */ - if (be32_to_cpu(slot->active) != LUKS_KEY_ENABLED) - return -ENOENT; - - log_debug("trying key slot %d...\n", slot_idx); - - 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 '%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; @@ -376,6 +320,64 @@ static int try_keyslot(struct udevice *blk, struct disk_partition *pinfo, log_debug_hex("derived_key[0-7]", derived_key, 8); + return 0; +} + +/** + * unlock_luks1() - Unlock a LUKSv1 partition + * + * @blk: Block device + * @pinfo: Partition information + * @hdr: LUKS1 header (already read) + * @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 + * + * 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, + bool pre_derived, u8 *master_key, u32 *key_size); + +/** + * 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 + * @md_type: Hash algorithm type for master key verification + * @key_size: Size of the key + * @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 key), -ve on error + */ +static int try_keyslot(struct udevice *blk, struct disk_partition *pinfo, + struct luks1_phdr *hdr, int slot_idx, + 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; + struct blk_desc *desc = dev_get_uclass_plat(blk); + u8 expkey[AES256_EXPAND_KEY_LENGTH]; + u8 key_digest[LUKS_DIGESTSIZE]; + u8 iv[AES_BLOCK_LENGTH]; + int ret; + + 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; + /* Read encrypted key material */ ret = blk_read(blk, pinfo->start + km_offset, km_blocks, km); if (ret != km_blocks) { @@ -387,9 +389,11 @@ 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); + /* 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 */ @@ -398,10 +402,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); } @@ -434,7 +436,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); @@ -442,19 +444,161 @@ static int try_keyslot(struct udevice *blk, struct disk_partition *pinfo, return -EACCES; } -int luks_unlock(struct udevice *blk, struct disk_partition *pinfo, - const char *pass, 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 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) 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) + * + * 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, + bool pre_derived, 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) + desc = dev_get_uclass_plat(blk); + + /* 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)) { + log_debug("only CBC mode is currently supported (got: %.32s)\n", + hdr->cipher_mode); + return -ENOTSUPP; + } + + /* Look up hash algorithm */ + ret = hash_lookup_algo(hdr->hash_spec, &hash_algo); + if (ret) { + log_debug("unsupported hash: %.32s\n", hdr->hash_spec); + return -ENOTSUPP; + } + + md_type = hash_mbedtls_type(hash_algo); + 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); + break; + } + } + if (!stripes) { + log_debug("no active key slots found\n"); + return -ENOENT; + } + + 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); + split_key = malloc(split_key_size); + km_blocks = (split_key_size + desc->blksz - 1) / desc->blksz; + km = malloc_cache_aligned(km_blocks * desc->blksz); + + if (!derived_key || !split_key || !km) { + ret = -ENOMEM; + 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]; + + /* Skip inactive slots */ + if (be32_to_cpu(slot->active) != LUKS_KEY_ENABLED) + 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, + derived_key, km, km_blocks, split_key, + candidate_key); + + if (!ret) { + /* Successfully unlocked */ + memcpy(master_key, candidate_key, key_size); + *key_sizep = key_size; + goto out; + } + /* Continue trying other slots on failure */ + } + + log_debug("Failed to unlock: wrong passphrase or no active key slots\n"); + ret = -EACCES; + +out: + if (derived_key) { + memset(derived_key, '\0', key_size); + free(derived_key); + } + if (split_key) { + memset(split_key, '\0', split_key_size); + free(split_key); + } + if (km) { + memset(km, '\0', km_blocks * desc->blksz); + free(km); + } + memset(candidate_key, '\0', sizeof(candidate_key)); + + return ret; +} + +int luks_unlock(struct udevice *blk, struct disk_partition *pinfo, + const u8 *pass, size_t pass_len, bool pre_derived, + 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); @@ -479,118 +623,26 @@ 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); - - if (version != LUKS_VERSION_1) { + switch (version) { + case LUKS_VERSION_1: + hdr = (struct luks1_phdr *)buffer; + 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, pre_derived, + master_key, key_sizep); + break; + default: log_debug("unsupported LUKS version %d\n", version); return -ENOTSUPP; } + if (ret) + return ret; - 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) { - log_debug("only CBC mode is currently supported (got: %.32s)\n", - hdr->cipher_mode); - return -ENOTSUPP; - } - - /* Look up hash algorithm */ - ret = hash_lookup_algo(hdr->hash_spec, &hash_algo); - if (ret) { - log_debug("unsupported hash: %.32s\n", hdr->hash_spec); - return -ENOTSUPP; - } - - md_type = hash_mbedtls_type(hash_algo); - - *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); - break; - } - } - if (!stripes) { - log_debug("no active key slots found\n"); - return -ENOENT; - } - - split_key_size = *key_size * stripes; - - log_debug("Trying to unlock LUKS partition: key size: %u bytes\n", - *key_size); - - /* Allocate buffers */ - 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); - - if (!derived_key || !split_key || !km) { - ret = -ENOMEM; - goto out; - } - - /* 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); - - if (!ret) { - /* Successfully unlocked */ - memcpy(master_key, candidate_key, *key_size); - goto out; - } - /* Continue trying other slots on failure */ - } - - log_debug("Failed to unlock: wrong passphrase or no active key slots\n"); - ret = -EACCES; - -out: - if (derived_key) { - memset(derived_key, '\0', *key_size); - free(derived_key); - } - if (split_key) { - memset(split_key, '\0', split_key_size); - free(split_key); - } - if (km) { - memset(km, '\0', km_blocks * desc->blksz); - free(km); - } - memset(candidate_key, '\0', sizeof(candidate_key)); - - return ret; + return 0; } -/** - * 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..db5f13badd9 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 * @@ -607,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]; @@ -649,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; @@ -660,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; @@ -718,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; @@ -730,15 +668,18 @@ 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) { - 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"); @@ -873,7 +814,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 @@ -881,7 +823,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 @@ -889,7 +833,8 @@ 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, + mbedtls_md_type_t md_type, const u8 *pass, + size_t pass_len, bool pre_derived, u8 *master_key, uint *key_sizep) { struct luks2_keyslot keyslot; @@ -906,15 +851,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, 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, 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); @@ -937,7 +894,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, bool pre_derived, + u8 *master_key, uint *key_sizep) { ofnode keyslots_node, keyslot_node; struct luks2_digest digest; @@ -955,7 +913,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, 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 14d3839fe6a..33f6ce12689 100644 --- a/drivers/block/luks_internal.h +++ b/drivers/block/luks_internal.h @@ -27,17 +27,39 @@ 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 * * @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 char *pass, 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__ */ diff --git a/include/luks.h b/include/luks.h index 6c39db7a2d2..da9be12e2a3 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, 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 6bf613f3b08..dfd6f7b411c 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, false, master_key, &key_size)); /* Test unlocking partition 2 with correct passphrase */ ut_assertok(run_command("luks unlock mmc c:2 test", 0)); 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.