filevault/
volume_header.rs1use crate::error::FileVaultError;
7use crate::read::{bytes16, le_u16, le_u32, le_u64};
8
9const CS_SIGNATURE_LE: u16 = 0x5343;
12const ENCRYPTION_METHOD_AES_XTS_128: u32 = 2;
14
15const METADATA_BLOCK_SLOTS: usize = 4;
17
18#[derive(Debug, Clone)]
20pub struct VolumeHeader {
21 pub block_size: u32,
24 pub bytes_per_sector: u32,
26 pub physical_volume_size: u64,
28 pub metadata_block_numbers: [u64; METADATA_BLOCK_SLOTS],
31 pub key_data: [u8; 16],
34 pub physical_volume_identifier: [u8; 16],
37}
38
39impl VolumeHeader {
40 pub fn parse(data: &[u8]) -> Result<Self, FileVaultError> {
46 let signature = le_u16(data, 88);
47 if signature != CS_SIGNATURE_LE {
48 return Err(FileVaultError::NotCoreStorage { found: signature });
49 }
50
51 let encryption_method = le_u32(data, 172);
52 if encryption_method != ENCRYPTION_METHOD_AES_XTS_128 {
53 return Err(FileVaultError::UnsupportedEncryptionMethod {
54 found: encryption_method,
55 });
56 }
57
58 let mut metadata_block_numbers = [0u64; METADATA_BLOCK_SLOTS];
59 for (i, slot) in metadata_block_numbers.iter_mut().enumerate() {
60 *slot = le_u64(data, 104 + i * 8);
61 }
62
63 Ok(VolumeHeader {
64 block_size: le_u32(data, 96),
65 bytes_per_sector: le_u32(data, 48),
66 physical_volume_size: le_u64(data, 64),
67 metadata_block_numbers,
68 key_data: bytes16(data, 176),
69 physical_volume_identifier: bytes16(data, 304),
70 })
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 fn ground_truth_header() -> [u8; 512] {
81 let mut h = [0u8; 512];
82 h[88] = b'C';
83 h[89] = b'S';
84 h[48..52].copy_from_slice(&512u32.to_le_bytes());
85 h[96..100].copy_from_slice(&4096u32.to_le_bytes());
86 h[64..72].copy_from_slice(&536_829_952u64.to_le_bytes());
87 h[172..176].copy_from_slice(&2u32.to_le_bytes());
88 for (i, block) in [1u64, 1025, 129_013, 130_037].iter().enumerate() {
89 h[104 + i * 8..104 + i * 8 + 8].copy_from_slice(&block.to_le_bytes());
90 }
91 h[176..192].copy_from_slice(&hex("18eaeb7da9ab0852ead69e9dabc86f59"));
92 h[304..320].copy_from_slice(&hex("3273a0553b8b47e8b970df35eecda81b"));
93 h
94 }
95
96 fn hex(s: &str) -> [u8; 16] {
97 let mut out = [0u8; 16];
98 for (i, byte) in out.iter_mut().enumerate() {
99 *byte = u8::from_str_radix(&s[i * 2..i * 2 + 2], 16).unwrap();
100 }
101 out
102 }
103
104 #[test]
105 fn parses_ground_truth_fields() {
106 let header = VolumeHeader::parse(&ground_truth_header()).unwrap();
107 assert_eq!(header.block_size, 4096);
108 assert_eq!(header.bytes_per_sector, 512);
109 assert_eq!(header.physical_volume_size, 536_829_952);
110 assert_eq!(header.metadata_block_numbers, [1, 1025, 129_013, 130_037]);
111 assert_eq!(header.key_data, hex("18eaeb7da9ab0852ead69e9dabc86f59"));
112 assert_eq!(
113 header.physical_volume_identifier,
114 hex("3273a0553b8b47e8b970df35eecda81b")
115 );
116 }
117
118 #[test]
119 fn rejects_non_corestorage() {
120 let mut h = ground_truth_header();
121 h[88] = b'X';
122 let err = VolumeHeader::parse(&h).unwrap_err();
123 assert!(
124 matches!(err, FileVaultError::NotCoreStorage { found } if found & 0x00ff == u16::from(b'X'))
125 );
126 }
127
128 #[test]
129 fn rejects_unsupported_encryption_method() {
130 let mut h = ground_truth_header();
131 h[172..176].copy_from_slice(&7u32.to_le_bytes());
132 let err = VolumeHeader::parse(&h).unwrap_err();
133 assert!(matches!(
134 err,
135 FileVaultError::UnsupportedEncryptionMethod { found: 7 }
136 ));
137 }
138
139 #[test]
140 fn short_input_is_not_corestorage() {
141 assert!(matches!(
142 VolumeHeader::parse(&[0u8; 10]),
143 Err(FileVaultError::NotCoreStorage { .. })
144 ));
145 }
146}