luks: Provide an implementation of luks2
Add supports for luks v2 which is a more common version used on modern systems. This makes use of Argon2 and also the JSON->FDT parser. Enable this feature for sandbox, tidying up the defconfig while we are here. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com>
This commit is contained in:
@@ -11,7 +11,7 @@ endif
|
||||
|
||||
ifndef CONFIG_XPL_BUILD
|
||||
obj-$(CONFIG_IDE) += ide.o
|
||||
obj-$(CONFIG_BLK_LUKS) += luks.o
|
||||
obj-$(CONFIG_BLK_LUKS) += luks.o luks2.o
|
||||
obj-$(CONFIG_RKMTD) += rkmtd.o
|
||||
endif
|
||||
obj-$(CONFIG_SANDBOX) += sandbox.o host-uclass.o host_dev.o
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <blk.h>
|
||||
#include <blkmap.h>
|
||||
#include <dm.h>
|
||||
#include <dm/ofnode.h>
|
||||
#include <hash.h>
|
||||
#include <hexdump.h>
|
||||
#include <json.h>
|
||||
@@ -21,6 +22,8 @@
|
||||
#include <linux/err.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/string.h>
|
||||
#include <mbedtls/aes.h>
|
||||
#include <mbedtls/cipher.h>
|
||||
#include <mbedtls/md.h>
|
||||
#include <mbedtls/pkcs5.h>
|
||||
#include <u-boot/sha256.h>
|
||||
@@ -476,6 +479,9 @@ 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) {
|
||||
log_debug("unsupported LUKS version %d\n", version);
|
||||
return -ENOTSUPP;
|
||||
@@ -591,11 +597,12 @@ int luks_create_blkmap(struct udevice *blk, struct disk_partition *pinfo,
|
||||
{
|
||||
u8 essiv_key[SHA256_SUM_LEN]; /* SHA-256 output */
|
||||
struct luks1_phdr *hdr;
|
||||
struct luks2_hdr *hdr2;
|
||||
struct blk_desc *desc;
|
||||
struct udevice *dev;
|
||||
uint payload_offset;
|
||||
int ret, version;
|
||||
bool use_essiv;
|
||||
int ret;
|
||||
|
||||
if (!blk || !pinfo || !master_key || !label || !blkmapp)
|
||||
return -EINVAL;
|
||||
@@ -608,7 +615,107 @@ int luks_create_blkmap(struct udevice *blk, struct disk_partition *pinfo,
|
||||
log_debug("failed to read LUKS header\n");
|
||||
return -EIO;
|
||||
}
|
||||
hdr = (struct luks1_phdr *)buf;
|
||||
|
||||
/* Check version */
|
||||
version = be16_to_cpu(*(__be16 *)(buf + LUKS_MAGIC_LEN));
|
||||
|
||||
if (version == LUKS_VERSION_2) {
|
||||
/* LUKS2: Parse JSON for segment offset */
|
||||
char *json_data;
|
||||
u64 hdr_size, segment_offset;
|
||||
int blocks;
|
||||
struct abuf fdt_buf;
|
||||
oftree tree;
|
||||
ofnode root, segments_node, segment0_node;
|
||||
const char *offset_str, *encryption;
|
||||
|
||||
abuf_init(&fdt_buf);
|
||||
|
||||
hdr2 = (struct luks2_hdr *)buf;
|
||||
hdr_size = be64_to_cpu(hdr2->hdr_size);
|
||||
|
||||
/* Read full header with JSON */
|
||||
blocks = (hdr_size + desc->blksz - 1) / desc->blksz;
|
||||
json_data = malloc_cache_aligned(blocks * desc->blksz);
|
||||
if (!json_data)
|
||||
return -ENOMEM;
|
||||
|
||||
if (blk_read(blk, pinfo->start, blocks, json_data) != blocks) {
|
||||
free(json_data);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* Convert JSON to FDT */
|
||||
ret = json_to_fdt(json_data + 4096, &fdt_buf);
|
||||
if (ret) {
|
||||
log_err("Failed to convert JSON to FDT: %d\n", ret);
|
||||
free(json_data);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Create oftree from FDT */
|
||||
tree = oftree_from_fdt(abuf_data(&fdt_buf));
|
||||
if (!oftree_valid(tree)) {
|
||||
abuf_uninit(&fdt_buf);
|
||||
free(json_data);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Get root node */
|
||||
root = oftree_root(tree);
|
||||
if (!ofnode_valid(root)) {
|
||||
abuf_uninit(&fdt_buf);
|
||||
free(json_data);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Navigate to segments node */
|
||||
segments_node = ofnode_find_subnode(root, "segments");
|
||||
if (!ofnode_valid(segments_node)) {
|
||||
abuf_uninit(&fdt_buf);
|
||||
free(json_data);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Get first segment (segment 0) */
|
||||
segment0_node = ofnode_find_subnode(segments_node, "0");
|
||||
if (!ofnode_valid(segment0_node)) {
|
||||
abuf_uninit(&fdt_buf);
|
||||
free(json_data);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Get offset (string in LUKS2 JSON) */
|
||||
offset_str = ofnode_read_string(segment0_node, "offset");
|
||||
if (!offset_str) {
|
||||
abuf_uninit(&fdt_buf);
|
||||
free(json_data);
|
||||
return -EINVAL;
|
||||
}
|
||||
segment_offset = simple_strtoull(offset_str, NULL, 10);
|
||||
|
||||
/* Convert byte offset to sectors */
|
||||
payload_offset = segment_offset / desc->blksz;
|
||||
|
||||
/* Check if ESSIV mode is used */
|
||||
encryption = ofnode_read_string(segment0_node, "encryption");
|
||||
if (encryption)
|
||||
use_essiv = strstr(encryption, "essiv");
|
||||
else
|
||||
use_essiv = false;
|
||||
|
||||
abuf_uninit(&fdt_buf);
|
||||
free(json_data);
|
||||
} else {
|
||||
/* LUKS1 */
|
||||
hdr = (struct luks1_phdr *)buf;
|
||||
|
||||
/* Check if ESSIV mode is used */
|
||||
use_essiv = strstr(hdr->cipher_mode, "essiv");
|
||||
|
||||
/* Get payload offset */
|
||||
payload_offset = be32_to_cpu(hdr->payload_offset);
|
||||
}
|
||||
|
||||
/* Create blkmap device */
|
||||
ret = blkmap_create(label, &dev);
|
||||
@@ -617,9 +724,7 @@ int luks_create_blkmap(struct udevice *blk, struct disk_partition *pinfo,
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Check if ESSIV mode is used */
|
||||
use_essiv = strstr(hdr->cipher_mode, "essiv");
|
||||
|
||||
/* Compute ESSIV key if needed */
|
||||
if (use_essiv) {
|
||||
int hash_size = SHA256_SUM_LEN;
|
||||
|
||||
@@ -632,7 +737,6 @@ int luks_create_blkmap(struct udevice *blk, struct disk_partition *pinfo,
|
||||
}
|
||||
|
||||
/* Map the encrypted partition to the blkmap device */
|
||||
payload_offset = be32_to_cpu(hdr->payload_offset);
|
||||
log_debug("mapping blkmap: blknr 0 blkcnt %lx payload_offset %x essiv %d\n",
|
||||
(ulong)pinfo->size, payload_offset, use_essiv);
|
||||
ret = blkmap_map_crypt(dev, 0, pinfo->size, blk, pinfo->start,
|
||||
|
||||
974
drivers/block/luks2.c
Normal file
974
drivers/block/luks2.c
Normal file
@@ -0,0 +1,974 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* LUKS2 (Linux Unified Key Setup version 2) support
|
||||
*
|
||||
* Copyright (C) 2025 Canonical Ltd
|
||||
*/
|
||||
|
||||
/* #define LOG_DEBUG */
|
||||
|
||||
#include <abuf.h>
|
||||
#include <blk.h>
|
||||
#include <dm.h>
|
||||
#include <dm/ofnode.h>
|
||||
#include <hash.h>
|
||||
#include <json.h>
|
||||
#include <log.h>
|
||||
#include <luks.h>
|
||||
#include <memalign.h>
|
||||
#include <part.h>
|
||||
#include <uboot_aes.h>
|
||||
#include <asm/unaligned.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/string.h>
|
||||
#include <mbedtls/aes.h>
|
||||
#include <mbedtls/base64.h>
|
||||
#include <mbedtls/md.h>
|
||||
#include <mbedtls/pkcs5.h>
|
||||
#include <u-boot/sha256.h>
|
||||
#include <argon2.h>
|
||||
#include "luks_internal.h"
|
||||
|
||||
/**
|
||||
* enum luks2_kdf_type - LUKS2 KDF type
|
||||
*
|
||||
* @LUKS2_KDF_PBKDF2: PBKDF2 key derivation function
|
||||
* @LUKS2_KDF_ARGON2I: Argon2i key derivation function
|
||||
* @LUKS2_KDF_ARGON2ID: Argon2id key derivation function
|
||||
*/
|
||||
enum luks2_kdf_type {
|
||||
LUKS2_KDF_PBKDF2,
|
||||
LUKS2_KDF_ARGON2I,
|
||||
LUKS2_KDF_ARGON2ID,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct luks2_digest - LUKS2 digest information
|
||||
*
|
||||
* @type: Digest KDF type
|
||||
* @hash: Hash algorithm name (e.g., "sha256")
|
||||
* @iters: PBKDF2 iteration count (valid if type == LUKS2_KDF_PBKDF2)
|
||||
* @time: Argon2 time cost parameter (valid if type == LUKS2_KDF_ARGON2*)
|
||||
* @memory: Argon2 memory cost parameter in KB (type == LUKS2_KDF_ARGON2*)
|
||||
* @cpus: Argon2 parallelism/lanes parameter (type == LUKS2_KDF_ARGON2*)
|
||||
* @salt: Decoded salt value
|
||||
* @salt_len: Actual length of decoded salt
|
||||
* @digest: Decoded digest (master key verification value)
|
||||
* @digest_len: Actual length of decoded digest
|
||||
*/
|
||||
struct luks2_digest {
|
||||
enum luks2_kdf_type type;
|
||||
const char *hash;
|
||||
u32 iters;
|
||||
u32 time;
|
||||
u32 memory;
|
||||
u32 cpus;
|
||||
u8 salt[LUKS_SALTSIZE];
|
||||
int salt_len;
|
||||
u8 digest[128];
|
||||
int digest_len;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct luks2_kdf - LUKS2 keyslot KDF parameters
|
||||
* @type: KDF type
|
||||
* @salt: Decoded KDF salt
|
||||
* @salt_len: Actual length of decoded salt
|
||||
* @iters: PBKDF2 iteration count (valid if type == LUKS2_KDF_PBKDF2)
|
||||
* @time: Argon2 time cost parameter (valid if type == LUKS2_KDF_ARGON2*)
|
||||
* @memory: Argon2 memory cost parameter in KB (type == LUKS2_KDF_ARGON2*)
|
||||
* @cpus: Argon2 parallelism/lanes parameter (type == LUKS2_KDF_ARGON2*)
|
||||
*/
|
||||
struct luks2_kdf {
|
||||
enum luks2_kdf_type type;
|
||||
u8 salt[LUKS_SALTSIZE];
|
||||
int salt_len;
|
||||
u32 iters;
|
||||
u32 time;
|
||||
u32 memory;
|
||||
u32 cpus;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct luks2_area - LUKS2 keyslot encrypted area parameters
|
||||
* @offset: Byte offset from partition start where key material is stored
|
||||
* @size: Size of encrypted key material in bytes
|
||||
* @encryption: Encryption mode string (e.g., "aes-xts-plain64")
|
||||
* @key_size: Encryption key size in bytes (32 for AES-256, 64 for XTS-512)
|
||||
*/
|
||||
struct luks2_area {
|
||||
u64 offset;
|
||||
u64 size;
|
||||
const char *encryption;
|
||||
u32 key_size;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct luks2_af - LUKS2 keyslot anti-forensic parameters
|
||||
* @stripes: Number of anti-forensic stripes (typically 4000)
|
||||
* @hash: Hash algorithm name for AF merge operation
|
||||
*/
|
||||
struct luks2_af {
|
||||
u32 stripes;
|
||||
const char *hash;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct luks2_keyslot - LUKS2 keyslot information
|
||||
* @type: Keyslot type (should be "luks2")
|
||||
* @key_size: Size of the master key in bytes
|
||||
* @kdf: Key derivation function parameters
|
||||
* @af: Anti-forensic parameters
|
||||
* @area: Encrypted key material area parameters
|
||||
*/
|
||||
struct luks2_keyslot {
|
||||
const char *type;
|
||||
u32 key_size;
|
||||
struct luks2_kdf kdf;
|
||||
struct luks2_af af;
|
||||
struct luks2_area area;
|
||||
};
|
||||
|
||||
/**
|
||||
* str_to_kdf_type() - Convert KDF type string to enum
|
||||
*
|
||||
* @type_str: KDF type string ("pbkdf2", "argon2i", or "argon2id")
|
||||
* Return: enum luks2_kdf_type value, or negative error code if unknown type
|
||||
*/
|
||||
static int str_to_kdf_type(const char *type_str)
|
||||
{
|
||||
if (!type_str)
|
||||
return -EINVAL;
|
||||
|
||||
if (!strcmp(type_str, "pbkdf2"))
|
||||
return LUKS2_KDF_PBKDF2;
|
||||
if (!strcmp(type_str, "argon2i"))
|
||||
return LUKS2_KDF_ARGON2I;
|
||||
if (!strcmp(type_str, "argon2id"))
|
||||
return LUKS2_KDF_ARGON2ID;
|
||||
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
/* Base64 decode wrapper for LUKS2 */
|
||||
static int base64_decode(const char *in, u8 *out, int out_len)
|
||||
{
|
||||
size_t olen;
|
||||
int ret;
|
||||
|
||||
ret = mbedtls_base64_decode(out, out_len, &olen,
|
||||
(const unsigned char *)in, strlen(in));
|
||||
if (ret == MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL)
|
||||
return -ENOSPC;
|
||||
if (ret == MBEDTLS_ERR_BASE64_INVALID_CHARACTER)
|
||||
return -EINVAL;
|
||||
if (ret)
|
||||
return -EINVAL;
|
||||
|
||||
return olen;
|
||||
}
|
||||
|
||||
/**
|
||||
* read_digest_info() - Read LUKS2 digest information from ofnode
|
||||
*
|
||||
* @digest_node: ofnode for the digest (e.g., digest "0")
|
||||
* @digest: Pointer to digest structure to fill
|
||||
* Return: 0 on success, -ve on error
|
||||
*/
|
||||
static int read_digest_info(ofnode digest_node, struct luks2_digest *digest)
|
||||
{
|
||||
const char *salt_b64, *digest_b64;
|
||||
|
||||
const char *type_str;
|
||||
int ret;
|
||||
|
||||
memset(digest, '\0', sizeof(*digest));
|
||||
|
||||
/* Read and convert digest type */
|
||||
type_str = ofnode_read_string(digest_node, "type");
|
||||
ret = str_to_kdf_type(type_str);
|
||||
if (ret < 0) {
|
||||
log_debug("LUKS2: unsupported digest type %s\n", type_str);
|
||||
return ret;
|
||||
}
|
||||
digest->type = ret;
|
||||
|
||||
/* Check if Argon2 is supported if needed */
|
||||
if ((digest->type == LUKS2_KDF_ARGON2I ||
|
||||
digest->type == LUKS2_KDF_ARGON2ID) &&
|
||||
!IS_ENABLED(CONFIG_ARGON2)) {
|
||||
log_debug("LUKS2: Argon2 not supported\n");
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
/* Read hash algorithm */
|
||||
digest->hash = ofnode_read_string(digest_node, "hash");
|
||||
if (!digest->hash)
|
||||
return -EINVAL;
|
||||
|
||||
/* Read KDF-specific parameters */
|
||||
if (digest->type == LUKS2_KDF_PBKDF2) {
|
||||
/* PBKDF2 */
|
||||
if (ofnode_read_u32(digest_node, "iterations", &digest->iters))
|
||||
return -EINVAL;
|
||||
} else {
|
||||
/* Argon2 */
|
||||
if (ofnode_read_u32(digest_node, "time", &digest->time) ||
|
||||
ofnode_read_u32(digest_node, "memory", &digest->memory) ||
|
||||
ofnode_read_u32(digest_node, "cpus", &digest->cpus))
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Read and decode salt */
|
||||
salt_b64 = ofnode_read_string(digest_node, "salt");
|
||||
if (!salt_b64)
|
||||
return -EINVAL;
|
||||
digest->salt_len = base64_decode(salt_b64, digest->salt,
|
||||
sizeof(digest->salt));
|
||||
if (digest->salt_len <= 0)
|
||||
return -EINVAL;
|
||||
|
||||
/* Read and decode digest */
|
||||
digest_b64 = ofnode_read_string(digest_node, "digest");
|
||||
if (!digest_b64)
|
||||
return -EINVAL;
|
||||
digest->digest_len = base64_decode(digest_b64, digest->digest,
|
||||
sizeof(digest->digest));
|
||||
if (digest->digest_len <= 0)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* read_keyslot_info() - Read LUKS2 keyslot information from ofnode
|
||||
*
|
||||
* @keyslot_node: ofnode for the keyslot (e.g., keyslot "0")
|
||||
* @keyslot: Pointer to keyslot structure to fill
|
||||
* @hash_name: Hash name to use for AF (from digest)
|
||||
* Return: 0 on success, -ve on error
|
||||
*/
|
||||
static int read_keyslot_info(ofnode keyslot_node, struct luks2_keyslot *keyslot,
|
||||
const char *hash_name)
|
||||
{
|
||||
const char *salt_b64, *offset_str, *size_str;
|
||||
ofnode kdf_node, af_node, area_node;
|
||||
int ret;
|
||||
|
||||
memset(keyslot, '\0', sizeof(*keyslot));
|
||||
|
||||
/* Read keyslot type */
|
||||
keyslot->type = ofnode_read_string(keyslot_node, "type");
|
||||
if (!keyslot->type || strcmp(keyslot->type, "luks2"))
|
||||
return -EINVAL;
|
||||
|
||||
/* Read key size */
|
||||
if (ofnode_read_u32(keyslot_node, "key_size", &keyslot->key_size))
|
||||
return -EINVAL;
|
||||
|
||||
/* Navigate to and read KDF node */
|
||||
kdf_node = ofnode_find_subnode(keyslot_node, "kdf");
|
||||
if (!ofnode_valid(kdf_node))
|
||||
return -EINVAL;
|
||||
|
||||
offset_str = ofnode_read_string(kdf_node, "type");
|
||||
ret = str_to_kdf_type(offset_str);
|
||||
if (ret < 0) {
|
||||
log_debug("LUKS2: unsupported KDF type %s\n", offset_str);
|
||||
return ret;
|
||||
}
|
||||
keyslot->kdf.type = ret;
|
||||
|
||||
/* Check if Argon2 is supported if needed */
|
||||
if ((keyslot->kdf.type == LUKS2_KDF_ARGON2I ||
|
||||
keyslot->kdf.type == LUKS2_KDF_ARGON2ID) &&
|
||||
!IS_ENABLED(CONFIG_ARGON2)) {
|
||||
log_debug("LUKS2: Argon2 not supported\n");
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
/* Read KDF salt */
|
||||
salt_b64 = ofnode_read_string(kdf_node, "salt");
|
||||
if (!salt_b64)
|
||||
return -EINVAL;
|
||||
keyslot->kdf.salt_len = base64_decode(salt_b64, keyslot->kdf.salt,
|
||||
sizeof(keyslot->kdf.salt));
|
||||
if (keyslot->kdf.salt_len <= 0)
|
||||
return -EINVAL;
|
||||
|
||||
/* Read KDF-specific parameters */
|
||||
if (keyslot->kdf.type == LUKS2_KDF_PBKDF2) {
|
||||
if (ofnode_read_u32(kdf_node, "iterations", &keyslot->kdf.iters))
|
||||
return -EINVAL;
|
||||
} else {
|
||||
/* Argon2 */
|
||||
if (ofnode_read_u32(kdf_node, "time", &keyslot->kdf.time) ||
|
||||
ofnode_read_u32(kdf_node, "memory", &keyslot->kdf.memory) ||
|
||||
ofnode_read_u32(kdf_node, "cpus", &keyslot->kdf.cpus))
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Navigate to and read AF node */
|
||||
af_node = ofnode_find_subnode(keyslot_node, "af");
|
||||
if (!ofnode_valid(af_node))
|
||||
return -EINVAL;
|
||||
|
||||
if (ofnode_read_u32(af_node, "stripes", &keyslot->af.stripes))
|
||||
keyslot->af.stripes = 4000; /* Default */
|
||||
keyslot->af.hash = hash_name;
|
||||
|
||||
/* Navigate to and read area node */
|
||||
area_node = ofnode_find_subnode(keyslot_node, "area");
|
||||
if (!ofnode_valid(area_node))
|
||||
return -EINVAL;
|
||||
|
||||
/* Read offset and size (strings in LUKS2 JSON) */
|
||||
offset_str = ofnode_read_string(area_node, "offset");
|
||||
if (!offset_str)
|
||||
return -EINVAL;
|
||||
keyslot->area.offset = simple_strtoull(offset_str, NULL, 10);
|
||||
|
||||
size_str = ofnode_read_string(area_node, "size");
|
||||
if (!size_str)
|
||||
return -EINVAL;
|
||||
keyslot->area.size = simple_strtoull(size_str, NULL, 10);
|
||||
|
||||
/* Read encryption mode */
|
||||
keyslot->area.encryption = ofnode_read_string(area_node, "encryption");
|
||||
if (!keyslot->area.encryption)
|
||||
return -EINVAL;
|
||||
|
||||
/* Read area key size */
|
||||
if (ofnode_read_u32(area_node, "key_size", &keyslot->area.key_size))
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* read_luks2_info() - Read and parse LUKS2 header and metadata
|
||||
*
|
||||
* @blk: Block device
|
||||
* @pinfo: Partition information
|
||||
* @fdt_buf: Buffer to hold the converted FDT (caller must uninit)
|
||||
* @digest: Pointer to digest structure to fill
|
||||
* @md_type: Pointer to receive mbedtls MD type
|
||||
* @keyslots_node: Pointer to receive keyslots ofnode
|
||||
* Return: 0 on success, -ve on error
|
||||
*/
|
||||
static int read_luks2_info(struct udevice *blk, struct disk_partition *pinfo,
|
||||
struct abuf *fdt_buf, struct luks2_digest *digest,
|
||||
mbedtls_md_type_t *md_typep, ofnode *keyslots_nodep)
|
||||
{
|
||||
struct blk_desc *desc = dev_get_uclass_plat(blk);
|
||||
ALLOC_CACHE_ALIGN_BUFFER(u8, buffer, desc->blksz);
|
||||
ofnode root, digests_node, digest0;
|
||||
struct hash_algo *hash_algo;
|
||||
mbedtls_md_type_t md_type;
|
||||
struct luks2_hdr *hdr;
|
||||
ofnode keyslots_node;
|
||||
char *json_data;
|
||||
int count, ret;
|
||||
u64 hdr_size;
|
||||
oftree tree;
|
||||
|
||||
abuf_init(fdt_buf);
|
||||
|
||||
/* Read LUKS2 header */
|
||||
if (blk_read(blk, pinfo->start, 1, buffer) != 1)
|
||||
return -EIO;
|
||||
|
||||
hdr = (struct luks2_hdr *)buffer;
|
||||
hdr_size = be64_to_cpu(hdr->hdr_size);
|
||||
|
||||
log_debug("LUKS2: header size %llu bytes\n", hdr_size);
|
||||
|
||||
/* Allocate and read full header with JSON */
|
||||
count = (hdr_size + desc->blksz - 1) / desc->blksz;
|
||||
json_data = malloc_cache_aligned(count * desc->blksz);
|
||||
if (!json_data)
|
||||
return -ENOMEM;
|
||||
|
||||
if (blk_read(blk, pinfo->start, count, json_data) != count) {
|
||||
ret = -EIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = -EINVAL;
|
||||
|
||||
/* JSON starts after a 4K binary header: convert to FDT */
|
||||
if (json_to_fdt(json_data + 4096, fdt_buf)) {
|
||||
log_err("Failed to convert JSON to FDT\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Create oftree from FDT */
|
||||
tree = oftree_from_fdt(abuf_data(fdt_buf));
|
||||
if (!oftree_valid(tree))
|
||||
goto out;
|
||||
|
||||
/* Get root node */
|
||||
root = oftree_root(tree);
|
||||
if (!ofnode_valid(root))
|
||||
goto out;
|
||||
|
||||
/* Navigate to digests node and get digest 0 */
|
||||
digests_node = ofnode_find_subnode(root, "digests");
|
||||
if (!ofnode_valid(digests_node))
|
||||
goto out;
|
||||
|
||||
digest0 = ofnode_find_subnode(digests_node, "0");
|
||||
if (!ofnode_valid(digest0))
|
||||
goto out;
|
||||
|
||||
/* Read digest information */
|
||||
ret = read_digest_info(digest0, digest);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
/* Get hash algorithm */
|
||||
ret = hash_lookup_algo(digest->hash, &hash_algo);
|
||||
if (ret) {
|
||||
log_debug("Unsupported hash: %s\n", digest->hash);
|
||||
ret = -ENOTSUPP;
|
||||
goto out;
|
||||
}
|
||||
md_type = hash_mbedtls_type(hash_algo);
|
||||
|
||||
/* Navigate to keyslots node */
|
||||
keyslots_node = ofnode_find_subnode(root, "keyslots");
|
||||
if (!ofnode_valid(keyslots_node)) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
*md_typep = md_type;
|
||||
*keyslots_nodep = keyslots_node;
|
||||
|
||||
out:
|
||||
memset(json_data, '\0', count * desc->blksz);
|
||||
free(json_data);
|
||||
if (ret)
|
||||
abuf_uninit(fdt_buf);
|
||||
|
||||
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
|
||||
*
|
||||
* Decrypts LUKS2 keyslot key material encrypted with AES-XTS mode.
|
||||
* XTS mode uses 512-byte sectors with sector numbers as tweaks.
|
||||
*
|
||||
* @derived_key: Key derived from passphrase using KDF
|
||||
* @key_size: Size of the derived key in bytes (32 or 64 for XTS)
|
||||
* @km: Encrypted key material buffer
|
||||
* @split_key: Output buffer for decrypted split key
|
||||
* @size: Size of the split key in bytes
|
||||
* Return: 0 on success, negative error code on failure
|
||||
*/
|
||||
static int decrypt_km_xts(const u8 *derived_key, uint key_size, const u8 *km,
|
||||
u8 *split_key, int size)
|
||||
{
|
||||
mbedtls_aes_xts_context ctx;
|
||||
const int blksize = 512;
|
||||
u8 data_unit[16];
|
||||
u64 sector;
|
||||
int ret;
|
||||
|
||||
/* Verify key size is valid for XTS (32 or 64 bytes) */
|
||||
if (key_size != 32 && key_size != 64) {
|
||||
log_err("Unsupported XTS key size: %u\n", key_size);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mbedtls_aes_xts_init(&ctx);
|
||||
ret = mbedtls_aes_xts_setkey_dec(&ctx, derived_key, key_size * 8);
|
||||
if (ret) {
|
||||
log_err("Failed to set XTS key: %d\n", ret);
|
||||
mbedtls_aes_xts_free(&ctx);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* XTS uses data unit (sector) as tweak
|
||||
* LUKS2 uses 512-byte sectors for keyslot area
|
||||
* Sector number is relative to start of keyslot area (not absolute)
|
||||
*/
|
||||
sector = 0;
|
||||
|
||||
/*
|
||||
* Decrypt in chunks (XTS requires whole sectors)
|
||||
* Each sector has its own data_unit/tweak value
|
||||
*/
|
||||
for (u64 pos = 0; pos < size; pos += blksize) {
|
||||
uint todo;
|
||||
|
||||
todo = (size - pos > blksize) ? blksize : (size - pos);
|
||||
|
||||
/* Prepare data_unit (sector number in little-endian) */
|
||||
memset(data_unit, '\0', sizeof(data_unit));
|
||||
for (int i = 0; i < 8; i++)
|
||||
data_unit[i] = (sector >> (i * 8)) & 0xFF;
|
||||
|
||||
ret = mbedtls_aes_crypt_xts(&ctx, MBEDTLS_AES_DECRYPT, todo,
|
||||
data_unit, km + pos,
|
||||
split_key + pos);
|
||||
if (ret) {
|
||||
log_err("XTS decryption failed at sector %llu: %d\n",
|
||||
sector, ret);
|
||||
mbedtls_aes_xts_free(&ctx);
|
||||
return -EINVAL;
|
||||
}
|
||||
sector++;
|
||||
}
|
||||
|
||||
mbedtls_aes_xts_free(&ctx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* decrypt_km_cbc() - Decrypt key material using CBC mode
|
||||
*
|
||||
* Decrypts LUKS keyslot key material encrypted with AES-CBC mode.
|
||||
* Supports both ESSIV mode and plain CBC with zero IV.
|
||||
*
|
||||
* @derived_key: Key derived from passphrase using KDF
|
||||
* @key_size: Size of the derived key in bytes
|
||||
* @encrypt: Encryption-specification string (may contain "essiv")
|
||||
* @km: Encrypted key material buffer
|
||||
* @split_key: Output buffer for decrypted split key
|
||||
* @size: Size of the split key in bytes
|
||||
* @km_blocks: Number of blocks in key material
|
||||
* @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)
|
||||
{
|
||||
u8 expkey[AES256_EXPAND_KEY_LENGTH];
|
||||
|
||||
aes_expand_key(derived_key, key_size * 8, expkey);
|
||||
|
||||
/* Check if ESSIV mode is used */
|
||||
if (strstr(encrypt, "essiv")) {
|
||||
essiv_decrypt(derived_key, key_size, expkey, km, split_key,
|
||||
km_blocks, blksz);
|
||||
} else {
|
||||
/* Plain CBC with zero IV */
|
||||
u8 iv[AES_BLOCK_LENGTH];
|
||||
|
||||
memset(iv, '\0', sizeof(iv));
|
||||
aes_cbc_decrypt_blocks(key_size * 8, expkey, iv, km, split_key,
|
||||
size / AES_BLOCK_LENGTH);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* LUKS2-specific: Unlock using PBKDF2 keyslot */
|
||||
/**
|
||||
* try_keyslot_pbkdf2() - Try to decrypt a LUKS2 keyslot using PBKDF2
|
||||
*
|
||||
* Attempts to decrypt a LUKS2 keyslot using the PBKDF2 key derivation function.
|
||||
* This involves deriving a key from the passphrase, reading the encrypted key
|
||||
* material from disk, decrypting it (using either XTS or CBC mode), and
|
||||
* recovering the candidate key through anti-forensic splitting.
|
||||
*
|
||||
* @blk: Block device containing the LUKS2 volume
|
||||
* @pinfo: Partition information for the LUKS2 volume
|
||||
* @ks: Keyslot information including KDF parameters and encryption area
|
||||
* @pass: User passphrase to try
|
||||
* @md_type: mbedtls message digest type for PBKDF2
|
||||
* @cand_key: Output buffer for the recovered candidate key
|
||||
* 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)
|
||||
{
|
||||
struct blk_desc *desc = dev_get_uclass_plat(blk);
|
||||
int ret, km_blocks, size;
|
||||
u8 derived_key[128];
|
||||
u8 *split_key, *km;
|
||||
|
||||
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);
|
||||
if (ret)
|
||||
return -EPROTO;
|
||||
|
||||
size = ks->key_size * ks->af.stripes;
|
||||
km_blocks = (size + desc->blksz - 1) / desc->blksz;
|
||||
|
||||
/* Allocate buffers */
|
||||
split_key = malloc(size);
|
||||
km = malloc_cache_aligned(km_blocks * desc->blksz);
|
||||
if (!split_key || !km) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Read encrypted key material */
|
||||
ret = blk_read(blk, pinfo->start + (ks->area.offset / desc->blksz),
|
||||
km_blocks, km);
|
||||
if (ret != km_blocks) {
|
||||
ret = -EIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Decrypt key material */
|
||||
if (strstr(ks->area.encryption, "xts"))
|
||||
ret = decrypt_km_xts(derived_key, ks->area.key_size, km,
|
||||
split_key, size);
|
||||
else
|
||||
ret = decrypt_km_cbc(derived_key, ks->area.key_size,
|
||||
ks->area.encryption, km, split_key, size,
|
||||
km_blocks, desc->blksz);
|
||||
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
/* AF-merge to recover candidate key */
|
||||
ret = af_merge(split_key, cand_key, ks->key_size, ks->af.stripes,
|
||||
ks->af.hash);
|
||||
|
||||
out:
|
||||
if (split_key) {
|
||||
memset(split_key, '\0', size);
|
||||
free(split_key);
|
||||
}
|
||||
if (km) {
|
||||
memset(km, '\0', km_blocks * desc->blksz);
|
||||
free(km);
|
||||
}
|
||||
memset(derived_key, '\0', sizeof(derived_key));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* 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)
|
||||
{
|
||||
struct blk_desc *desc = dev_get_uclass_plat(blk);
|
||||
int ret, km_blocks, size;
|
||||
u8 derived_key[128];
|
||||
u8 *split_key, *km;
|
||||
|
||||
log_debug("LUKS2: trying keyslot with Argon2id (t=%u, m=%u, p=%u)\n",
|
||||
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,
|
||||
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,
|
||||
ks->kdf.salt_len, derived_key,
|
||||
ks->area.key_size);
|
||||
if (ret) {
|
||||
log_err("Argon2id failed: %s\n", argon2_error_message(ret));
|
||||
return -EPROTO;
|
||||
}
|
||||
log_debug("LUKS2 Argon2: key derivation succeeded\n");
|
||||
|
||||
size = ks->key_size * ks->af.stripes;
|
||||
km_blocks = (size + desc->blksz - 1) / desc->blksz;
|
||||
|
||||
/* Allocate buffers */
|
||||
split_key = malloc(size);
|
||||
km = malloc_cache_aligned(km_blocks * desc->blksz);
|
||||
if (!split_key || !km) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Read encrypted key material */
|
||||
ret = blk_read(blk, pinfo->start + (ks->area.offset / desc->blksz),
|
||||
km_blocks, km);
|
||||
if (ret != km_blocks) {
|
||||
ret = -EIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
log_debug("LUKS2 Argon2: read %d blocks from offset %llu, encryption=%s\n",
|
||||
km_blocks, ks->area.offset, ks->area.encryption);
|
||||
|
||||
/* Decrypt key material */
|
||||
if (strstr(ks->area.encryption, "xts"))
|
||||
ret = decrypt_km_xts(derived_key, ks->area.key_size,
|
||||
km, split_key, size);
|
||||
else
|
||||
ret = decrypt_km_cbc(derived_key, ks->area.key_size,
|
||||
ks->area.encryption, km, split_key,
|
||||
size, km_blocks, desc->blksz);
|
||||
|
||||
if (ret)
|
||||
goto out;
|
||||
log_debug("LUKS2 Argon2: decryption completed successfully\n");
|
||||
|
||||
/* AF-merge to recover candidate key */
|
||||
log_debug("LUKS2 Argon2: calling AF-merge with key_size=%u, stripes=%u, hash=%s\n",
|
||||
ks->key_size, ks->af.stripes, ks->af.hash);
|
||||
ret = af_merge(split_key, cand_key, ks->key_size, ks->af.stripes,
|
||||
ks->af.hash);
|
||||
log_debug("LUKS2 Argon2: AF-merge returned %d\n", ret);
|
||||
|
||||
out:
|
||||
if (split_key) {
|
||||
memset(split_key, '\0', size);
|
||||
free(split_key);
|
||||
}
|
||||
if (km) {
|
||||
memset(km, '\0', km_blocks * desc->blksz);
|
||||
free(km);
|
||||
}
|
||||
memset(derived_key, '\0', sizeof(derived_key));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* verify_master_key() - Verify a candidate master key against the digest
|
||||
*
|
||||
* This function takes a candidate master key (successfully derived from a
|
||||
* keyslot) and verifies it matches the stored digest using the appropriate KDF.
|
||||
*
|
||||
* @digest: Digest information (KDF type, parameters, expected digest value)
|
||||
* @md_type: mbedtls message digest type (for PBKDF2)
|
||||
* @cand_key: The candidate master key to verify
|
||||
* @key_size: Size of the candidate key
|
||||
* @master_key: Output buffer for verified master key
|
||||
* @key_sizep: Output pointer for key size
|
||||
* Return: 0 if verified and copied to master_key, -EACCES if mismatch, -ve on
|
||||
* error
|
||||
*/
|
||||
static int verify_master_key(const struct luks2_digest *digest,
|
||||
mbedtls_md_type_t md_type,
|
||||
const u8 *cand_key, uint key_size, u8 *master_key,
|
||||
uint *key_sizep)
|
||||
{
|
||||
u8 calculated_digest[128];
|
||||
int ret;
|
||||
|
||||
log_debug("LUKS2: keyslot unlock succeeded, verifying digest (type=%d)\n",
|
||||
digest->type);
|
||||
|
||||
/* Verify against digest using the appropriate KDF */
|
||||
if (digest->type == LUKS2_KDF_PBKDF2) {
|
||||
/* PBKDF2 digest verification */
|
||||
log_debug("LUKS2: verifying with PBKDF2 (iters=%u, saltlen=%d, digestlen=%d)\n",
|
||||
digest->iters, digest->salt_len, digest->digest_len);
|
||||
ret = mbedtls_pkcs5_pbkdf2_hmac_ext(md_type, cand_key,
|
||||
key_size, digest->salt,
|
||||
digest->salt_len,
|
||||
digest->iters,
|
||||
digest->digest_len,
|
||||
calculated_digest);
|
||||
if (ret) {
|
||||
log_debug("PBKDF2 digest hash failed: %d\n", ret);
|
||||
return -EACCES;
|
||||
}
|
||||
} else {
|
||||
/* Argon2 digest verification */
|
||||
log_debug("LUKS2: verifying with Argon2 (t=%u, m=%u, p=%u)\n",
|
||||
digest->time, digest->memory, digest->cpus);
|
||||
ret = argon2id_hash_raw(digest->time, digest->memory,
|
||||
digest->cpus, cand_key, key_size,
|
||||
digest->salt, digest->salt_len,
|
||||
calculated_digest, digest->digest_len);
|
||||
if (ret) {
|
||||
log_debug("Argon2 digest hash failed: %s\n",
|
||||
argon2_error_message(ret));
|
||||
return -EACCES;
|
||||
}
|
||||
}
|
||||
|
||||
log_debug("LUKS2: digest calculated, comparing...\n");
|
||||
if (memcmp(calculated_digest, digest->digest, digest->digest_len)) {
|
||||
log_debug("LUKS2: digest mismatch!\n");
|
||||
return -EACCES;
|
||||
}
|
||||
|
||||
log_debug("LUKS2: digest match, unlock successful\n");
|
||||
memcpy(master_key, cand_key, key_size);
|
||||
*key_sizep = key_size;
|
||||
|
||||
return 0; /* Success! */
|
||||
}
|
||||
|
||||
/**
|
||||
* try_unlock_keyslot() - Try to unlock a single keyslot and verify master key
|
||||
*
|
||||
* This function attempts to unlock one keyslot by:
|
||||
* 1. Reading keyslot metadata from ofnode
|
||||
* 2. Deriving the candidate master key using the appropriate KDF
|
||||
* 3. Verifying the candidate key against the stored digest
|
||||
*
|
||||
* @blk: Block device containing the LUKS partition
|
||||
* @pinfo: Partition information
|
||||
* @keyslot_node: ofnode for this specific keyslot
|
||||
* @digest: Digest information for verification
|
||||
* @md_type: mbedtls message digest type (for PBKDF2)
|
||||
* @pass: User-provided 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
|
||||
*/
|
||||
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)
|
||||
{
|
||||
struct luks2_keyslot keyslot;
|
||||
u8 cand_key[128];
|
||||
uint key_size;
|
||||
int ret;
|
||||
|
||||
/* Read keyslot information */
|
||||
ret = read_keyslot_info(keyslot_node, &keyslot, digest->hash);
|
||||
if (ret) {
|
||||
/* Skip unsupported or invalid keyslots */
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
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);
|
||||
} 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);
|
||||
}
|
||||
log_debug("LUKS2: keyslot try returned %d\n", ret);
|
||||
|
||||
if (!ret) {
|
||||
/* Verify the candidate key against the digest */
|
||||
ret = verify_master_key(digest, md_type, cand_key,
|
||||
keyslot.key_size, master_key,
|
||||
&key_size);
|
||||
memset(cand_key, '\0', sizeof(cand_key));
|
||||
if (!ret) {
|
||||
*key_sizep = key_size;
|
||||
return 0; /* Success! */
|
||||
}
|
||||
/* Verification failed, continue trying */
|
||||
}
|
||||
|
||||
memset(cand_key, '\0', sizeof(cand_key));
|
||||
|
||||
return -EAGAIN; /* Continue trying other keyslots */
|
||||
}
|
||||
|
||||
int unlock_luks2(struct udevice *blk, struct disk_partition *pinfo,
|
||||
const char *pass, u8 *master_key, uint *key_sizep)
|
||||
{
|
||||
ofnode keyslots_node, keyslot_node;
|
||||
struct luks2_digest digest;
|
||||
mbedtls_md_type_t md_type;
|
||||
struct abuf fdt_buf;
|
||||
int ret;
|
||||
|
||||
/* Read and parse LUKS2 header and metadata */
|
||||
ret = read_luks2_info(blk, pinfo, &fdt_buf, &digest, &md_type,
|
||||
&keyslots_node);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Try each keyslot until one succeeds */
|
||||
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);
|
||||
if (!ret) /* Successfully unlocked! */
|
||||
break;
|
||||
|
||||
/* -EAGAIN means skip, other errors also continue trying */
|
||||
}
|
||||
abuf_uninit(&fdt_buf);
|
||||
if (ret) {
|
||||
if (ret == -EAGAIN) /* no usable slots */
|
||||
log_debug("LUKS2: no supported keyslots found\n");
|
||||
else /* no slots worked */
|
||||
log_debug("LUKS2: wrong passphrase\n");
|
||||
ret = -EACCES;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -27,4 +27,17 @@
|
||||
int af_merge(const u8 *src, u8 *dst, size_t key_size, uint stripes,
|
||||
const char *hash_spec);
|
||||
|
||||
/**
|
||||
* unlock_luks2() - Unlock a LUKS2 partition with a passphrase
|
||||
*
|
||||
* @blk: Block device
|
||||
* @pinfo: Partition information
|
||||
* @pass: Passphrase to unlock the partition
|
||||
* @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);
|
||||
|
||||
#endif /* __LUKS_INTERNAL_H__ */
|
||||
|
||||
Reference in New Issue
Block a user