Skip to main content

bitlocker/
header.rs

1//! BitLocker volume-header parsing.
2//!
3//! The first 512-byte sector identifies the BitLocker variant and locates the
4//! three FVE metadata blocks. Three on-disk layouts exist (Windows Vista,
5//! Windows 7/10, and BitLocker To Go on FAT); they are distinguished by the
6//! signature at offset 3 and the boot entry at offset 0. This is not a
7//! special-case per image — it is the documented rule for each variant of the
8//! format (see `docs/RESEARCH.md`).
9
10use crate::bytes::{le_u16, le_u64, read_guid};
11use crate::error::{BdeError, Result};
12
13/// Which BitLocker on-disk volume-header layout was recognised.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum BdeVariant {
16    /// Windows Vista (`-FVE-FS-`, boot `EB 52 90`): metadata block 1 is a cluster
17    /// number; the other two offsets come from the metadata block header.
18    WindowsVista,
19    /// Windows 7 and later (`-FVE-FS-`, boot `EB 58 90`).
20    Windows7OrLater,
21    /// BitLocker To Go on a FAT volume (`MSWIN4.1`).
22    BitLockerToGo,
23}
24
25/// The parsed BitLocker volume header.
26#[derive(Debug, Clone)]
27pub struct VolumeHeader {
28    /// Which layout was recognised.
29    pub variant: BdeVariant,
30    /// Bytes per sector (from the BPB); defaults to 512 when the field is zero.
31    pub bytes_per_sector: u16,
32    /// The BitLocker identifier GUID (all-zero for the Vista layout, which stores
33    /// none at a fixed offset).
34    pub bitlocker_guid: [u8; 16],
35    /// Byte offsets of the three FVE metadata blocks relative to the volume start.
36    /// For the Vista layout only the first is derived here (the block header
37    /// carries all three authoritatively).
38    pub fve_metadata_offsets: [u64; 3],
39}
40
41const SIG_FVE: &[u8; 8] = b"-FVE-FS-";
42const SIG_TO_GO: &[u8; 8] = b"MSWIN4.1";
43const BOOT_WIN7: [u8; 3] = [0xeb, 0x58, 0x90];
44
45impl VolumeHeader {
46    /// Parse the 512-byte volume header sector.
47    ///
48    /// # Errors
49    /// Returns [`BdeError::NotBitLocker`] (carrying the offending signature bytes)
50    /// when neither the `-FVE-FS-` nor the `MSWIN4.1` signature is present.
51    pub fn parse(sector: &[u8]) -> Result<VolumeHeader> {
52        let mut signature = [0u8; 8];
53        if let Some(s) = sector.get(3..11) {
54            signature.copy_from_slice(s);
55        }
56        let mut bytes_per_sector = le_u16(sector, 11);
57        if bytes_per_sector == 0 {
58            bytes_per_sector = 512;
59        }
60
61        if &signature == SIG_TO_GO {
62            return Ok(VolumeHeader {
63                variant: BdeVariant::BitLockerToGo,
64                bytes_per_sector,
65                bitlocker_guid: read_guid(sector, 424),
66                fve_metadata_offsets: [
67                    le_u64(sector, 440),
68                    le_u64(sector, 448),
69                    le_u64(sector, 456),
70                ],
71            });
72        }
73
74        if &signature == SIG_FVE {
75            let boot = sector.get(0..3).unwrap_or(&[]);
76            if boot == BOOT_WIN7 {
77                return Ok(VolumeHeader {
78                    variant: BdeVariant::Windows7OrLater,
79                    bytes_per_sector,
80                    bitlocker_guid: read_guid(sector, 160),
81                    fve_metadata_offsets: [
82                        le_u64(sector, 176),
83                        le_u64(sector, 184),
84                        le_u64(sector, 192),
85                    ],
86                });
87            }
88            // Windows Vista: block 1 is a cluster number at offset 56; the cluster
89            // size is bytes-per-sector × sectors-per-cluster (offset 13).
90            let sectors_per_cluster = u64::from(sector.get(13).copied().unwrap_or(1).max(1));
91            let cluster_size = u64::from(bytes_per_sector) * sectors_per_cluster;
92            let block1 = le_u64(sector, 56).saturating_mul(cluster_size);
93            return Ok(VolumeHeader {
94                variant: BdeVariant::WindowsVista,
95                bytes_per_sector,
96                bitlocker_guid: [0u8; 16],
97                fve_metadata_offsets: [block1, 0, 0],
98            });
99        }
100
101        Err(BdeError::NotBitLocker { signature })
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    fn base_sector() -> Vec<u8> {
110        let mut s = vec![0u8; 512];
111        s[11] = 0x00;
112        s[12] = 0x02; // bytes per sector = 512 (LE)
113        s
114    }
115
116    #[test]
117    fn parses_bitlocker_to_go() {
118        let mut s = base_sector();
119        s[0..3].copy_from_slice(&[0xeb, 0x58, 0x90]);
120        s[3..11].copy_from_slice(b"MSWIN4.1");
121        s[424..440].copy_from_slice(&[
122            0x3b, 0xd6, 0x67, 0x49, 0x29, 0x2e, 0xd8, 0x4a, 0x83, 0x99, 0xf6, 0xa3, 0x39, 0xe3,
123            0xd0, 0x01,
124        ]);
125        s[440..448].copy_from_slice(&0x0210_0000u64.to_le_bytes());
126        s[448..456].copy_from_slice(&0x02b5_5800u64.to_le_bytes());
127        s[456..464].copy_from_slice(&0x035a_b000u64.to_le_bytes());
128
129        let h = VolumeHeader::parse(&s).unwrap();
130        assert_eq!(h.variant, BdeVariant::BitLockerToGo);
131        assert_eq!(h.bytes_per_sector, 512);
132        assert_eq!(
133            h.fve_metadata_offsets,
134            [0x0210_0000, 0x02b5_5800, 0x035a_b000]
135        );
136        assert_eq!(
137            crate::format_guid(&h.bitlocker_guid),
138            "4967d63b-2e29-4ad8-8399-f6a339e3d001"
139        );
140    }
141
142    #[test]
143    fn parses_windows7() {
144        let mut s = base_sector();
145        s[0..3].copy_from_slice(&[0xeb, 0x58, 0x90]);
146        s[3..11].copy_from_slice(b"-FVE-FS-");
147        s[176..184].copy_from_slice(&0x1000u64.to_le_bytes());
148        s[184..192].copy_from_slice(&0x2000u64.to_le_bytes());
149        s[192..200].copy_from_slice(&0x3000u64.to_le_bytes());
150
151        let h = VolumeHeader::parse(&s).unwrap();
152        assert_eq!(h.variant, BdeVariant::Windows7OrLater);
153        assert_eq!(h.fve_metadata_offsets, [0x1000, 0x2000, 0x3000]);
154    }
155
156    #[test]
157    fn parses_vista_cluster_offset() {
158        let mut s = base_sector();
159        s[0..3].copy_from_slice(&[0xeb, 0x52, 0x90]);
160        s[3..11].copy_from_slice(b"-FVE-FS-");
161        s[13] = 8; // sectors per cluster
162        s[56..64].copy_from_slice(&100u64.to_le_bytes()); // cluster number
163        let h = VolumeHeader::parse(&s).unwrap();
164        assert_eq!(h.variant, BdeVariant::WindowsVista);
165        // 100 clusters * (512 * 8) = 409600
166        assert_eq!(h.fve_metadata_offsets[0], 100 * 512 * 8);
167    }
168
169    #[test]
170    fn rejects_non_bitlocker_with_signature() {
171        let mut s = base_sector();
172        s[0..3].copy_from_slice(&[0xeb, 0x52, 0x90]);
173        s[3..11].copy_from_slice(b"NTFS    ");
174        let err = VolumeHeader::parse(&s).unwrap_err();
175        assert!(matches!(err, BdeError::NotBitLocker { signature } if &signature == b"NTFS    "));
176    }
177
178    #[test]
179    fn short_sector_does_not_panic() {
180        assert!(matches!(
181            VolumeHeader::parse(&[0u8; 4]),
182            Err(BdeError::NotBitLocker { .. })
183        ));
184    }
185}