use crate::error::FileVaultError;
use crate::read::{bytes16, le_u16, le_u32, le_u64};
const CS_SIGNATURE_LE: u16 = 0x5343;
const ENCRYPTION_METHOD_AES_XTS_128: u32 = 2;
const METADATA_BLOCK_SLOTS: usize = 4;
#[derive(Debug, Clone)]
pub struct VolumeHeader {
pub block_size: u32,
pub bytes_per_sector: u32,
pub physical_volume_size: u64,
pub metadata_block_numbers: [u64; METADATA_BLOCK_SLOTS],
pub key_data: [u8; 16],
pub physical_volume_identifier: [u8; 16],
}
impl VolumeHeader {
pub fn parse(data: &[u8]) -> Result<Self, FileVaultError> {
let signature = le_u16(data, 88);
if signature != CS_SIGNATURE_LE {
return Err(FileVaultError::NotCoreStorage { found: signature });
}
let encryption_method = le_u32(data, 172);
if encryption_method != ENCRYPTION_METHOD_AES_XTS_128 {
return Err(FileVaultError::UnsupportedEncryptionMethod {
found: encryption_method,
});
}
let mut metadata_block_numbers = [0u64; METADATA_BLOCK_SLOTS];
for (i, slot) in metadata_block_numbers.iter_mut().enumerate() {
*slot = le_u64(data, 104 + i * 8);
}
Ok(VolumeHeader {
block_size: le_u32(data, 96),
bytes_per_sector: le_u32(data, 48),
physical_volume_size: le_u64(data, 64),
metadata_block_numbers,
key_data: bytes16(data, 176),
physical_volume_identifier: bytes16(data, 304),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
fn ground_truth_header() -> [u8; 512] {
let mut h = [0u8; 512];
h[88] = b'C';
h[89] = b'S';
h[48..52].copy_from_slice(&512u32.to_le_bytes());
h[96..100].copy_from_slice(&4096u32.to_le_bytes());
h[64..72].copy_from_slice(&536_829_952u64.to_le_bytes());
h[172..176].copy_from_slice(&2u32.to_le_bytes());
for (i, block) in [1u64, 1025, 129_013, 130_037].iter().enumerate() {
h[104 + i * 8..104 + i * 8 + 8].copy_from_slice(&block.to_le_bytes());
}
h[176..192].copy_from_slice(&hex("18eaeb7da9ab0852ead69e9dabc86f59"));
h[304..320].copy_from_slice(&hex("3273a0553b8b47e8b970df35eecda81b"));
h
}
fn hex(s: &str) -> [u8; 16] {
let mut out = [0u8; 16];
for (i, byte) in out.iter_mut().enumerate() {
*byte = u8::from_str_radix(&s[i * 2..i * 2 + 2], 16).unwrap();
}
out
}
#[test]
fn parses_ground_truth_fields() {
let header = VolumeHeader::parse(&ground_truth_header()).unwrap();
assert_eq!(header.block_size, 4096);
assert_eq!(header.bytes_per_sector, 512);
assert_eq!(header.physical_volume_size, 536_829_952);
assert_eq!(header.metadata_block_numbers, [1, 1025, 129_013, 130_037]);
assert_eq!(header.key_data, hex("18eaeb7da9ab0852ead69e9dabc86f59"));
assert_eq!(
header.physical_volume_identifier,
hex("3273a0553b8b47e8b970df35eecda81b")
);
}
#[test]
fn rejects_non_corestorage() {
let mut h = ground_truth_header();
h[88] = b'X';
let err = VolumeHeader::parse(&h).unwrap_err();
assert!(
matches!(err, FileVaultError::NotCoreStorage { found } if found & 0x00ff == u16::from(b'X'))
);
}
#[test]
fn rejects_unsupported_encryption_method() {
let mut h = ground_truth_header();
h[172..176].copy_from_slice(&7u32.to_le_bytes());
let err = VolumeHeader::parse(&h).unwrap_err();
assert!(matches!(
err,
FileVaultError::UnsupportedEncryptionMethod { found: 7 }
));
}
#[test]
fn short_input_is_not_corestorage() {
assert!(matches!(
VolumeHeader::parse(&[0u8; 10]),
Err(FileVaultError::NotCoreStorage { .. })
));
}
}