Skip to content
secure_file.rs 12.1 KiB
Newer Older
use crate::{
Stefan Schindler's avatar
Stefan Schindler committed
    decrypt_file_buffered, encrypt_file_buffered,
Stefan Schindler's avatar
Stefan Schindler committed
    errors::{CvfsErrors, CvfsResult},
    metadata::FileTag,
    open_rwlocked, CryptoVfsInner,
use multi_key_manager::{file::File, LiveKeyMaterial, OpeningNonce};
use ring::aead::{NonceSequence, OpeningKey, SealingKey};
use std::{
    fmt::Debug,
Stefan Schindler's avatar
Stefan Schindler committed
    io::{Read, Seek, Write},
Stefan Schindler's avatar
Stefan Schindler committed
    path::Path,
};

/// an open active file
pub struct ActiveFile {
    /// Clonable needed for metadata
Stefan Schindler's avatar
Stefan Schindler committed
    pub(crate) id: FileTag,
    /// The actual data buffer
    data: Vec<u8>,
    /// seekable
    offset: usize,
Stefan Schindler's avatar
Stefan Schindler committed
    /// We don't have the key inside this struct, but the `Metadata`
    modified_flag: bool,
    real_file: File,
}
Stefan Schindler's avatar
Stefan Schindler committed
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)?;
Stefan Schindler's avatar
Stefan Schindler committed
        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,
Stefan Schindler's avatar
Stefan Schindler committed
            modified_flag: false,
Stefan Schindler's avatar
Stefan Schindler committed
            real_file,
        })
    }
Stefan Schindler's avatar
Stefan Schindler committed

    /// Same as `Write::flush` with more detailed error message
    pub fn try_flush<N: NonceSequence>(&mut self, key: &mut SealingKey<N>) -> CvfsResult<()> {
        if self.data.is_empty() {
            // Don't save empty buffers as it confuses the decryption
            return Ok(());
        }
Stefan Schindler's avatar
Stefan Schindler committed
        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) {
Stefan Schindler's avatar
Stefan Schindler committed
        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);
        }
Stefan Schindler's avatar
Stefan Schindler committed
        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;
Stefan Schindler's avatar
Stefan Schindler committed
    /// Will always panic!
    ///
    /// can not flush directly, must be done through the `SecureFileHandler`
    fn flush(&mut self) -> Result<(), std::io::Error> {
Stefan Schindler's avatar
Stefan Schindler committed
        panic!("can not flush directly, must be done through the SecureFileHandler")
    }
}
Stefan Schindler's avatar
Stefan Schindler committed
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)
    }
}
Stefan Schindler's avatar
Stefan Schindler committed
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()
    }
}
Stefan Schindler's avatar
Stefan Schindler committed

/// These Handlers should reduce the time the main Mutex is held so concurrent access is possible
pub struct SecureFileHandler {
    //pub(crate) metadata: Weak<Mutex<Metadata>>,
    pub(crate) cvfs: Weak<Mutex<CryptoVfsInner>>,
Stefan Schindler's avatar
Stefan Schindler committed
    pub(crate) id: FileTag,
Stefan Schindler's avatar
Stefan Schindler committed
}
impl SecureFileHandler {
    pub fn try_write(&mut self, buffer: &[u8]) -> CvfsResult<usize> {
        let arc = self.cvfs.upgrade().ok_or(CvfsErrors::CVFSAlreadyDropped(
            "try_write(): 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().ok_or(CvfsErrors::CVFSAlreadyDropped(
            "try_read(): Metadata was dropped before SecureFileHandler",
        ))?;
        let mut cvfs = arc.lock().expect("CryptoVfsInner Mutex poisened");
        let file = cvfs.metadata.find_active_file(&self.id)?;
Stefan Schindler's avatar
Stefan Schindler committed
    }
    pub fn try_flush(&mut self) -> CvfsResult<()> {
        let arc = self.cvfs.upgrade().ok_or(CvfsErrors::CVFSAlreadyDropped(
            "try_flush(): 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)?;
Stefan Schindler's avatar
Stefan Schindler committed
    }

    /// 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<()> {
        let arc = self.cvfs.upgrade().ok_or(CvfsErrors::CVFSAlreadyDropped(
            "set_len(): Metadata was dropped before SecureFileHandler",
        ))?;
        let mut cvfs = arc.lock().expect("CryptoVfsInner Mutex poisened");
        let file = cvfs.metadata.find_active_file(&self.id)?;
Stefan Schindler's avatar
Stefan Schindler committed
    }
}
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:?}")))
    }
}
Stefan Schindler's avatar
Stefan Schindler committed
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) {
        // ignore errors since we can not do much at this point
        let _ = self.try_flush();
Stefan Schindler's avatar
Stefan Schindler committed

fn stdout_flush() -> std::io::Result<()> {
    std::io::stdout().flush()

#[cfg(test)]
mod tests {
    use super::*;
    use ring::aead::{BoundKey, UnboundKey};
    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
    }

Stefan Schindler's avatar
Stefan Schindler committed
    fn test_key() -> UnboundKey {
        let key_material = [42u8; 32];
        let algorithm = &ring::aead::CHACHA20_POLY1305;

Stefan Schindler's avatar
Stefan Schindler committed
        UnboundKey::new(algorithm, &key_material).expect("unable to construct UnboundKey")
    }
    fn test_opening_key() -> OpeningKey<OpeningNonce> {
        let nonce = OpeningNonce::new(2);
Stefan Schindler's avatar
Stefan Schindler committed
        OpeningKey::new(test_key(), nonce)
Stefan Schindler's avatar
Stefan Schindler committed
    fn test_sealing_key() -> SealingKey<MutableNonce> {
        let nonce = MutableNonce::new(Rc::new(RefCell::new(1u128)));
Stefan Schindler's avatar
Stefan Schindler committed
        SealingKey::new(test_key(), nonce)
    }

    #[test]
    fn simple_write() {
        let path = path("simple_write.bin");
        // delete if it already exists
        let _ = std::fs::remove_file(&path);
Stefan Schindler's avatar
Stefan Schindler committed
        let mut key = test_opening_key();
        let mut a = ActiveFile::open_rwlocked(&path, FileTag(1), &mut key)
            .expect("unable to open test file");
        assert_eq!(0, a.offset);
Stefan Schindler's avatar
Stefan Schindler committed
        assert_eq!(true, a.modified_flag);
        println!("a.data: {:?}", a.data);
        assert_eq!(3, a.data.len());
        assert_eq!(3, a.offset);
Stefan Schindler's avatar
Stefan Schindler committed

        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");
Stefan Schindler's avatar
Stefan Schindler committed
    }

    #[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();
Stefan Schindler's avatar
Stefan Schindler committed
        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);
Stefan Schindler's avatar
Stefan Schindler committed

    #[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);
    }