use std::io::{self, Read, Seek, SeekFrom};
use crate::error::FileVaultError;
use crate::unlock::VolumeKeys;
use crate::xts;
const SECTOR_SIZE: usize = 512;
#[derive(Debug)]
pub struct DecryptedVolume<R: Read + Seek> {
reader: R,
keys: VolumeKeys,
physical_base: u64,
size: u64,
position: u64,
}
impl<R: Read + Seek> DecryptedVolume<R> {
pub(crate) fn new(reader: R, keys: VolumeKeys, physical_base: u64, size: u64) -> Self {
DecryptedVolume {
reader,
keys,
physical_base,
size,
position: 0,
}
}
#[must_use]
pub fn size(&self) -> u64 {
self.size
}
pub fn read_at(&mut self, offset: u64, buf: &mut [u8]) -> Result<usize, FileVaultError> {
if offset >= self.size || buf.is_empty() {
return Ok(0);
}
let available = self.size - offset;
let want = (buf.len() as u64).min(available) as usize;
let first_sector = offset / SECTOR_SIZE as u64;
let end = offset + want as u64;
let last_sector = (end - 1) / SECTOR_SIZE as u64;
let sector_count = (last_sector - first_sector + 1) as usize;
let region_len =
sector_count
.checked_mul(SECTOR_SIZE)
.ok_or(FileVaultError::OutOfRange {
what: "read region length",
})?;
let mut region = vec![0u8; region_len];
let physical = self.physical_base + first_sector * SECTOR_SIZE as u64;
self.reader.seek(SeekFrom::Start(physical))?;
read_full_or_eof(&mut self.reader, &mut region)?;
xts::decrypt_units(
&mut region,
&self.keys.vmk,
&self.keys.tweak_key,
SECTOR_SIZE,
u128::from(first_sector),
);
let inner = (offset - first_sector * SECTOR_SIZE as u64) as usize;
let slice = region
.get(inner..inner + want)
.ok_or(FileVaultError::OutOfRange {
what: "decrypted window",
})?;
buf.get_mut(..want)
.ok_or(FileVaultError::OutOfRange {
what: "output buffer window",
})?
.copy_from_slice(slice);
Ok(want)
}
}
fn read_full_or_eof<R: Read>(reader: &mut R, buf: &mut [u8]) -> io::Result<()> {
let mut filled = 0;
while filled < buf.len() {
match reader.read(&mut buf[filled..]) {
Ok(0) => break,
Ok(n) => filled += n,
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
Err(e) => return Err(e),
}
}
Ok(())
}
impl<R: Read + Seek> Read for DecryptedVolume<R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let n = self.read_at(self.position, buf).map_err(io::Error::other)?;
self.position += n as u64;
Ok(n)
}
}
impl<R: Read + Seek> Seek for DecryptedVolume<R> {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
let new = match pos {
SeekFrom::Start(o) => o,
SeekFrom::End(o) => add_signed(self.size, o)?,
SeekFrom::Current(o) => add_signed(self.position, o)?,
};
self.position = new;
Ok(new)
}
}
fn add_signed(base: u64, offset: i64) -> io::Result<u64> {
let result = if offset >= 0 {
base.checked_add(offset as u64)
} else {
base.checked_sub(offset.unsigned_abs())
};
result.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "seek out of range"))
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
fn keys() -> VolumeKeys {
VolumeKeys {
vmk: [0x55u8; 16],
tweak_key: [0x66u8; 16],
}
}
fn build_backing(base: u64, plaintext: &[u8], keys: &VolumeKeys) -> Vec<u8> {
let mut cipher = plaintext.to_vec();
crate::xts::encrypt_units(&mut cipher, &keys.vmk, &keys.tweak_key, SECTOR_SIZE, 0);
let mut image = vec![0u8; base as usize];
image.extend_from_slice(&cipher);
image
}
#[test]
fn read_at_decrypts_aligned_sector() {
let k = keys();
let plaintext: Vec<u8> = (0..1024u32).map(|i| (i & 0xff) as u8).collect();
let image = build_backing(4096, &plaintext, &k);
let mut vol =
DecryptedVolume::new(Cursor::new(image), keys(), 4096, plaintext.len() as u64);
let mut buf = [0u8; 512];
assert_eq!(vol.read_at(0, &mut buf).unwrap(), 512);
assert_eq!(&buf[..], &plaintext[..512]);
assert_eq!(vol.read_at(512, &mut buf).unwrap(), 512);
assert_eq!(&buf[..], &plaintext[512..1024]);
}
#[test]
fn read_at_handles_unaligned_window() {
let k = keys();
let plaintext: Vec<u8> = (0..1024u32).map(|i| (i & 0xff) as u8).collect();
let image = build_backing(0, &plaintext, &k);
let mut vol = DecryptedVolume::new(Cursor::new(image), keys(), 0, plaintext.len() as u64);
let mut buf = [0u8; 300];
assert_eq!(vol.read_at(600, &mut buf).unwrap(), 300);
assert_eq!(&buf[..], &plaintext[600..900]);
}
#[test]
fn read_at_past_end_returns_zero() {
let mut vol = DecryptedVolume::new(Cursor::new(vec![0u8; 4096]), keys(), 0, 512);
let mut buf = [0u8; 16];
assert_eq!(vol.read_at(512, &mut buf).unwrap(), 0);
assert_eq!(vol.read_at(1000, &mut buf).unwrap(), 0);
}
#[test]
fn read_at_truncates_to_volume_end() {
let k = keys();
let plaintext: Vec<u8> = (0..512u32).map(|i| (i & 0xff) as u8).collect();
let image = build_backing(0, &plaintext, &k);
let mut vol = DecryptedVolume::new(Cursor::new(image), keys(), 0, 512);
let mut buf = [0u8; 512];
assert_eq!(vol.read_at(256, &mut buf).unwrap(), 256);
assert_eq!(&buf[..256], &plaintext[256..512]);
}
#[test]
fn empty_buffer_reads_nothing() {
let mut vol = DecryptedVolume::new(Cursor::new(vec![0u8; 4096]), keys(), 0, 512);
assert_eq!(vol.read_at(0, &mut []).unwrap(), 0);
}
#[test]
fn size_accessor() {
let vol = DecryptedVolume::new(Cursor::new(vec![0u8; 16]), keys(), 0, 167_772_160);
assert_eq!(vol.size(), 167_772_160);
}
#[test]
fn read_and_seek_impls() {
let k = keys();
let plaintext: Vec<u8> = (0..1024u32).map(|i| (i & 0xff) as u8).collect();
let image = build_backing(0, &plaintext, &k);
let mut vol = DecryptedVolume::new(Cursor::new(image), keys(), 0, plaintext.len() as u64);
let mut buf = [0u8; 512];
assert_eq!(std::io::Read::read(&mut vol, &mut buf).unwrap(), 512);
assert_eq!(&buf[..], &plaintext[..512]);
assert_eq!(vol.seek(SeekFrom::Start(512)).unwrap(), 512);
assert_eq!(std::io::Read::read(&mut vol, &mut buf).unwrap(), 512);
assert_eq!(&buf[..], &plaintext[512..1024]);
assert_eq!(vol.seek(SeekFrom::End(0)).unwrap(), 1024);
assert_eq!(vol.seek(SeekFrom::Current(-1024)).unwrap(), 0);
}
#[test]
fn seek_out_of_range_errors() {
let mut vol = DecryptedVolume::new(Cursor::new(vec![0u8; 16]), keys(), 0, 512);
assert!(vol.seek(SeekFrom::Current(-1)).is_err());
assert!(vol.seek(SeekFrom::End(1)).is_ok());
}
struct FlakyReader {
data: Vec<u8>,
pos: usize,
interrupted_once: bool,
}
impl Read for FlakyReader {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if !self.interrupted_once {
self.interrupted_once = true;
return Err(io::Error::from(io::ErrorKind::Interrupted));
}
if self.pos >= self.data.len() {
return Err(io::Error::other("backing read failed"));
}
let n = (buf.len()).min(self.data.len() - self.pos).min(4);
buf[..n].copy_from_slice(&self.data[self.pos..self.pos + n]);
self.pos += n;
Ok(n)
}
}
#[test]
fn read_full_or_eof_retries_interrupt_and_propagates_error() {
let mut r = FlakyReader {
data: vec![1u8; 8],
pos: 0,
interrupted_once: false,
};
let mut buf = [0u8; 16];
let err = read_full_or_eof(&mut r, &mut buf).unwrap_err();
assert_eq!(err.to_string(), "backing read failed");
assert_eq!(&buf[..8], &[1u8; 8]);
}
struct EofReader;
impl Read for EofReader {
fn read(&mut self, _buf: &mut [u8]) -> io::Result<usize> {
Ok(0)
}
}
#[test]
fn read_full_or_eof_stops_at_clean_eof() {
let mut r = EofReader;
let mut buf = [0xffu8; 8];
read_full_or_eof(&mut r, &mut buf).unwrap();
assert_eq!(buf, [0xffu8; 8]);
}
}