Newer
Older
metadata::FileTag,
open_rwlocked, CryptoVfsInner,
use multi_key_manager::{file::File, LiveKeyMaterial, OpeningNonce};
use ring::aead::{NonceSequence, OpeningKey, SealingKey};
sync::{Mutex, Weak},
};
/// an open active file
pub struct ActiveFile {
/// Clonable needed for metadata
/// The actual data buffer
/// seekable
offset: usize,
/// We don't have the key inside this struct, but the `Metadata`
modified_flag: bool,
impl ActiveFile {
pub fn open_rwlocked(
real_path: &Path,
id: FileTag,
file_key: &mut OpeningKey<OpeningNonce>,
) -> CvfsResult<Self> {
let mut real_file = open_rwlocked(real_path)?;
let data = if real_file.empty()? {
Vec::new()
} else {
decrypt_file_buffered(&mut real_file, file_key)
.map_err(|_generic_ring_error| CvfsErrors::LeafDecryptionError)?
};
Ok(Self {
id,
data,
/// Same as `Write::flush` with more detailed error message
pub fn try_flush<N: NonceSequence>(&mut self, key: &mut SealingKey<N>) -> CvfsResult<()> {
Stefan Schindler
committed
if self.data.is_empty() {
// Don't save empty buffers as it confuses the decryption
return Ok(());
}
encrypt_file_buffered(&mut self.real_file, &self.data, key)?;
self.modified_flag = false;
Ok(())
}
fn set_len(&mut self, size: usize) -> CvfsResult<()> {
Ok(self.data.resize(size, 0))
}
impl Drop for ActiveFile {
fn drop(&mut self) {
if self.modified_flag {
let error = "aead_vfs::ActiveFile was not flushed by Metadata";
if panicking() {
eprintln!("{error}");
} else {
panic!("{error}");
}
}
}
}
impl Write for ActiveFile {
/// All writes are to the buffer and not commited to disk until `.flush()?` is called or the file closes
fn write(&mut self, buffer: &[u8]) -> Result<usize, std::io::Error> {
let bytes_written = buffer.len();
let new_max_len = self.offset + buffer.len();
if self.data.len() < new_max_len {
self.data.resize_with(new_max_len, Default::default);
}
let data_len = self.data.len();
let target_range = self.offset..new_max_len;
println!(" < target_range: {target_range:?}");
let target = &mut self.data[target_range];
println!(
" < offset: {}, new: {new_max_len}, self.data: {data_len} target: {}, buffer: {}",
self.offset,
target.len(),
buffer.len()
);
println!(" < target: {:?}", target);
stdout_flush()?;
// TODO try the more efficient method, while avoiding SIG-6
//target.copy_from_slice(buffer);
//target.clone_from_slice(buffer);
// safes because it checks both ranges
for (t, s) in target.iter_mut().zip(buffer.iter()) {
*t = *s;
}
self.offset = new_max_len;
self.modified_flag = true;
Ok(bytes_written)
}
/// Will always panic!
///
/// can not flush directly, must be done through the `SecureFileHandler`
fn flush(&mut self) -> Result<(), std::io::Error> {
panic!("can not flush directly, must be done through the SecureFileHandler")
}
}
impl Read for ActiveFile {
fn read(&mut self, out_buffer: &mut [u8]) -> Result<usize, std::io::Error> {
let available_buffer = &self.data[self.offset..];
let mut read_bytes = 0;
for (t, s) in out_buffer.iter_mut().zip(available_buffer.iter()) {
*t = *s;
read_bytes += 1;
}
self.offset += read_bytes;
Ok(read_bytes)
}
}
impl Seek for ActiveFile {
fn seek(&mut self, target: std::io::SeekFrom) -> Result<u64, std::io::Error> {
use std::io::SeekFrom::*;
self.offset = match target {
Start(target) => target.try_into().expect("unable to convert seeking value"),
Current(target) => {
let new = self.offset as i64 + target;
if new < 0 {
panic!("seeked before start");
}
new as usize
}
End(target) => {
let new = self.data.len() as i64 + target;
if new < 0 {
panic!("seeked before start");
}
new as usize
}
};
Ok(self.offset as u64)
impl Debug for ActiveFile {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ActiveFile")
.field("id", &self.id)
// save space and never go to the fance print
.field(
"data",
&format_args!(
"{:?}[..{}]",
&self.data[0..(12.min(self.data.len()))],
self.data.len()
),
)
.field("offset", &self.offset)
.field("modified_flag", &self.modified_flag)
.field("real_file", &self.real_file)
.finish()
}
}
/// These Handlers should reduce the time the main Mutex is held so concurrent access is possible
//pub(crate) metadata: Weak<Mutex<Metadata>>,
pub(crate) cvfs: Weak<Mutex<CryptoVfsInner>>,
pub fn try_write(&mut self, buffer: &[u8]) -> CvfsResult<usize> {
.cvfs
.upgrade()
.expect("CryptoVfsInner was dropped before SecureFileHandler");
let mut cvfs = arc.lock().expect("Metadata Mutex poisened");
let file = cvfs.metadata.find_active_file(&self.id)?;
Ok(file.write(buffer)?)
}
pub fn try_read(&mut self, out_buffer: &mut [u8]) -> CvfsResult<usize> {
let arc = self
.cvfs
.upgrade()
.expect("Metadata was dropped before SecureFileHandler");
let mut cvfs = arc.lock().expect("CryptoVfsInner Mutex poisened");
let file = cvfs.metadata.find_active_file(&self.id)?;
Ok(file.read(out_buffer)?)
pub fn try_flush(&mut self) -> CvfsResult<()> {
.cvfs
.upgrade()
.expect("Metadata was dropped before SecureFileHandler");
let mut cvfs = arc.lock().expect("CryptoVfsInner Mutex poisened");
// splitting the CryptoVfsInner struct instead of the MutexGuard
let cvfs: &mut CryptoVfsInner = &mut cvfs;
let mut key = cvfs.keys.get_sealing_master_key();
let file = cvfs.metadata.find_active_file(&self.id)?;
file.try_flush(&mut key)
/// Works on the memory until `.try_flush()` is called.
///
/// The cursor is not changed and may be hanging over the end, use the `Seek` methods like `Seek.rewind()` for that.
pub fn set_len(&mut self, size: usize) -> CvfsResult<()> {
.cvfs
.upgrade()
.expect("Metadata was dropped before SecureFileHandler");
let mut cvfs = arc.lock().expect("CryptoVfsInner Mutex poisened");
let file = cvfs.metadata.find_active_file(&self.id)?;
file.set_len(size)
}
}
impl Write for SecureFileHandler {
fn write(&mut self, buffer: &[u8]) -> Result<usize, std::io::Error> {
self.try_write(buffer)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("{e:?}")))
}
fn flush(&mut self) -> Result<(), std::io::Error> {
self.try_flush()
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("{e:?}")))
}
}
impl Read for SecureFileHandler {
fn read(&mut self, out_buffer: &mut [u8]) -> Result<usize, std::io::Error> {
self.try_read(out_buffer)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("{e:?}")))
}
}
impl Drop for SecureFileHandler {
fn drop(&mut self) {
if let Err(error) = self.try_flush() {
if panicking() {
eprintln!("SecureFileHandler::drop() -> {error:?}");
} else {
panic!("SecureFileHandler::drop() -> {error:?}");
}
}
}
}
fn stdout_flush() -> std::io::Result<()> {
std::io::stdout().flush()
#[cfg(test)]
mod tests {
use super::*;
use multi_key_manager::MutableNonce;
use ring::aead::{BoundKey, UnboundKey};
Stefan Schindler
committed
use std::{cell::RefCell, path::PathBuf, rc::Rc};
/// ext must not start with a `/`
fn path<P: AsRef<Path>>(ext: P) -> PathBuf {
let mut p = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
// we are in a cargo workspace
p.pop();
p.push("target");
p.push("aead-vfs_tests-secure-file");
std::fs::create_dir_all(&p).expect("unable to create testing directory");
p.push(ext);
p
}
let key_material = [42u8; 32];
let algorithm = &ring::aead::CHACHA20_POLY1305;
UnboundKey::new(algorithm, &key_material).expect("unable to construct UnboundKey")
}
fn test_opening_key() -> OpeningKey<OpeningNonce> {
let nonce = OpeningNonce::new(2);
fn test_sealing_key() -> SealingKey<MutableNonce> {
Stefan Schindler
committed
let nonce = MutableNonce::new(Rc::new(RefCell::new(1u128)));
#[test]
fn simple_write() {
let path = path("simple_write.bin");
// delete if it already exists
let _ = std::fs::remove_file(&path);
let mut a = ActiveFile::open_rwlocked(&path, FileTag(1), &mut key)
.expect("unable to open test file");
a.write(b"abc").unwrap();
println!("a.data: {:?}", a.data);
assert_eq!(3, a.data.len());
assert_eq!(3, a.offset);
a.write(b"def").unwrap();
println!("a.data: {:?}", a.data);
assert_eq!(6, a.data.len());
assert_eq!(6, a.offset);
let mut key = test_sealing_key();
a.try_flush(&mut key).expect("unable to flush");
}
#[test]
fn write_and_seek() {
let mut key = test_opening_key();
let mut a = ActiveFile::open_rwlocked(&path("write_and_seek.bin"), FileTag(1), &mut key)
.expect("unable to open test file");
a.write(b"abcdefghi").unwrap();
println!("a.data: {:?}", a.data);
assert_eq!(9, a.data.len());
assert_eq!(9, a.offset);
a.seek(std::io::SeekFrom::Current(-6)).unwrap();
a.write(b"XYZ").unwrap();
assert_eq!(true, a.modified_flag);
println!("a.data: {:?}", a.data);
assert_eq!(9, a.data.len());
assert_eq!(6, a.offset);
let mut key = test_sealing_key();
a.try_flush(&mut key).unwrap();
assert_eq!(false, a.modified_flag);
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
#[test]
fn write_and_read() {
let mut a = {
let mut key = test_opening_key();
ActiveFile::open_rwlocked(&path("write_and_read.bin"), FileTag(1), &mut key)
.expect("unable to open test file")
};
a.write(b"abcdefghi").unwrap();
println!("a.data: {:?}", a.data);
assert_eq!(9, a.data.len());
assert_eq!(9, a.offset);
a.rewind().unwrap();
let mut buffer = [0u8; 5];
let n_read = a.read(&mut buffer).unwrap();
assert_eq!(5, n_read);
assert_eq!(5, a.offset);
assert_eq!(b"abcde", &buffer[..n_read]);
let n_read = a.read(&mut buffer).unwrap();
assert_eq!(4, n_read);
assert_eq!(9, a.offset);
assert_eq!(b"fghi", &buffer[..n_read]);
let mut key = test_sealing_key();
a.try_flush(&mut key).unwrap();
assert_eq!(false, a.modified_flag);
}