Skip to main content

bitlocker/
metadata.rs

1//! FVE metadata block, header, and entry parsing.
2//!
3//! A BitLocker volume carries three copies of an FVE metadata block. Each block
4//! is a `-FVE-FS-` block header, a 48-byte metadata header (cipher, volume GUID,
5//! creation time), and a recursive array of metadata entries — the key
6//! protectors (VMK), the FVEK, and the volume-header-block descriptor. See
7//! `docs/RESEARCH.md`.
8
9use crate::bytes::{le_u16, le_u32, le_u64, read_guid, slice_owned};
10
11const FVE_SIGNATURE: &[u8; 8] = b"-FVE-FS-";
12
13/// Metadata-entry type: a Volume Master Key protector.
14pub const ENTRY_TYPE_VMK: u16 = 0x0002;
15/// Metadata-entry type: the Full Volume Encryption Key.
16pub const ENTRY_TYPE_FVEK: u16 = 0x0003;
17/// Metadata-entry type / value type: the volume-header block descriptor.
18pub const ENTRY_TYPE_VOLUME_HEADER: u16 = 0x000f;
19
20/// Value type: an AES-CCM encrypted key.
21pub const VALUE_TYPE_AES_CCM: u16 = 0x0005;
22/// Value type: a stretch key (salt + nested AES-CCM key).
23pub const VALUE_TYPE_STRETCH: u16 = 0x0003;
24/// Value type: a Volume Master Key protector.
25pub const VALUE_TYPE_VMK: u16 = 0x0008;
26
27/// Key-protection type: password.
28pub const PROTECTION_PASSWORD: u16 = 0x2000;
29
30/// One FVE metadata entry (`entry_type`, `value_type`, `version`, and the raw
31/// value data that follows the 8-byte entry header).
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct MetadataEntry {
34    /// Entry type (e.g. [`ENTRY_TYPE_VMK`]).
35    pub entry_type: u16,
36    /// Value type (e.g. [`VALUE_TYPE_AES_CCM`]).
37    pub value_type: u16,
38    /// Entry version (typically 1).
39    pub version: u16,
40    /// The value data following the 8-byte entry header.
41    pub data: Vec<u8>,
42}
43
44impl MetadataEntry {
45    /// Parse a flat sequence of metadata entries from `data`.
46    ///
47    /// Each entry is `size(u16) | entry_type(u16) | value_type(u16) |
48    /// version(u16) | value_data`. Parsing stops on a size below the 8-byte
49    /// header, an entry that would run past the buffer, or the end of `data` —
50    /// never looping forever on a lying size.
51    #[must_use]
52    pub fn parse_sequence(data: &[u8]) -> Vec<MetadataEntry> {
53        let mut out = Vec::new();
54        let mut pos = 0usize;
55        while pos + 8 <= data.len() {
56            let size = le_u16(data, pos) as usize;
57            if size < 8 || pos + size > data.len() {
58                break;
59            }
60            out.push(MetadataEntry {
61                entry_type: le_u16(data, pos + 2),
62                value_type: le_u16(data, pos + 4),
63                version: le_u16(data, pos + 6),
64                data: slice_owned(data, pos + 8, size - 8),
65            });
66            pos += size;
67        }
68        out
69    }
70
71    /// Parse this entry's value data (from `offset`) as a nested entry sequence.
72    #[must_use]
73    pub fn nested(&self, offset: usize) -> Vec<MetadataEntry> {
74        let start = offset.min(self.data.len());
75        Self::parse_sequence(&self.data[start..])
76    }
77
78    /// Whether this entry is a VMK protector.
79    #[must_use]
80    pub fn is_vmk(&self) -> bool {
81        self.entry_type == ENTRY_TYPE_VMK && self.value_type == VALUE_TYPE_VMK
82    }
83
84    /// The key-protection type of a VMK entry (protector type at value offset 26),
85    /// or `None` for a non-VMK entry.
86    #[must_use]
87    pub fn protection_type(&self) -> Option<u16> {
88        self.is_vmk().then(|| le_u16(&self.data, 26))
89    }
90}
91
92/// A parsed FVE metadata block: cipher, identity, and the entry array, plus the
93/// block-header fields the read path needs (encrypted size and the relocated
94/// volume-header region).
95#[derive(Debug, Clone)]
96pub struct FveMetadata {
97    /// Volume encryption method (metadata header offset 36).
98    pub encryption_method: u16,
99    /// Volume identifier GUID (metadata header offset 16).
100    pub volume_guid: [u8; 16],
101    /// Volume creation time as a Windows FILETIME (metadata header offset 40).
102    pub creation_time: u64,
103    /// The metadata entries.
104    pub entries: Vec<MetadataEntry>,
105    /// Number of still-encrypted bytes from the front (block header, v2 offset 16).
106    /// Zero means "whole volume".
107    pub encrypted_volume_size: u64,
108    /// Byte offset where the original volume header is stored, relocated.
109    pub volume_header_offset: u64,
110    /// Size in bytes of the relocated volume-header region.
111    pub volume_header_size: u64,
112    /// Byte offsets of the three metadata blocks (read back as zeros).
113    pub metadata_offsets: [u64; 3],
114    /// Size of the metadata region (metadata header offset 0).
115    pub metadata_size: u32,
116}
117
118impl FveMetadata {
119    /// Parse an FVE metadata block from bytes beginning at its block header.
120    ///
121    /// Returns `None` when the `-FVE-FS-` block-header signature is absent, so
122    /// the caller can try the next candidate block offset.
123    #[must_use]
124    pub fn parse(block: &[u8], bytes_per_sector: u16) -> Option<FveMetadata> {
125        if block.get(0..8) != Some(FVE_SIGNATURE.as_slice()) {
126            return None;
127        }
128
129        let encrypted_volume_size = le_u64(block, 16);
130        let num_volume_header_sectors = le_u32(block, 28);
131        let metadata_offsets = [le_u64(block, 32), le_u64(block, 40), le_u64(block, 48)];
132        let block_volume_header_offset = le_u64(block, 56);
133
134        // FVE metadata header starts at block offset 64.
135        let mh = 64usize;
136        let metadata_size = le_u32(block, mh);
137        let volume_guid = read_guid(block, mh + 16);
138        let encryption_method = le_u16(block, mh + 36);
139        let creation_time = le_u64(block, mh + 40);
140
141        // Entries follow the 48-byte metadata header, bounded by metadata_size.
142        let entries_start = mh + 48;
143        let entries_end = (mh + metadata_size as usize).min(block.len());
144        let entries = if entries_end > entries_start {
145            MetadataEntry::parse_sequence(&block[entries_start..entries_end])
146        } else {
147            Vec::new()
148        };
149
150        // Resolve the relocated volume-header region: prefer the dedicated
151        // volume-header-block entry (type 0x000f), else the block-header fields.
152        let mut volume_header_offset = block_volume_header_offset;
153        let mut volume_header_size =
154            u64::from(num_volume_header_sectors) * u64::from(bytes_per_sector);
155        if let Some(e) = entries
156            .iter()
157            .find(|e| e.entry_type == ENTRY_TYPE_VOLUME_HEADER)
158        {
159            let bo = le_u64(&e.data, 0);
160            let bs = le_u64(&e.data, 8);
161            if bo != 0 {
162                volume_header_offset = bo;
163            }
164            if bs != 0 {
165                volume_header_size = bs;
166            }
167        }
168
169        Some(FveMetadata {
170            encryption_method,
171            volume_guid,
172            creation_time,
173            entries,
174            encrypted_volume_size,
175            volume_header_offset,
176            volume_header_size,
177            metadata_offsets,
178            metadata_size,
179        })
180    }
181
182    /// Iterate the VMK protector entries.
183    pub fn vmk_entries(&self) -> impl Iterator<Item = &MetadataEntry> {
184        self.entries.iter().filter(|e| e.is_vmk())
185    }
186
187    /// The key-protection types present, in metadata order.
188    #[must_use]
189    pub fn protector_types(&self) -> Vec<u16> {
190        self.vmk_entries()
191            .filter_map(MetadataEntry::protection_type)
192            .collect()
193    }
194
195    /// The top-level FVEK entry (an AES-CCM encrypted key wrapped by the VMK).
196    #[must_use]
197    pub fn fvek_entry(&self) -> Option<&MetadataEntry> {
198        self.entries
199            .iter()
200            .find(|e| e.entry_type == ENTRY_TYPE_FVEK && e.value_type == VALUE_TYPE_AES_CCM)
201    }
202}
203
204#[cfg(test)]
205mod tests {
206    use super::*;
207
208    fn entry_bytes(entry_type: u16, value_type: u16, version: u16, data: &[u8]) -> Vec<u8> {
209        let size = (8 + data.len()) as u16;
210        let mut v = Vec::new();
211        v.extend_from_slice(&size.to_le_bytes());
212        v.extend_from_slice(&entry_type.to_le_bytes());
213        v.extend_from_slice(&value_type.to_le_bytes());
214        v.extend_from_slice(&version.to_le_bytes());
215        v.extend_from_slice(data);
216        v
217    }
218
219    #[test]
220    fn parse_sequence_splits_entries() {
221        let mut buf = Vec::new();
222        buf.extend(entry_bytes(0x000f, 0x000f, 1, &[1, 2, 3, 4]));
223        buf.extend(entry_bytes(ENTRY_TYPE_VMK, VALUE_TYPE_VMK, 1, &[9; 20]));
224        let entries = MetadataEntry::parse_sequence(&buf);
225        assert_eq!(entries.len(), 2);
226        assert_eq!(entries[0].entry_type, 0x000f);
227        assert_eq!(entries[0].data, vec![1, 2, 3, 4]);
228        assert!(entries[1].is_vmk());
229    }
230
231    #[test]
232    fn parse_sequence_stops_on_lying_size() {
233        // size field claims 8 (empty) then a size of 0 — must not loop forever.
234        let mut buf = entry_bytes(1, 2, 1, &[]);
235        buf.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 0]); // size=0 -> stop
236        let entries = MetadataEntry::parse_sequence(&buf);
237        assert_eq!(entries.len(), 1);
238    }
239
240    #[test]
241    fn parse_sequence_stops_on_oversize() {
242        // size claims 100 but only 8 bytes present.
243        let mut buf = Vec::new();
244        buf.extend_from_slice(&100u16.to_le_bytes());
245        buf.extend_from_slice(&[0u8; 6]);
246        assert!(MetadataEntry::parse_sequence(&buf).is_empty());
247    }
248
249    fn build_block(entries: &[Vec<u8>]) -> Vec<u8> {
250        let mut entry_bytes = Vec::new();
251        for e in entries {
252            entry_bytes.extend_from_slice(e);
253        }
254        let metadata_size = 48 + entry_bytes.len();
255        let mut block = vec![0u8; 64 + metadata_size];
256        block[0..8].copy_from_slice(FVE_SIGNATURE);
257        block[10..12].copy_from_slice(&2u16.to_le_bytes()); // version 2
258        block[16..24].copy_from_slice(&0x0400_0000u64.to_le_bytes()); // encrypted size
259        block[28..32].copy_from_slice(&16u32.to_le_bytes()); // vol header sectors
260        block[56..64].copy_from_slice(&0x0211_0800u64.to_le_bytes()); // vol header offset
261                                                                      // metadata header @64
262        block[64..68].copy_from_slice(&(metadata_size as u32).to_le_bytes());
263        block[64 + 16..64 + 32].copy_from_slice(&[0xAB; 16]); // volume guid
264        block[64 + 36..64 + 38].copy_from_slice(&0x8000u16.to_le_bytes()); // method
265        block[64 + 40..64 + 48].copy_from_slice(&130_461_864_497_281_120u64.to_le_bytes());
266        block[64 + 48..].copy_from_slice(&entry_bytes);
267        block
268    }
269
270    #[test]
271    fn parse_full_block() {
272        // A volume-header-block entry (0x000f) + a password VMK.
273        let mut vh_data = Vec::new();
274        vh_data.extend_from_slice(&0x0211_0800u64.to_le_bytes()); // block offset
275        vh_data.extend_from_slice(&0x0051_5a00u64.to_le_bytes()); // block size
276        let vh = entry_bytes(
277            ENTRY_TYPE_VOLUME_HEADER,
278            ENTRY_TYPE_VOLUME_HEADER,
279            1,
280            &vh_data,
281        );
282
283        let mut vmk_data = vec![0u8; 28];
284        vmk_data[26..28].copy_from_slice(&PROTECTION_PASSWORD.to_le_bytes());
285        let vmk = entry_bytes(ENTRY_TYPE_VMK, VALUE_TYPE_VMK, 1, &vmk_data);
286
287        let block = build_block(&[vh, vmk]);
288        let m = FveMetadata::parse(&block, 512).unwrap();
289        assert_eq!(m.encryption_method, 0x8000);
290        assert_eq!(m.volume_guid, [0xAB; 16]);
291        assert_eq!(m.creation_time, 130_461_864_497_281_120);
292        assert_eq!(m.encrypted_volume_size, 0x0400_0000);
293        assert_eq!(m.volume_header_offset, 0x0211_0800);
294        assert_eq!(m.volume_header_size, 0x0051_5a00); // from the 0x000f entry
295        assert_eq!(m.entries.len(), 2);
296        assert_eq!(m.protector_types(), vec![PROTECTION_PASSWORD]);
297    }
298
299    #[test]
300    fn parse_returns_none_without_signature() {
301        let block = vec![0u8; 128];
302        assert!(FveMetadata::parse(&block, 512).is_none());
303    }
304
305    #[test]
306    fn volume_header_size_falls_back_to_sector_count() {
307        // No 0x000f entry: size = num_volume_header_sectors * bytes_per_sector.
308        let block = build_block(&[]);
309        let m = FveMetadata::parse(&block, 512).unwrap();
310        assert_eq!(m.volume_header_size, 16 * 512);
311        assert_eq!(m.volume_header_offset, 0x0211_0800);
312    }
313
314    #[test]
315    fn truncated_block_does_not_panic() {
316        let mut block = build_block(&[]);
317        block.truncate(70);
318        let _ = FveMetadata::parse(&block, 512);
319    }
320}