1use std::io::{Read, Seek, SeekFrom};
10
11use crate::crypto::{aes_ccm_unwrap, password_hash, stretch_key, SectorCipher};
12use crate::error::{BdeError, Result};
13use crate::header::VolumeHeader;
14use crate::metadata::{FveMetadata, PROTECTION_PASSWORD, VALUE_TYPE_AES_CCM, VALUE_TYPE_STRETCH};
15
16const METHOD_AES128_CBC_DIFFUSER: u16 = 0x8000;
18const METHOD_NONE: u16 = 0x0000;
20const SECTOR_SIZE: usize = 512;
22const METADATA_READ_LEN: usize = 64 * 1024;
25
26pub struct BitLockerVolume;
29
30impl BitLockerVolume {
31 pub fn read_metadata<R: Read + Seek>(reader: &mut R) -> Result<FveMetadata> {
39 let mut header = [0u8; SECTOR_SIZE];
40 reader.seek(SeekFrom::Start(0))?;
41 read_fill(reader, &mut header)?;
42 let volume_header = VolumeHeader::parse(&header)?;
43
44 let offsets = volume_header.fve_metadata_offsets;
45 for &offset in &offsets {
46 if offset == 0 {
47 continue;
48 }
49 let mut block = vec![0u8; METADATA_READ_LEN];
50 reader.seek(SeekFrom::Start(offset))?;
51 let n = read_available(reader, &mut block)?;
52 block.truncate(n);
53 if let Some(meta) = FveMetadata::parse(&block, volume_header.bytes_per_sector) {
54 return Ok(meta);
55 }
56 }
57 Err(BdeError::NoValidMetadata { offsets })
58 }
59
60 pub fn unlock_with_password<R: Read + Seek>(
67 mut reader: R,
68 password: &str,
69 ) -> Result<DecryptedVolume<R>> {
70 let metadata = Self::read_metadata(&mut reader)?;
71
72 if metadata.encryption_method != METHOD_AES128_CBC_DIFFUSER {
73 return Err(BdeError::UnsupportedEncryptionMethod {
74 method: metadata.encryption_method,
75 });
76 }
77
78 let cipher = derive_cipher(&metadata, password)?;
79 let total_size = reader.seek(SeekFrom::End(0))?;
80
81 Ok(DecryptedVolume {
82 reader,
83 cipher,
84 metadata,
85 total_size,
86 position: 0,
87 })
88 }
89}
90
91fn derive_cipher(metadata: &FveMetadata, password: &str) -> Result<SectorCipher> {
93 let vmk = metadata
95 .vmk_entries()
96 .find(|e| e.protection_type() == Some(PROTECTION_PASSWORD))
97 .ok_or_else(|| BdeError::NoPasswordProtector {
98 found: metadata.protector_types(),
99 })?;
100
101 let props = vmk.nested(28);
103 let stretch = props
104 .iter()
105 .find(|e| e.value_type == VALUE_TYPE_STRETCH)
106 .ok_or(BdeError::MissingKeyMaterial {
107 what: "stretch key",
108 })?;
109 let vmk_ccm = props
110 .iter()
111 .find(|e| e.value_type == VALUE_TYPE_AES_CCM)
112 .ok_or(BdeError::MissingKeyMaterial {
113 what: "VMK AES-CCM key",
114 })?;
115
116 let mut salt = [0u8; 16];
118 let salt_src = stretch
119 .data
120 .get(4..20)
121 .ok_or(BdeError::MissingKeyMaterial {
122 what: "stretch salt",
123 })?;
124 salt.copy_from_slice(salt_src);
125
126 let stretched = stretch_key(&password_hash(password), &salt);
128 let vmk_container =
129 aes_ccm_unwrap(&stretched, &vmk_ccm.data).ok_or(BdeError::AuthenticationFailed {
130 what: "volume master key",
131 })?;
132 let vmk_key = take_key32(&vmk_container, 12, "volume master key")?;
133
134 let fvek_entry = metadata
136 .fvek_entry()
137 .ok_or(BdeError::MissingKeyMaterial { what: "FVEK entry" })?;
138 let fvek_container = aes_ccm_unwrap(&vmk_key, &fvek_entry.data)
139 .ok_or(BdeError::AuthenticationFailed { what: "FVEK" })?;
140 let fvek = take_key16(&fvek_container, 12, "FVEK")?;
141 let tweak = take_key16(&fvek_container, 44, "FVEK")?;
142
143 Ok(SectorCipher::new(fvek, tweak))
144}
145
146fn take_key32(container: &[u8], off: usize, what: &'static str) -> Result<[u8; 32]> {
147 let s = container
148 .get(off..off + 32)
149 .ok_or(BdeError::MalformedKeyContainer {
150 what,
151 got: container.len(),
152 need: off + 32,
153 })?;
154 let mut k = [0u8; 32];
155 k.copy_from_slice(s);
156 Ok(k)
157}
158
159fn take_key16(container: &[u8], off: usize, what: &'static str) -> Result<[u8; 16]> {
160 let s = container
161 .get(off..off + 16)
162 .ok_or(BdeError::MalformedKeyContainer {
163 what,
164 got: container.len(),
165 need: off + 16,
166 })?;
167 let mut k = [0u8; 16];
168 k.copy_from_slice(s);
169 Ok(k)
170}
171
172pub struct DecryptedVolume<R> {
174 reader: R,
175 cipher: SectorCipher,
176 metadata: FveMetadata,
177 total_size: u64,
178 position: u64,
179}
180
181impl<R: Read + Seek> DecryptedVolume<R> {
182 #[must_use]
184 pub fn metadata(&self) -> &FveMetadata {
185 &self.metadata
186 }
187
188 #[must_use]
190 pub fn volume_size(&self) -> u64 {
191 self.total_size
192 }
193
194 pub fn read_at(&mut self, offset: u64, buf: &mut [u8]) -> Result<()> {
200 let mut done = 0usize;
201 while done < buf.len() {
202 let pos = offset + done as u64;
203 let sector_start = pos - (pos % SECTOR_SIZE as u64);
204 let within = (pos - sector_start) as usize;
205 let plain = self.decrypt_logical_sector(sector_start)?;
206 let take = (SECTOR_SIZE - within).min(buf.len() - done);
207 buf[done..done + take].copy_from_slice(&plain[within..within + take]);
208 done += take;
209 }
210 Ok(())
211 }
212
213 fn decrypt_logical_sector(&mut self, sector_start: u64) -> Result<[u8; SECTOR_SIZE]> {
217 let meta_size = u64::from(self.metadata.metadata_size);
219 for &m in &self.metadata.metadata_offsets {
220 if m != 0 && sector_start >= m && sector_start < m + meta_size {
221 return Ok([0u8; SECTOR_SIZE]);
222 }
223 }
224
225 let physical = if sector_start < self.metadata.volume_header_size {
228 self.metadata.volume_header_offset + sector_start
229 } else {
230 sector_start
231 };
232
233 let mut ct = [0u8; SECTOR_SIZE];
234 self.reader.seek(SeekFrom::Start(physical))?;
235 read_available(&mut self.reader, &mut ct)?;
236
237 let evs = self.metadata.encrypted_volume_size;
239 if self.metadata.encryption_method == METHOD_NONE || (evs != 0 && physical >= evs) {
240 return Ok(ct);
241 }
242
243 let plain = self.cipher.decrypt_sector(&ct, physical);
244 let mut out = [0u8; SECTOR_SIZE];
245 let n = plain.len().min(SECTOR_SIZE);
246 out[..n].copy_from_slice(&plain[..n]);
247 Ok(out)
248 }
249}
250
251impl<R: Read + Seek> Read for DecryptedVolume<R> {
252 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
253 if self.position >= self.total_size {
254 return Ok(0);
255 }
256 let remaining = self.total_size - self.position;
257 let n = (buf.len() as u64).min(remaining) as usize;
258 self.read_at(self.position, &mut buf[..n])
259 .map_err(|e| std::io::Error::other(e.to_string()))?;
260 self.position += n as u64;
261 Ok(n)
262 }
263}
264
265impl<R: Read + Seek> Seek for DecryptedVolume<R> {
266 fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
267 let new = match pos {
268 SeekFrom::Start(o) => i128::from(o),
269 SeekFrom::End(o) => i128::from(self.total_size) + i128::from(o),
270 SeekFrom::Current(o) => i128::from(self.position) + i128::from(o),
271 };
272 if new < 0 {
273 return Err(std::io::Error::new(
274 std::io::ErrorKind::InvalidInput,
275 "seek before start",
276 ));
277 }
278 self.position = new as u64;
279 Ok(self.position)
280 }
281}
282
283fn read_fill<R: Read>(reader: &mut R, buf: &mut [u8]) -> Result<()> {
285 reader.read_exact(buf)?;
286 Ok(())
287}
288
289fn read_available<R: Read>(reader: &mut R, buf: &mut [u8]) -> Result<usize> {
292 let mut filled = 0;
293 while filled < buf.len() {
294 match reader.read(&mut buf[filled..]) {
295 Ok(0) => break,
296 Ok(n) => filled += n,
297 Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => {}
298 Err(e) => return Err(e.into()),
299 }
300 }
301 for b in &mut buf[filled..] {
302 *b = 0;
303 }
304 Ok(filled)
305}
306
307#[cfg(test)]
308mod tests {
309 use super::*;
310 use crate::crypto::{aes_ccm_wrap, password_hash, stretch_key, SectorCipher};
311 use crate::metadata::{
312 ENTRY_TYPE_FVEK, ENTRY_TYPE_VMK, ENTRY_TYPE_VOLUME_HEADER, PROTECTION_PASSWORD,
313 };
314 use std::io::Cursor;
315
316 const RELOCATED_OFFSET: u64 = 0x4000;
317 const META_BLOCK_OFFSET: u64 = 0x1000;
318 const IMAGE_SIZE: usize = 0x5000;
319 const ENCRYPTED_SIZE: u64 = 0x4800;
322
323 fn entry(entry_type: u16, value_type: u16, data: &[u8]) -> Vec<u8> {
324 let size = (8 + data.len()) as u16;
325 let mut v = Vec::new();
326 v.extend_from_slice(&size.to_le_bytes());
327 v.extend_from_slice(&entry_type.to_le_bytes());
328 v.extend_from_slice(&value_type.to_le_bytes());
329 v.extend_from_slice(&1u16.to_le_bytes());
330 v.extend_from_slice(data);
331 v
332 }
333
334 fn build_volume(password: &str) -> (Vec<u8>, [u8; 512]) {
337 let salt = [0x33u8; 16];
338 let fvek = [0x11u8; 16];
339 let tweak = [0x22u8; 16];
340 let vmk = [0x44u8; 32];
341
342 let mut vmk_container = vec![0u8; 44];
343 vmk_container[12..44].copy_from_slice(&vmk);
344 let stretched = stretch_key(&password_hash(password), &salt);
345 let vmk_ccm = aes_ccm_wrap(&stretched, &[0x55; 12], &vmk_container);
346
347 let mut fvek_container = vec![0u8; 76];
348 fvek_container[12..28].copy_from_slice(&fvek);
349 fvek_container[44..60].copy_from_slice(&tweak);
350 let fvek_ccm = aes_ccm_wrap(&vmk, &[0x66; 12], &fvek_container);
351
352 let mut stretch_data = vec![0u8; 4]; stretch_data.extend_from_slice(&salt);
354 let stretch_entry = entry(0, VALUE_TYPE_STRETCH, &stretch_data);
355 let vmk_ccm_entry = entry(0, VALUE_TYPE_AES_CCM, &vmk_ccm);
356
357 let mut vmk_data = vec![0u8; 28];
358 vmk_data[26..28].copy_from_slice(&PROTECTION_PASSWORD.to_le_bytes());
359 vmk_data.extend_from_slice(&stretch_entry);
360 vmk_data.extend_from_slice(&vmk_ccm_entry);
361 let vmk_entry = entry(ENTRY_TYPE_VMK, VALUE_TYPE_VMK, &vmk_data);
362
363 let fvek_entry = entry(ENTRY_TYPE_FVEK, VALUE_TYPE_AES_CCM, &fvek_ccm);
364
365 let mut vh_data = Vec::new();
366 vh_data.extend_from_slice(&RELOCATED_OFFSET.to_le_bytes());
367 vh_data.extend_from_slice(&512u64.to_le_bytes());
368 let vh_entry = entry(ENTRY_TYPE_VOLUME_HEADER, ENTRY_TYPE_VOLUME_HEADER, &vh_data);
369
370 let mut entries = Vec::new();
371 entries.extend_from_slice(&vh_entry);
372 entries.extend_from_slice(&vmk_entry);
373 entries.extend_from_slice(&fvek_entry);
374 let metadata_size = 48 + entries.len();
375
376 let mut image = vec![0u8; IMAGE_SIZE];
377 image[0..3].copy_from_slice(&[0xeb, 0x58, 0x90]);
378 image[3..11].copy_from_slice(b"MSWIN4.1");
379 image[12] = 0x02; image[440..448].copy_from_slice(&META_BLOCK_OFFSET.to_le_bytes());
381
382 let mb = META_BLOCK_OFFSET as usize;
383 image[mb..mb + 8].copy_from_slice(b"-FVE-FS-");
384 image[mb + 10..mb + 12].copy_from_slice(&2u16.to_le_bytes());
385 image[mb + 16..mb + 24].copy_from_slice(&ENCRYPTED_SIZE.to_le_bytes());
386 image[mb + 28..mb + 32].copy_from_slice(&1u32.to_le_bytes());
387 image[mb + 32..mb + 40].copy_from_slice(&META_BLOCK_OFFSET.to_le_bytes());
388 image[mb + 56..mb + 64].copy_from_slice(&RELOCATED_OFFSET.to_le_bytes());
389 image[mb + 64..mb + 68].copy_from_slice(&(metadata_size as u32).to_le_bytes());
390 image[mb + 64 + 36..mb + 64 + 38].copy_from_slice(&0x8000u16.to_le_bytes());
391 image[mb + 64 + 48..mb + 64 + 48 + entries.len()].copy_from_slice(&entries);
392
393 let mut plain = [0u8; 512];
394 for (i, b) in plain.iter_mut().enumerate() {
395 *b = (i as u8) ^ 0x5a;
396 }
397 let cipher = SectorCipher::new(fvek, tweak);
398 let ct = cipher.encrypt_sector(&plain, RELOCATED_OFFSET);
399 let ro = RELOCATED_OFFSET as usize;
400 image[ro..ro + 512].copy_from_slice(&ct);
401
402 (image, plain)
403 }
404
405 use crate::metadata::{VALUE_TYPE_AES_CCM, VALUE_TYPE_STRETCH, VALUE_TYPE_VMK};
406
407 #[test]
408 fn unlock_and_read_synthetic_volume() {
409 let (image, plain) = build_volume("test-pw");
413 let mut vol = BitLockerVolume::unlock_with_password(Cursor::new(image), "test-pw").unwrap();
414 let mut buf = [0u8; 512];
415 vol.read_at(0, &mut buf).unwrap();
416 assert_eq!(buf, plain);
417 assert_eq!(vol.metadata().encryption_method, 0x8000);
418
419 let mut z = [1u8; 512];
421 vol.read_at(META_BLOCK_OFFSET, &mut z).unwrap();
422 assert_eq!(z, [0u8; 512]);
423
424 let mut e = [0u8; 512];
427 vol.read_at(0x600, &mut e).unwrap();
428
429 let mut r = [0u8; 512];
432 vol.read_at(ENCRYPTED_SIZE, &mut r).unwrap();
433 assert_eq!(r, [0u8; 512]);
434 }
435
436 #[test]
437 fn seek_variants_and_eof() {
438 use std::io::{Read as _, Seek as _};
439 let (image, _) = build_volume("test-pw");
440 let mut vol = BitLockerVolume::unlock_with_password(Cursor::new(image), "test-pw").unwrap();
441
442 assert_eq!(vol.seek(SeekFrom::End(0)).unwrap(), IMAGE_SIZE as u64);
443 let mut b = [0u8; 16];
444 assert_eq!(vol.read(&mut b).unwrap(), 0); assert_eq!(vol.seek(SeekFrom::Start(10)).unwrap(), 10);
447 assert_eq!(vol.seek(SeekFrom::Current(5)).unwrap(), 15);
448 assert!(vol.seek(SeekFrom::Current(-100)).is_err()); }
450
451 fn meta_only_image(
454 method: u16,
455 protectors: &[u16],
456 header_offsets: [u64; 3],
457 block_at: Option<usize>,
458 ) -> Vec<u8> {
459 let mut entries = Vec::new();
460 for p in protectors {
461 let mut d = vec![0u8; 28];
462 d[26..28].copy_from_slice(&p.to_le_bytes());
463 entries.extend(entry(0x0002, 0x0008, &d));
464 }
465 let metadata_size = 48 + entries.len();
466 let mut image = vec![0u8; 0x2000];
467 image[0..3].copy_from_slice(&[0xeb, 0x58, 0x90]);
468 image[3..11].copy_from_slice(b"MSWIN4.1");
469 image[12] = 0x02;
470 image[440..448].copy_from_slice(&header_offsets[0].to_le_bytes());
471 image[448..456].copy_from_slice(&header_offsets[1].to_le_bytes());
472 image[456..464].copy_from_slice(&header_offsets[2].to_le_bytes());
473 if let Some(mb) = block_at {
474 image[mb..mb + 8].copy_from_slice(b"-FVE-FS-");
475 image[mb + 10..mb + 12].copy_from_slice(&2u16.to_le_bytes());
476 image[mb + 32..mb + 40].copy_from_slice(&(mb as u64).to_le_bytes());
477 image[mb + 64..mb + 68].copy_from_slice(&(metadata_size as u32).to_le_bytes());
478 image[mb + 64 + 36..mb + 64 + 38].copy_from_slice(&method.to_le_bytes());
479 image[mb + 64 + 48..mb + 64 + 48 + entries.len()].copy_from_slice(&entries);
480 }
481 image
482 }
483
484 #[test]
485 fn unsupported_method_errors() {
486 let img = meta_only_image(0x8004, &[0x2000], [0x1000, 0, 0], Some(0x1000));
487 let res = BitLockerVolume::unlock_with_password(Cursor::new(img), "x");
488 assert!(matches!(
489 res,
490 Err(BdeError::UnsupportedEncryptionMethod { method: 0x8004 })
491 ));
492 }
493
494 #[test]
495 fn no_password_protector_errors() {
496 let img = meta_only_image(0x8000, &[0x0800], [0x1000, 0, 0], Some(0x1000));
498 let res = BitLockerVolume::unlock_with_password(Cursor::new(img), "x");
499 assert!(matches!(res, Err(BdeError::NoPasswordProtector { .. })));
500 }
501
502 #[test]
503 fn no_valid_metadata_errors() {
504 let img = meta_only_image(0x8000, &[0x2000], [0x1000, 0x1200, 0x1400], None);
506 let err = BitLockerVolume::read_metadata(&mut Cursor::new(img)).unwrap_err();
507 assert!(matches!(err, BdeError::NoValidMetadata { .. }));
508 }
509
510 #[test]
511 fn read_metadata_skips_zero_first_offset() {
512 let img = meta_only_image(0x8000, &[0x2000], [0, 0x1000, 0], Some(0x1000));
514 let meta = BitLockerVolume::read_metadata(&mut Cursor::new(img)).unwrap();
515 assert_eq!(meta.encryption_method, 0x8000);
516 }
517
518 struct FlakyReader {
521 header: Vec<u8>,
522 phase: usize,
523 }
524
525 impl Read for FlakyReader {
526 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
527 self.phase += 1;
528 match self.phase {
529 1 => {
530 let n = buf.len().min(self.header.len());
531 buf[..n].copy_from_slice(&self.header[..n]);
532 Ok(n)
533 }
534 2 => Err(std::io::Error::new(
535 std::io::ErrorKind::Interrupted,
536 "eintr",
537 )),
538 _ => Err(std::io::Error::other("boom")),
539 }
540 }
541 }
542
543 impl Seek for FlakyReader {
544 fn seek(&mut self, _pos: SeekFrom) -> std::io::Result<u64> {
545 Ok(0)
546 }
547 }
548
549 #[test]
550 fn io_error_during_block_read_propagates() {
551 let mut header = vec![0u8; 512];
552 header[0..3].copy_from_slice(&[0xeb, 0x58, 0x90]);
553 header[3..11].copy_from_slice(b"MSWIN4.1");
554 header[12] = 0x02;
555 header[440..448].copy_from_slice(&0x1000u64.to_le_bytes());
556 let mut reader = FlakyReader { header, phase: 0 };
557 let res = BitLockerVolume::read_metadata(&mut reader);
558 assert!(matches!(res, Err(BdeError::Io(_))));
559 }
560
561 #[test]
562 fn wrong_password_fails_authentication() {
563 let (image, _) = build_volume("test-pw");
564 let res = BitLockerVolume::unlock_with_password(Cursor::new(image), "wrong");
565 assert!(matches!(
566 res,
567 Err(BdeError::AuthenticationFailed { what }) if what == "volume master key"
568 ));
569 }
570
571 #[test]
572 fn read_and_seek_traits() {
573 use std::io::{Read as _, Seek as _};
574 let (image, plain) = build_volume("test-pw");
575 let mut vol = BitLockerVolume::unlock_with_password(Cursor::new(image), "test-pw").unwrap();
576 vol.seek(SeekFrom::Start(0)).unwrap();
577 let mut buf = [0u8; 256];
578 vol.read_exact(&mut buf).unwrap();
579 assert_eq!(&buf[..], &plain[..256]);
580 assert_eq!(vol.volume_size(), IMAGE_SIZE as u64);
581 }
582
583 #[test]
584 fn non_bitlocker_reader_errors() {
585 let r = BitLockerVolume::unlock_with_password(Cursor::new(vec![0u8; 1024]), "x");
586 assert!(matches!(r, Err(BdeError::NotBitLocker { .. })));
587 }
588
589 #[test]
590 fn read_metadata_reports_protectors() {
591 let (image, _) = build_volume("test-pw");
592 let meta = BitLockerVolume::read_metadata(&mut Cursor::new(image)).unwrap();
593 assert_eq!(meta.protector_types(), vec![PROTECTION_PASSWORD]);
594 }
595}