mirror of
https://gitlab.com/cryptsetup/cryptsetup.git
synced 2025-12-05 16:00:05 +01:00
bitlk: implement validation of FVE metadata
This commit implements FVE metadata block validation based on: * CRC-32 (to detect random corruption); * AES-CCM-encrypted SHA-256 (to detect malicious manipulations). The hash-based validation requires us to decrypt the VMK first, so it's only performed when obtaining the volume key. This allows us to detect corrupted/altered FVE metadata blocks and pick the valid one (before this commit: the first FVE metadata block is always selected). Fixes: #953 tests: add BitLocker image with corrupted headers The image contains 2 manually corrupted metadata blocks (out of 3), the library should use the third one to correctly load the volume. Signed-off-by: Maxim Suhanov <dfirblog@gmail.com>
This commit is contained in:
committed by
Milan Broz
parent
9cfdd6ba06
commit
68d4749d8a
@@ -111,6 +111,7 @@ struct bitlk_superblock {
|
||||
struct bitlk_fve_metadata {
|
||||
/* FVE metadata block header */
|
||||
uint8_t signature[8];
|
||||
/* size of this block (in 16-byte units) */
|
||||
uint16_t fve_size;
|
||||
uint16_t fve_version;
|
||||
uint16_t curr_state;
|
||||
@@ -132,6 +133,32 @@ struct bitlk_fve_metadata {
|
||||
uint64_t creation_time;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct bitlk_validation_hash {
|
||||
uint16_t size;
|
||||
uint16_t role;
|
||||
uint16_t type;
|
||||
uint16_t flags;
|
||||
/* likely a hash type code, anything other than 0x2005 isn't supported */
|
||||
uint16_t hash_type;
|
||||
uint16_t unknown1;
|
||||
/* SHA-256 */
|
||||
uint8_t hash[32];
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct bitlk_fve_metadata_validation {
|
||||
/* FVE metadata validation block header */
|
||||
uint16_t validation_size;
|
||||
uint16_t validation_version;
|
||||
uint32_t fve_crc32;
|
||||
/* this is a single nested structure's header defined here for simplicity */
|
||||
uint16_t nested_struct_size;
|
||||
uint16_t nested_struct_role;
|
||||
uint16_t nested_struct_type;
|
||||
uint16_t nested_struct_flags;
|
||||
/* datum containing a similar nested structure (encrypted using VMK) with hash (SHA256) */
|
||||
uint8_t nested_struct_data[BITLK_VALIDATION_VMK_DATA_SIZE];
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct bitlk_entry_header_block {
|
||||
uint64_t offset;
|
||||
uint64_t size;
|
||||
@@ -361,6 +388,54 @@ static int parse_vmk_entry(struct crypt_device *cd, uint8_t *data, int start, in
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool check_fve_metadata(struct bitlk_fve_metadata *fve)
|
||||
{
|
||||
if (memcmp(fve->signature, BITLK_SIGNATURE, sizeof(fve->signature)) || le16_to_cpu(fve->fve_version) != 2 ||
|
||||
(fve->fve_size << 4) > BITLK_FVE_METADATA_SIZE)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool check_fve_metadata_validation(struct bitlk_fve_metadata_validation *validation)
|
||||
{
|
||||
/* only check if there is room for CRC-32, the actual size must be larger */
|
||||
if (le16_to_cpu(validation->validation_size) < 8 || le16_to_cpu(validation->validation_version > 2))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parse_fve_metadata_validation(struct bitlk_metadata *params, struct bitlk_fve_metadata_validation *validation)
|
||||
{
|
||||
/* extra checks for a nested structure (MAC) and BITLK FVE metadata */
|
||||
|
||||
if (le16_to_cpu(validation->validation_size) < sizeof(struct bitlk_fve_metadata_validation))
|
||||
return false;
|
||||
|
||||
if (le16_to_cpu(validation->nested_struct_size != BITLK_VALIDATION_VMK_HEADER_SIZE + BITLK_VALIDATION_VMK_DATA_SIZE) ||
|
||||
le16_to_cpu(validation->nested_struct_role) != 0 ||
|
||||
le16_to_cpu(validation->nested_struct_type) != 5)
|
||||
return false;
|
||||
|
||||
/* nonce */
|
||||
memcpy(params->validation->nonce,
|
||||
validation->nested_struct_data,
|
||||
BITLK_NONCE_SIZE);
|
||||
|
||||
/* MAC tag */
|
||||
memcpy(params->validation->mac_tag,
|
||||
validation->nested_struct_data + BITLK_NONCE_SIZE,
|
||||
BITLK_VMK_MAC_TAG_SIZE);
|
||||
|
||||
/* AES-CCM encrypted datum with SHA256 hash */
|
||||
memcpy(params->validation->enc_datum,
|
||||
validation->nested_struct_data + BITLK_NONCE_SIZE + BITLK_VMK_MAC_TAG_SIZE,
|
||||
BITLK_VALIDATION_VMK_DATA_SIZE - BITLK_NONCE_SIZE - BITLK_VMK_MAC_TAG_SIZE);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void BITLK_bitlk_fvek_free(struct bitlk_fvek *fvek)
|
||||
{
|
||||
if (!fvek)
|
||||
@@ -391,6 +466,7 @@ void BITLK_bitlk_metadata_free(struct bitlk_metadata *metadata)
|
||||
|
||||
free(metadata->guid);
|
||||
free(metadata->description);
|
||||
free(metadata->validation);
|
||||
BITLK_bitlk_vmk_free(metadata->vmks);
|
||||
BITLK_bitlk_fvek_free(metadata->fvek);
|
||||
}
|
||||
@@ -402,20 +478,25 @@ int BITLK_read_sb(struct crypt_device *cd, struct bitlk_metadata *params)
|
||||
struct bitlk_signature sig = {};
|
||||
struct bitlk_superblock sb = {};
|
||||
struct bitlk_fve_metadata fve = {};
|
||||
struct bitlk_fve_metadata_validation validation = {};
|
||||
struct bitlk_entry_vmk entry_vmk = {};
|
||||
uint8_t *fve_entries = NULL;
|
||||
uint8_t *fve_validated_block = NULL;
|
||||
size_t fve_entries_size = 0;
|
||||
uint32_t fve_metadata_size = 0;
|
||||
uint32_t fve_size_real = 0;
|
||||
int fve_offset = 0;
|
||||
char guid_buf[UUID_STR_LEN] = {0};
|
||||
uint16_t entry_size = 0;
|
||||
uint16_t entry_type = 0;
|
||||
int i = 0;
|
||||
int r = 0;
|
||||
int valid_fve_metadata_idx = -1;
|
||||
int start = 0;
|
||||
size_t key_size = 0;
|
||||
const char *key = NULL;
|
||||
char *description = NULL;
|
||||
struct crypt_hash *hash;
|
||||
|
||||
struct bitlk_vmk *vmk = NULL;
|
||||
struct bitlk_vmk *vmk_p = params->vmks;
|
||||
@@ -490,15 +571,80 @@ int BITLK_read_sb(struct crypt_device *cd, struct bitlk_metadata *params)
|
||||
for (i = 0; i < 3; i++)
|
||||
params->metadata_offset[i] = le64_to_cpu(sb.fve_offset[i]);
|
||||
|
||||
log_dbg(cd, "Reading BITLK FVE metadata of size %zu on device %s, offset %" PRIu64 ".",
|
||||
sizeof(fve), device_path(device), params->metadata_offset[0]);
|
||||
fve_validated_block = malloc(BITLK_FVE_METADATA_SIZE);
|
||||
if (fve_validated_block == NULL) {
|
||||
r = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* read FVE metadata from the first metadata area */
|
||||
if (read_lseek_blockwise(devfd, device_block_size(cd, device),
|
||||
device_alignment(device), &fve, sizeof(fve), params->metadata_offset[0]) != sizeof(fve) ||
|
||||
memcmp(fve.signature, BITLK_SIGNATURE, sizeof(fve.signature)) ||
|
||||
le16_to_cpu(fve.fve_version) != 2) {
|
||||
log_err(cd, _("Failed to read BITLK FVE metadata from %s."), device_path(device));
|
||||
for (i = 0; i < 3; i++) {
|
||||
/* iterate over FVE metadata copies and pick the valid one */
|
||||
log_dbg(cd, "Reading BITLK FVE metadata copy #%d of size %zu on device %s, offset %" PRIu64 ".",
|
||||
i, sizeof(fve), device_path(device), params->metadata_offset[i]);
|
||||
|
||||
if (read_lseek_blockwise(devfd, device_block_size(cd, device),
|
||||
device_alignment(device), &fve, sizeof(fve), params->metadata_offset[i]) != sizeof(fve) ||
|
||||
!check_fve_metadata(&fve) ||
|
||||
(fve_size_real = le16_to_cpu(fve.fve_size) << 4, read_lseek_blockwise(devfd, device_block_size(cd, device),
|
||||
device_alignment(device), &validation, sizeof(validation), params->metadata_offset[i] + fve_size_real) != sizeof(validation)) ||
|
||||
!check_fve_metadata_validation(&validation) ||
|
||||
/* double-fetch is here, but we aren't validating MAC */
|
||||
read_lseek_blockwise(devfd, device_block_size(cd, device), device_alignment(device), fve_validated_block, fve_size_real,
|
||||
params->metadata_offset[i]) != fve_size_real ||
|
||||
(crypt_crc32(~0, fve_validated_block, fve_size_real) ^ ~0) != le32_to_cpu(validation.fve_crc32)) {
|
||||
/* found an invalid FVE metadata copy, log and skip */
|
||||
log_dbg(cd, _("Failed to read or validate BITLK FVE metadata copy #%d from %s."), i, device_path(device));
|
||||
} else {
|
||||
/* found a valid FVE metadata copy, use it */
|
||||
valid_fve_metadata_idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (valid_fve_metadata_idx < 0) {
|
||||
/* all FVE metadata copies are invalid, fail */
|
||||
log_err(cd, _("Failed to read and validate BITLK FVE metadata from %s."), device_path(device));
|
||||
r = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* check that a valid FVE metadata block is in its expected location */
|
||||
if (params->metadata_offset[valid_fve_metadata_idx] != le64_to_cpu(fve.fve_offset[valid_fve_metadata_idx])) {
|
||||
log_err(cd, _("Failed to validate the location of BITLK FVE metadata from %s."), device_path(device));
|
||||
r = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* update offsets from a valid FVE metadata copy */
|
||||
for (i = 0; i < 3; i++)
|
||||
params->metadata_offset[i] = le64_to_cpu(fve.fve_offset[i]);
|
||||
|
||||
/* check that the FVE metadata hasn't changed between reads, because we are preparing for the MAC check */
|
||||
if (memcmp(&fve, fve_validated_block, sizeof(fve)) != 0) {
|
||||
log_err(cd, _("BITLK FVE metadata changed between reads from %s."), device_path(device));
|
||||
r = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
crypt_backend_memzero(¶ms->sha256_fve, 32);
|
||||
if (crypt_hash_init(&hash, "sha256")) {
|
||||
log_err(cd, _("Failed to hash BITLK FVE metadata read from %s."), device_path(device));
|
||||
r = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
crypt_hash_write(hash, (const char *)fve_validated_block, fve_size_real);
|
||||
crypt_hash_final(hash, (char *)¶ms->sha256_fve, 32);
|
||||
crypt_hash_destroy(hash);
|
||||
|
||||
/* do some extended checks against FVE metadata, but not including MAC verification */
|
||||
params->validation = malloc(sizeof(struct bitlk_validation));
|
||||
if (!params->validation) {
|
||||
r = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!parse_fve_metadata_validation(params, &validation)) {
|
||||
log_err(cd, _("Failed to parse BITLK FVE validation metadata from %s."), device_path(device));
|
||||
r = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
@@ -583,17 +729,18 @@ int BITLK_read_sb(struct crypt_device *cd, struct bitlk_metadata *params)
|
||||
}
|
||||
memset(fve_entries, 0, fve_entries_size);
|
||||
|
||||
log_dbg(cd, "Reading BITLK FVE metadata entries of size %zu on device %s, offset %" PRIu64 ".",
|
||||
fve_entries_size, device_path(device), params->metadata_offset[0] + BITLK_FVE_METADATA_HEADERS_LEN);
|
||||
log_dbg(cd, "Getting BITLK FVE metadata entries of size %zu on device %s, offset %" PRIu64 ".",
|
||||
fve_entries_size, device_path(device), params->metadata_offset[valid_fve_metadata_idx] + BITLK_FVE_METADATA_HEADERS_LEN);
|
||||
|
||||
if (read_lseek_blockwise(devfd, device_block_size(cd, device),
|
||||
device_alignment(device), fve_entries, fve_entries_size,
|
||||
params->metadata_offset[0] + BITLK_FVE_METADATA_HEADERS_LEN) != (ssize_t)fve_entries_size) {
|
||||
log_err(cd, _("Failed to read BITLK metadata entries from %s."), device_path(device));
|
||||
if (BITLK_FVE_METADATA_HEADERS_LEN + fve_entries_size > fve_size_real) {
|
||||
log_err(cd, _("Failed to check BITLK metadata entries previously read from %s."), device_path(device));
|
||||
r = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* fetch these entries from validated buffer to avoid double-fetch */
|
||||
memcpy(fve_entries, fve_validated_block + BITLK_FVE_METADATA_HEADERS_LEN, fve_entries_size);
|
||||
|
||||
while ((fve_entries_size - start) >= (sizeof(entry_size) + sizeof(entry_type))) {
|
||||
|
||||
/* size of this entry */
|
||||
@@ -716,6 +863,8 @@ int BITLK_read_sb(struct crypt_device *cd, struct bitlk_metadata *params)
|
||||
}
|
||||
out:
|
||||
free(fve_entries);
|
||||
free(fve_validated_block);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -1110,6 +1259,7 @@ int BITLK_get_volume_key(struct crypt_device *cd,
|
||||
struct volume_key *open_vmk_key = NULL;
|
||||
struct volume_key *vmk_dec_key = NULL;
|
||||
struct volume_key *recovery_key = NULL;
|
||||
struct bitlk_validation_hash dec_hash = {};
|
||||
const struct bitlk_vmk *next_vmk = NULL;
|
||||
|
||||
next_vmk = params->vmks;
|
||||
@@ -1172,6 +1322,36 @@ int BITLK_get_volume_key(struct crypt_device *cd,
|
||||
}
|
||||
crypt_free_volume_key(vmk_dec_key);
|
||||
|
||||
log_dbg(cd, "Trying to decrypt validation metadata using VMK.");
|
||||
r = crypt_bitlk_decrypt_key(crypt_volume_key_get_key(open_vmk_key),
|
||||
crypt_volume_key_length(open_vmk_key),
|
||||
(const char*)params->validation->enc_datum,
|
||||
(char *)&dec_hash,
|
||||
BITLK_VALIDATION_VMK_DATA_SIZE - BITLK_NONCE_SIZE - BITLK_VMK_MAC_TAG_SIZE,
|
||||
(const char*)params->validation->nonce, BITLK_NONCE_SIZE,
|
||||
(const char*)params->validation->mac_tag, BITLK_VMK_MAC_TAG_SIZE);
|
||||
if (r < 0) {
|
||||
log_dbg(cd, "Failed to decrypt validation metadata using VMK.");
|
||||
crypt_free_volume_key(open_vmk_key);
|
||||
if (r == -ENOTSUP)
|
||||
return r;
|
||||
break;
|
||||
}
|
||||
|
||||
/* now, do the MAC validation */
|
||||
if (le16_to_cpu(dec_hash.role) != 0 ||le16_to_cpu(dec_hash.type) != 1 ||
|
||||
(le16_to_cpu(dec_hash.hash_type) != 0x2005)) {
|
||||
log_dbg(cd, "Failed to parse decrypted validation metadata.");
|
||||
crypt_free_volume_key(open_vmk_key);
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
if (memcmp(dec_hash.hash, params->sha256_fve, sizeof(dec_hash.hash)) != 0) {
|
||||
log_dbg(cd, "Failed MAC validation of BITLK FVE metadata.");
|
||||
crypt_free_volume_key(open_vmk_key);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
r = decrypt_key(cd, open_fvek_key, params->fvek->vk, open_vmk_key,
|
||||
params->fvek->mac_tag, BITLK_VMK_MAC_TAG_SIZE,
|
||||
params->fvek->nonce, BITLK_NONCE_SIZE, true);
|
||||
|
||||
@@ -21,6 +21,8 @@ struct volume_key;
|
||||
#define BITLK_NONCE_SIZE 12
|
||||
#define BITLK_SALT_SIZE 16
|
||||
#define BITLK_VMK_MAC_TAG_SIZE 16
|
||||
#define BITLK_VALIDATION_VMK_HEADER_SIZE 8
|
||||
#define BITLK_VALIDATION_VMK_DATA_SIZE 72
|
||||
|
||||
#define BITLK_STATE_NORMAL 0x0004
|
||||
|
||||
@@ -85,6 +87,13 @@ struct bitlk_fvek {
|
||||
struct volume_key *vk;
|
||||
};
|
||||
|
||||
struct bitlk_validation {
|
||||
uint8_t mac_tag[BITLK_VMK_MAC_TAG_SIZE];
|
||||
uint8_t nonce[BITLK_NONCE_SIZE];
|
||||
/* technically, this is not "VMK", but some sources call it this way */
|
||||
uint8_t enc_datum[BITLK_VALIDATION_VMK_DATA_SIZE];
|
||||
};
|
||||
|
||||
struct bitlk_metadata {
|
||||
uint16_t sector_size;
|
||||
uint64_t volume_size;
|
||||
@@ -101,8 +110,10 @@ struct bitlk_metadata {
|
||||
uint32_t metadata_version;
|
||||
uint64_t volume_header_offset;
|
||||
uint64_t volume_header_size;
|
||||
const char *sha256_fve[32];
|
||||
struct bitlk_vmk *vmks;
|
||||
struct bitlk_fvek *fvek;
|
||||
struct bitlk_validation *validation;
|
||||
};
|
||||
|
||||
int BITLK_read_sb(struct crypt_device *cd, struct bitlk_metadata *params);
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user