Skip to content
lib.rs 15.4 KiB
Newer Older
Stefan Schindler's avatar
Stefan Schindler committed
#![forbid(unsafe_code)]

Stefan Schindler's avatar
Stefan Schindler committed
use multi_key_manager::{file::open_rwlocked, LiveKeyMaterial, LiveKeys, WithNonce};
use std::{
    fs::create_dir_all,
    path::{Path, PathBuf},
};

pub mod errors;
use errors::{specify_io_error, specify_kme_error, CvfsErrors, CvfsResult};
mod aead;
use aead::{decrypt_file_buffered, encrypt_file_buffered};
pub mod metadata;
Stefan Schindler's avatar
Stefan Schindler committed
use metadata::{FileInfo, FileTag, Metadata};
mod secure_file;
use secure_file::ActiveFile;
pub use secure_file::SecureFileHandler;

const SUPERBLOCK_PATH: &str = "encfsstorage.superblock";
const BLOCK_PATH: &str = "blocks/";
const METADATA_PATH: &str = "metadata.index";

pub fn open_dir<P: AsRef<Path>>(
    decryption_input: &str,
    directory_path: P,
) -> Result<CryptoVFS, CvfsErrors> {
Stefan Schindler's avatar
Stefan Schindler committed
    let directory_path = directory_path.as_ref().to_owned();
    specify_io_error(create_dir_all(&directory_path), "unable to create_dir_all")?;
Stefan Schindler's avatar
Stefan Schindler committed
    let keys_path = path_extend(&directory_path, SUPERBLOCK_PATH);
    if keys_path.is_dir() {
        return Err(CvfsErrors::GeneralIO(format!(
            "keys_path must not be a directory: {:?}",
            keys_path.display()
        )));
    }
Stefan Schindler's avatar
Stefan Schindler committed
    let metadata_path = path_extend(&directory_path, METADATA_PATH);
    if metadata_path.is_dir() {
        return Err(CvfsErrors::GeneralIO(format!(
            "metadata_path must not be a directory: {:?}",
            metadata_path.display()
        )));
    }
    let mut metadata_file = specify_io_error(open_rwlocked(&metadata_path), {
        #[cfg(feature = "fs_locks")]
        {
            "unable to lock the metadata"
        }
        #[cfg(not(feature = "fs_locks"))]
        {
            "unable to open the metadata"
        }
    })?;
Stefan Schindler's avatar
Stefan Schindler committed
    let blocks_path = path_extend(&directory_path, BLOCK_PATH);
    specify_io_error(create_dir_all(blocks_path), "unable to create blocks_path")?;
    let keys = if keys_path.is_file() {
        LiveKeys::read_from_disk(decryption_input, keys_path)
    } else {
        LiveKeys::generate_new(decryption_input, keys_path)
    };
    let mut keys = specify_kme_error(keys, "unable to read or generate master key")?;
Stefan Schindler's avatar
Stefan Schindler committed
    let metadata = if metadata_file.empty()? {
        Metadata::empty()
    } else {
Stefan Schindler's avatar
Stefan Schindler committed
        let content =
            decrypt_file_buffered(&mut metadata_file, &mut keys.get_opening_master_key())?;
        println!(" > CryptoVfsInner::open_dir");
        Metadata::restore_from_slice(&content)?
    };

    Ok(CryptoVFS(Arc::new(Mutex::new(CryptoVfsInner {
        metadata: metadata,
        file_tag_counter: 0, // AtomicUsize::new(0),
Stefan Schindler's avatar
Stefan Schindler committed
        real_base_path: directory_path,
    }))))
}

/// A mounted virtual file system lock wrapper
#[derive(Debug)]
pub struct CryptoVFS(Arc<Mutex<CryptoVfsInner>>);
impl CryptoVFS {
    pub fn exists(&self, path: &str) -> CvfsResult<bool> {
        self.0.lock()?.exists(path)
    }

    /// Flush all open SecureFileHandler and the metadata to disk
    ///
    /// Droping the VFS will automatically flush it
    pub fn flush_all(&mut self) -> CvfsResult<()> {
        self.0.lock()?.flush_all()
    }

    /// Opens a file in write-only mode.
    ///
    /// This function will create a file if it does not exist, and will truncate it if it does.
    pub fn create(&mut self, path: &str) -> CvfsResult<SecureFileHandler> {
        let this = Arc::downgrade(&self.0);
        self.0.lock()?.create(this, path)
    }

    /// Existing file handlers will return a `CvfsErrors::FileHandlerNotFound` on any subsequent action
    ///
    /// Returns true if the File was removed with this action, false if it did not exist
    pub fn delete(&mut self, path: &str) -> CvfsResult<bool> {
        self.0.lock()?.delete(path)
    }
}

/// A mounted virtual file system
pub struct CryptoVfsInner {
    /// Cyclic reference to itself
    //this: Weak<Mutex<CryptoVfsInner>>,
    keys: LiveKeys,
    file_tag_counter: u64, //AtomicUsize,
Stefan Schindler's avatar
Stefan Schindler committed
    real_base_path: PathBuf,
    /// Opens a file in write-only mode.
    ///
    /// This function will create a file if it does not exist, and will truncate it if it does.
        &mut self,
        this: Weak<Mutex<CryptoVfsInner>>,
        path: &str,
    ) -> CvfsResult<SecureFileHandler> {
        //let weak_metadata = Arc::downgrade(&self.metadata);
        //let mut metadata = self.metadata.lock()?;
        //Arc::get_mut(&mut self.metadata).expect("CryptoVfsInner::create double get_mut(metadata)");
        let Self {
            metadata,
            file_tag_counter,
            ..
        } = self;
Stefan Schindler's avatar
Stefan Schindler committed

        let entry: &mut FileInfo = metadata.files.entry(path.into()).or_insert_with(|| {
            let mut real_path = path_extend(&self.real_base_path, BLOCK_PATH);
Stefan Schindler's avatar
Stefan Schindler committed
            loop {
                let num = random_u64();
                real_block_name = format!("{num:8x}.bin");
                real_path.push(&real_block_name);
Stefan Schindler's avatar
Stefan Schindler committed
                if real_path.exists() == false {
                    // this is very likely
                    break;
                } else {
                    // remove the file name and try again
                    real_path.pop();
                }
            }
            println!("new block path: {:?}", real_path.display());
Stefan Schindler's avatar
Stefan Schindler committed
        });
        println!("CryptoVfsInner::create found entry: {entry:?}");
Stefan Schindler's avatar
Stefan Schindler committed

        let id = match &entry.active {
            Some(active) => {
                // already open
                active.id.clone()
            }
            None => {
                // try to open the file
                let id = FileTag::next(file_tag_counter);
Stefan Schindler's avatar
Stefan Schindler committed
                let mut file_key = self
                    .keys
                    .get_opening_master_key_with_nonce(entry.get_opening_nonce());

                // actually open the backing storage
                let mut real_path = path_extend(&self.real_base_path, BLOCK_PATH);
                real_path.push(&entry.real_block_name);
                let active = ActiveFile::open_rwlocked(&real_path, id.clone(), &mut file_key)?;

                println!("CryptoVfsInner::create made a file: {:?}", active);
                entry.active = Some(active);
Stefan Schindler's avatar
Stefan Schindler committed

                id
            }
        };

        Ok(SecureFileHandler {
            id,
            cvfs: this, //Arc::downgrade(&self.metadata),
    fn exists(&self, path: &str) -> CvfsResult<bool> {
        Ok(self.metadata.files.contains_key(path))
    /// Flush all open SecureFileHandler and the metadata to disk
    ///
    /// Droping the VFS will automatically flush it
    fn flush_all(&mut self) -> CvfsResult<()> {
        self.keys.store_to_disk()?;

        let metadata_path = path_extend(&self.real_base_path, METADATA_PATH);
        let metadata_file = open_rwlocked(metadata_path)?;
        //let mut metadata = self.metadata.lock()?;
        self.metadata.store_to_disk(metadata_file, &mut self.keys)

    fn delete(&mut self, path: &str) -> CvfsResult<bool> {
        let found = if let Some(file_info) = self.metadata.files.remove(path) {
            let mut block_path = path_extend(&self.real_base_path, BLOCK_PATH);
            block_path.push(&file_info.real_block_name);
            std::fs::remove_file(block_path)?;
            true
        } else {
            false
        };
        Ok(found)
    fn drop(&mut self) {
        if let Err(e) = self.flush_all() {
            let error = format!("aead_vfs::CryptoVfsInner was not flushed to disk: {e:?}");
            if panicking() {
                eprintln!("{error}");
            } else {
                panic!("{error}");
            }
        }
    }
}

pub fn path_extend<B: AsRef<Path>, E: AsRef<Path>>(base: B, ext: E) -> PathBuf {
    let mut buf = base.as_ref().to_owned();
    buf.push(ext);
    buf
}

Stefan Schindler's avatar
Stefan Schindler committed
/// Generate a secure random value
pub fn random<T: ring::rand::RandomlyConstructable>() -> T {
    let rng = ring::rand::SystemRandom::new();
    ring::rand::generate(&rng)
        .expect("unable to generate random value")
        .expose()
}
pub fn random_u64() -> u64 {
    let r = random::<[u8; 8]>();
    u64::from_le_bytes(r)
}

#[cfg(test)]
mod tests {
    use super::*;
Stefan Schindler's avatar
Stefan Schindler committed
    use std::io::{read_to_string, Write};

    /// 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");
        std::fs::create_dir_all(&p).expect("unable to create testing directory");
        p.push(ext);
        p
    }

    #[test]
    fn it_works() {
        let result = open_dir("bla bla secure key bla", path("it_works"));
        assert!(result.is_ok());
    }

    #[test]
    fn encrypt_decrypt_cycle() {
        let mut keys =
            LiveKeys::generate_new("paper_key paper_key", path("encrypt_decrypt_cycle.keys"))
                .unwrap();
        let mut file = open_rwlocked(path("encrypt_decrypt_cycle.bin")).unwrap();
        let random_data = b"abcdefghijklmnopqrstuvwxyz";
        println!(" >> DBG: {}", random_data.len());

        println!("nonce_before_encrypt: {:?}", keys.get_opening_nonce());
Stefan Schindler's avatar
Stefan Schindler committed
        encrypt_file_buffered(&mut file, random_data, &mut keys.get_sealing_master_key())
            .expect("unable to encrypt");

        let nonce_after_encrypt = keys.get_opening_nonce();
        println!("nonce_after_encrypt: {nonce_after_encrypt:?}");

Stefan Schindler's avatar
Stefan Schindler committed
        let decrypted = decrypt_file_buffered(&mut file, &mut keys.get_opening_master_key())
            .expect("unable to decrypt");

        let nonce_after_decrypt = keys.get_opening_nonce();
        println!("nonce_after_decrypt: {nonce_after_decrypt:?}");

        assert_eq!(random_data, &*decrypted);
    }

    /*
        #[test]
        fn encrypt_decrypt_cycle_large_file() {
            let mut keys = LiveKeys::generate_new(
                "paper_key paper_key",
                path("encrypt_decrypt_cycle_large_file.keys"),
            )
            .unwrap();
            let mut file = open_rwlocked(path("encrypt_decrypt_cycle_large_file.bin")).unwrap();
            let base_date = b"abcdefghijklmnopqrstuvwxyz";
            let mut random_data = Vec::new();
            for _ in 0..1024 * 1024 {
                random_data.extend_from_slice(base_date);
            }

            println!(" >> DBG: {}", random_data.len());

            println!("nonce_before_encrypt: {:?}", keys.get_opening_nonce());
            encrypt_file_buffered(&mut file, &random_data, &mut keys).expect("unable to encrypt");

            let nonce_after_encrypt = keys.get_opening_nonce();
            println!("nonce_after_encrypt: {nonce_after_encrypt:?}");

            let decrypted = decrypt_file_buffered(&mut file, &mut keys).expect("unable to decrypt");

            let nonce_after_decrypt = keys.get_opening_nonce();

            assert_eq!(random_data, decrypted);
        }
    // */

    #[test]
    fn double_store_and_decrypt_cycle() {
        let mut keys = LiveKeys::generate_new(
            "paper_key paper_key",
            path("double_store_and_decrypt_cycle.keys"),
        )
        .unwrap();
        let mut file = open_rwlocked(path("double_store_and_decrypt_cycle.bin")).unwrap();
        let initial_data = b"alksdjflkasjdlfkjasldkfjlaksjdfl";
        let random_data = b"abcdefghijklmnopqrstuvwxyz";
        println!(" >> DBG: {}", random_data.len());

        println!("1: {:?}", keys.get_opening_nonce());
Stefan Schindler's avatar
Stefan Schindler committed
        let mut key = keys.get_sealing_master_key();
        encrypt_file_buffered(&mut file, initial_data, &mut key).expect("unable to encrypt");
        drop(key);

        println!("2: {:?}", keys.get_opening_nonce());
Stefan Schindler's avatar
Stefan Schindler committed
        encrypt_file_buffered(&mut file, random_data, &mut keys.get_sealing_master_key())
            .expect("unable to encrypt");

        let nonce_after_encrypt = keys.get_opening_nonce();
        println!("3: {nonce_after_encrypt:?}");

Stefan Schindler's avatar
Stefan Schindler committed
        let mut key = keys.get_opening_master_key();
        let decrypted = decrypt_file_buffered(&mut file, &mut key);
        drop(key);

        let nonce_after_decrypt = keys.get_opening_nonce();
        println!("4: {nonce_after_decrypt:?}");
        let decrypted = decrypted.expect("unable to decrypt");

        assert_eq!(random_data, &*decrypted);
    }

    #[test]
    fn file_does_not_exist() {
        let vfs = open_dir("bla bla secure key bla", path("file_does_not_exist"))
            .expect("unable to open");
Stefan Schindler's avatar
Stefan Schindler committed
        assert_eq!(Ok(false), vfs.exists("something"));
    }

    #[test]
    fn file_does_exist() {
        let mut vfs =
            open_dir("bla bla secure key bla", path("file_does_exist")).expect("unable to open");
        let _handler = vfs.create("something").unwrap();
Stefan Schindler's avatar
Stefan Schindler committed
        assert_eq!(Ok(true), vfs.exists("something"));
        let vfs = open_dir("bla bla secure key bla", path("file_does_exist")).unwrap();
Stefan Schindler's avatar
Stefan Schindler committed
        assert_eq!(Ok(true), vfs.exists("something"));
Stefan Schindler's avatar
Stefan Schindler committed

    #[test]
    fn store_string() {
        let clear = String::from("this is a string that I need to store privately");
        let safe_name = "my thing.txt";
        let repo_key = "this is a secure secre key for testing";

        {
            let mut vfs = open_dir(repo_key, path("store_string")).expect("unable to open");

            let mut handler = vfs.create(safe_name).expect("unable to get handler");
            handler
                .write_all(clear.as_bytes())
                .expect("unable to write_all");

            handler.try_flush().unwrap();

            //println!("{vfs:?}");
            println!("\n222222222222222222\nsecond opening");
Stefan Schindler's avatar
Stefan Schindler committed
            let mut vfs = open_dir(repo_key, path("store_string")).expect("unable to open");

            let handler = vfs.create(safe_name).unwrap();
Stefan Schindler's avatar
Stefan Schindler committed
            let decrypted = read_to_string(handler).unwrap();

            assert_eq!(clear, decrypted);

    #[test]
    fn open_two() {
        let mut vfs = open_dir("bla bla secure key bla", path("open_two")).expect("unable to open");

        let mut a = vfs.create("a").unwrap();
        let mut a1 = vfs.create("a1").unwrap();
        let mut b = vfs.create("b").unwrap();
        assert_ne!(a.id, b.id);
        assert_ne!(a.id, a1.id);

        a.set_len(0).unwrap();
        a1.set_len(3).unwrap();
        b.set_len(12).unwrap();

        a.write_all(b"abcdefghijklmnopqrstuvwxyz").unwrap();
        a1.write_all(b"abcdefghijklmnopqrstuvwxyz").unwrap();
        b.write_all(b"01234566789012345678901234").unwrap();
    }

    #[test]
    fn delete_a_file() {
        let mut vfs =
            open_dir("bla bla secure key bla", path("delete_a_file")).expect("unable to open");

        let mut a = vfs.create("a").unwrap();
        let mut b = vfs.create("b").unwrap();
        assert_ne!(a.id, b.id);

        b.set_len(12).unwrap();

        a.write_all(b"abcdefghijklmnopqrstuvwxyz").unwrap();
        b.write_all(b"abcdefghijklmnopqrstuvwxyz").unwrap();

        vfs.flush_all().unwrap();

        let mut b_block_path = path("delete_a_file");
        b_block_path.push("blocks");
        b_block_path.push(&vfs.0.lock().unwrap().metadata.files["b"].real_block_name);
        assert!(b_block_path.exists());

        assert_eq!(Ok(true), vfs.delete("b"));
        assert!(b_block_path.exists() == false);
        assert_eq!(Ok(false), vfs.exists("b"));

        //assert!(false, "{vfs:#?}");
    }