use crate::bytes::{le_u16, le_u32, le_u64, read_guid, slice_owned};
const FVE_SIGNATURE: &[u8; 8] = b"-FVE-FS-";
pub const ENTRY_TYPE_VMK: u16 = 0x0002;
pub const ENTRY_TYPE_FVEK: u16 = 0x0003;
pub const ENTRY_TYPE_VOLUME_HEADER: u16 = 0x000f;
pub const VALUE_TYPE_AES_CCM: u16 = 0x0005;
pub const VALUE_TYPE_STRETCH: u16 = 0x0003;
pub const VALUE_TYPE_VMK: u16 = 0x0008;
pub const PROTECTION_PASSWORD: u16 = 0x2000;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MetadataEntry {
pub entry_type: u16,
pub value_type: u16,
pub version: u16,
pub data: Vec<u8>,
}
impl MetadataEntry {
#[must_use]
pub fn parse_sequence(data: &[u8]) -> Vec<MetadataEntry> {
let mut out = Vec::new();
let mut pos = 0usize;
while pos + 8 <= data.len() {
let size = le_u16(data, pos) as usize;
if size < 8 || pos + size > data.len() {
break;
}
out.push(MetadataEntry {
entry_type: le_u16(data, pos + 2),
value_type: le_u16(data, pos + 4),
version: le_u16(data, pos + 6),
data: slice_owned(data, pos + 8, size - 8),
});
pos += size;
}
out
}
#[must_use]
pub fn nested(&self, offset: usize) -> Vec<MetadataEntry> {
let start = offset.min(self.data.len());
Self::parse_sequence(&self.data[start..])
}
#[must_use]
pub fn is_vmk(&self) -> bool {
self.entry_type == ENTRY_TYPE_VMK && self.value_type == VALUE_TYPE_VMK
}
#[must_use]
pub fn protection_type(&self) -> Option<u16> {
self.is_vmk().then(|| le_u16(&self.data, 26))
}
}
#[derive(Debug, Clone)]
pub struct FveMetadata {
pub encryption_method: u16,
pub volume_guid: [u8; 16],
pub creation_time: u64,
pub entries: Vec<MetadataEntry>,
pub encrypted_volume_size: u64,
pub volume_header_offset: u64,
pub volume_header_size: u64,
pub metadata_offsets: [u64; 3],
pub metadata_size: u32,
}
impl FveMetadata {
#[must_use]
pub fn parse(block: &[u8], bytes_per_sector: u16) -> Option<FveMetadata> {
if block.get(0..8) != Some(FVE_SIGNATURE.as_slice()) {
return None;
}
let encrypted_volume_size = le_u64(block, 16);
let num_volume_header_sectors = le_u32(block, 28);
let metadata_offsets = [le_u64(block, 32), le_u64(block, 40), le_u64(block, 48)];
let block_volume_header_offset = le_u64(block, 56);
let mh = 64usize;
let metadata_size = le_u32(block, mh);
let volume_guid = read_guid(block, mh + 16);
let encryption_method = le_u16(block, mh + 36);
let creation_time = le_u64(block, mh + 40);
let entries_start = mh + 48;
let entries_end = (mh + metadata_size as usize).min(block.len());
let entries = if entries_end > entries_start {
MetadataEntry::parse_sequence(&block[entries_start..entries_end])
} else {
Vec::new()
};
let mut volume_header_offset = block_volume_header_offset;
let mut volume_header_size =
u64::from(num_volume_header_sectors) * u64::from(bytes_per_sector);
if let Some(e) = entries
.iter()
.find(|e| e.entry_type == ENTRY_TYPE_VOLUME_HEADER)
{
let bo = le_u64(&e.data, 0);
let bs = le_u64(&e.data, 8);
if bo != 0 {
volume_header_offset = bo;
}
if bs != 0 {
volume_header_size = bs;
}
}
Some(FveMetadata {
encryption_method,
volume_guid,
creation_time,
entries,
encrypted_volume_size,
volume_header_offset,
volume_header_size,
metadata_offsets,
metadata_size,
})
}
pub fn vmk_entries(&self) -> impl Iterator<Item = &MetadataEntry> {
self.entries.iter().filter(|e| e.is_vmk())
}
#[must_use]
pub fn protector_types(&self) -> Vec<u16> {
self.vmk_entries()
.filter_map(MetadataEntry::protection_type)
.collect()
}
#[must_use]
pub fn fvek_entry(&self) -> Option<&MetadataEntry> {
self.entries
.iter()
.find(|e| e.entry_type == ENTRY_TYPE_FVEK && e.value_type == VALUE_TYPE_AES_CCM)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn entry_bytes(entry_type: u16, value_type: u16, version: u16, data: &[u8]) -> Vec<u8> {
let size = (8 + data.len()) as u16;
let mut v = Vec::new();
v.extend_from_slice(&size.to_le_bytes());
v.extend_from_slice(&entry_type.to_le_bytes());
v.extend_from_slice(&value_type.to_le_bytes());
v.extend_from_slice(&version.to_le_bytes());
v.extend_from_slice(data);
v
}
#[test]
fn parse_sequence_splits_entries() {
let mut buf = Vec::new();
buf.extend(entry_bytes(0x000f, 0x000f, 1, &[1, 2, 3, 4]));
buf.extend(entry_bytes(ENTRY_TYPE_VMK, VALUE_TYPE_VMK, 1, &[9; 20]));
let entries = MetadataEntry::parse_sequence(&buf);
assert_eq!(entries.len(), 2);
assert_eq!(entries[0].entry_type, 0x000f);
assert_eq!(entries[0].data, vec![1, 2, 3, 4]);
assert!(entries[1].is_vmk());
}
#[test]
fn parse_sequence_stops_on_lying_size() {
let mut buf = entry_bytes(1, 2, 1, &[]);
buf.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 0]); let entries = MetadataEntry::parse_sequence(&buf);
assert_eq!(entries.len(), 1);
}
#[test]
fn parse_sequence_stops_on_oversize() {
let mut buf = Vec::new();
buf.extend_from_slice(&100u16.to_le_bytes());
buf.extend_from_slice(&[0u8; 6]);
assert!(MetadataEntry::parse_sequence(&buf).is_empty());
}
fn build_block(entries: &[Vec<u8>]) -> Vec<u8> {
let mut entry_bytes = Vec::new();
for e in entries {
entry_bytes.extend_from_slice(e);
}
let metadata_size = 48 + entry_bytes.len();
let mut block = vec![0u8; 64 + metadata_size];
block[0..8].copy_from_slice(FVE_SIGNATURE);
block[10..12].copy_from_slice(&2u16.to_le_bytes()); block[16..24].copy_from_slice(&0x0400_0000u64.to_le_bytes()); block[28..32].copy_from_slice(&16u32.to_le_bytes()); block[56..64].copy_from_slice(&0x0211_0800u64.to_le_bytes()); block[64..68].copy_from_slice(&(metadata_size as u32).to_le_bytes());
block[64 + 16..64 + 32].copy_from_slice(&[0xAB; 16]); block[64 + 36..64 + 38].copy_from_slice(&0x8000u16.to_le_bytes()); block[64 + 40..64 + 48].copy_from_slice(&130_461_864_497_281_120u64.to_le_bytes());
block[64 + 48..].copy_from_slice(&entry_bytes);
block
}
#[test]
fn parse_full_block() {
let mut vh_data = Vec::new();
vh_data.extend_from_slice(&0x0211_0800u64.to_le_bytes()); vh_data.extend_from_slice(&0x0051_5a00u64.to_le_bytes()); let vh = entry_bytes(
ENTRY_TYPE_VOLUME_HEADER,
ENTRY_TYPE_VOLUME_HEADER,
1,
&vh_data,
);
let mut vmk_data = vec![0u8; 28];
vmk_data[26..28].copy_from_slice(&PROTECTION_PASSWORD.to_le_bytes());
let vmk = entry_bytes(ENTRY_TYPE_VMK, VALUE_TYPE_VMK, 1, &vmk_data);
let block = build_block(&[vh, vmk]);
let m = FveMetadata::parse(&block, 512).unwrap();
assert_eq!(m.encryption_method, 0x8000);
assert_eq!(m.volume_guid, [0xAB; 16]);
assert_eq!(m.creation_time, 130_461_864_497_281_120);
assert_eq!(m.encrypted_volume_size, 0x0400_0000);
assert_eq!(m.volume_header_offset, 0x0211_0800);
assert_eq!(m.volume_header_size, 0x0051_5a00); assert_eq!(m.entries.len(), 2);
assert_eq!(m.protector_types(), vec![PROTECTION_PASSWORD]);
}
#[test]
fn parse_returns_none_without_signature() {
let block = vec![0u8; 128];
assert!(FveMetadata::parse(&block, 512).is_none());
}
#[test]
fn volume_header_size_falls_back_to_sector_count() {
let block = build_block(&[]);
let m = FveMetadata::parse(&block, 512).unwrap();
assert_eq!(m.volume_header_size, 16 * 512);
assert_eq!(m.volume_header_offset, 0x0211_0800);
}
#[test]
fn truncated_block_does_not_panic() {
let mut block = build_block(&[]);
block.truncate(70);
let _ = FveMetadata::parse(&block, 512);
}
}