diff --git a/Cargo.lock b/Cargo.lock index c201a0e..2cb08eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aead" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" +dependencies = [ + "generic-array", +] + [[package]] name = "ansi_term" version = "0.12.1" @@ -40,7 +49,9 @@ version = "0.1.0" dependencies = [ "anyhow", "base64", + "blake", "cargo-husky", + "chacha20poly1305", "criterion", "fail", "femme", @@ -87,6 +98,16 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "blake" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f76cff23583935d01f1d259e546bf988450648a644da066de24717fdd015aa81" +dependencies = [ + "gcc", + "libc", +] + [[package]] name = "block-buffer" version = "0.9.0" @@ -153,6 +174,40 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chacha20" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01b72a433d0cf2aef113ba70f62634c56fddb0f244e6377185c56a7cadbd8f91" +dependencies = [ + "cfg-if 1.0.0", + "cipher", + "cpufeatures", + "zeroize", +] + +[[package]] +name = "chacha20poly1305" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b84ed6d1d5f7aa9bdde921a5090e0ca4d934d250ea3b402a5fab3a994e28a2a" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array", +] + [[package]] name = "clap" version = "2.33.3" @@ -339,6 +394,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "gcc" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" + [[package]] name = "generic-array" version = "0.14.4" @@ -534,6 +595,17 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "poly1305" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "ppv-lite86" version = "0.2.14" @@ -864,6 +936,12 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + [[package]] name = "syn" version = "1.0.80" @@ -938,6 +1016,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +[[package]] +name = "universal-hash" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +dependencies = [ + "generic-array", + "subtle", +] + [[package]] name = "uuid" version = "0.8.2" @@ -1091,3 +1179,9 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "zeroize" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf68b08513768deaa790264a7fac27a58cbf2705cfcdc9448362229217d7e970" diff --git a/Cargo.toml b/Cargo.toml index a1cf893..9ff0daf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,8 @@ description = "modern and simple, yet efficient backup solution" [dependencies] anyhow = "1.0" base64 = "0.13" +blake = "2" +chacha20poly1305 = "0.9" fail = "0.4" femme = "2" hex = "0.4" diff --git a/src/index/io.rs b/src/index/io.rs index 7958a0a..420dff5 100644 --- a/src/index/io.rs +++ b/src/index/io.rs @@ -2,9 +2,13 @@ use std::{ collections::HashMap, fs::{self, File}, io::Read, + os::unix::prelude::OsStrExt, path::{Path, PathBuf}, }; +use chacha20poly1305::aead::{Aead, NewAead}; +use chacha20poly1305::{Key, XChaCha20Poly1305, XNonce}; // Or `XChaCha20Poly1305` + use uuid::Uuid; use crate::index::item::IndexItem; @@ -18,14 +22,14 @@ use nix::unistd::getpid; use std::{cmp::max, io::Write}; impl Index { - pub fn load(repository_path: &Path) -> Result { + pub fn load(repository_path: &Path, secret: &[u8]) -> Result { if !repository_path.exists() { let mut index = Index::new()?; - index.save(repository_path)?; + index.save(repository_path, secret)?; } let lock = Lock::lock(repository_path)?; let index_file_path = &Index::index_file_path_for_repository_path(repository_path)?; - let index = Index::load_from_file(index_file_path)?; + let index = Index::load_from_file(index_file_path, secret)?; lock.release()?; log::debug!( "[{}] loaded index from {}, version: {}; {} items", @@ -37,19 +41,19 @@ impl Index { Ok(index) } - pub fn save(&mut self, repository_path: &Path) -> Result<()> { + pub fn save(&mut self, repository_path: &Path, secret: &[u8]) -> Result<()> { let lock_id = Uuid::new_v4(); let lock = Lock::lock(repository_path)?; let index_file_path = &Index::index_file_path_for_repository_path(repository_path)?; if index_file_path.exists() { - let index = Index::load_from_file(&Index::index_file_path_for_repository_path(repository_path)?)?; + let index = Index::load_from_file(&Index::index_file_path_for_repository_path(repository_path)?, secret)?; self.merge_items_by_file_id(index.items_by_file_id); self.merge_newest_items(index.newest_items_by_source_path); self.version = max(self.version, index.version); } self.version = self.version.next(); - self.write_index_to_file(index_file_path)?; + self.write_index_to_file(index_file_path, secret)?; lock.release()?; log::debug!( "[{}] saved index version {} with lock id {} to {}; {} items", @@ -62,7 +66,7 @@ impl Index { Ok(()) } - fn write_index_to_file(&mut self, index_file_path: &Path) -> Result<()> { + fn write_index_to_file(&mut self, index_file_path: &Path, secret: &[u8]) -> Result<()> { let parent = index_file_path.parent(); match parent { None => Err(anyhow!(format!( @@ -75,7 +79,17 @@ impl Index { let serialised = serde_json::to_string_pretty(&self)?; let bytes = serialised.as_bytes(); - let encoded = error_correcting_encoder::encode(bytes)?; + + let mut hash = [0; 32]; + blake::hash(256, secret, &mut hash)?; + let key = Key::from_slice(&hash); + let cipher = XChaCha20Poly1305::new(key); + + blake::hash(256, index_file_path.as_os_str().as_bytes(), &mut hash)?; + let nonce = XNonce::from_slice(&hash[0..(192 / 8)]); + + let encrypted = cipher.encrypt(nonce, bytes.as_ref()).map_err(|e| anyhow!("{}", e))?; + let encoded = error_correcting_encoder::encode(&encrypted)?; { let mut file = File::create(index_file_path)?; @@ -97,13 +111,22 @@ impl Index { } } - fn load_from_file(index_file_path: &Path) -> Result { + fn load_from_file(index_file_path: &Path, secret: &[u8]) -> Result { let mut file = File::open(index_file_path)?; let mut encoded = vec![]; file.read_to_end(&mut encoded)?; let decoded = error_correcting_encoder::decode(&encoded)?; - let index_text = String::from_utf8(decoded)?; + + let mut hash = [0; 32]; + blake::hash(256, secret, &mut hash)?; + let key = Key::from_slice(&hash); + let cipher = XChaCha20Poly1305::new(key); + blake::hash(256, index_file_path.as_os_str().as_bytes(), &mut hash)?; + let nonce = XNonce::from_slice(&hash[0..(192 / 8)]); + + let decrypted = cipher.decrypt(nonce, decoded.as_ref()).map_err(|e| anyhow!("{}", e))?; + let index_text = String::from_utf8(decrypted)?; let index: Index = serde_json::from_str(&index_text) .context(format!("cannot read index from: {}", index_file_path.to_string_lossy()))?; @@ -144,7 +167,8 @@ mod must { let mut index = Index::new()?; let old_version = index.version; - index.save(temp_dir.path())?; + let secret = b"some secret"; + index.save(temp_dir.path(), secret)?; let new_version = index.version; @@ -155,11 +179,13 @@ mod must { #[test] fn be_same_when_loaded_from_disk() -> Result<()> { + femme::with_level(log::LevelFilter::Debug); let repository_path = tempdir()?; let mut original = Index::new()?; - original.save(repository_path.path())?; - let loaded = Index::load(repository_path.path())?; + let secret = b"some secret"; + original.save(repository_path.path(), secret)?; + let loaded = Index::load(repository_path.path(), secret)?; assert_eq!(original, loaded); diff --git a/src/repository/mod.rs b/src/repository/mod.rs index e9acc15..14961e3 100644 --- a/src/repository/mod.rs +++ b/src/repository/mod.rs @@ -26,6 +26,7 @@ pub struct Repository { /// path to where the repository is stored on disk path: PathBuf, index: Index, + secret: String, } const DATA_DIR_NAME: &str = "data"; @@ -97,20 +98,21 @@ impl Debug for ItemId { } impl<'a> Repository { - pub fn init(path: &Path) -> Result { + pub fn init(path: &Path, secret: &str) -> Result { fs::create_dir_all(path)?; let mut index = Index::new()?; - index.save(path)?; - let repository = Repository::open(path)?; + index.save(path, secret.as_bytes())?; + let repository = Repository::open(path, secret)?; fs::create_dir_all(repository.data_dir()?)?; Ok(repository) } - pub fn open(path: &Path) -> Result { - let index = Index::load(path)?; + pub fn open(path: &Path, secret: &str) -> Result { + let index = Index::load(path, secret.as_bytes())?; let repository = Repository { path: path.to_path_buf(), index, + secret: secret.to_owned(), }; Ok(repository) @@ -121,7 +123,7 @@ impl<'a> Repository { } pub fn save_index(&mut self) -> Result<()> { - self.index.save(&self.path) + self.index.save(&self.path, self.secret.as_bytes()) } pub fn store(&mut self, source_path: &Path) -> Result<()> { @@ -232,9 +234,10 @@ mod must { let file_size2 = 27; let source = TestSource::new()?; let repository_path = tempdir()?; - Repository::init(repository_path.path())?; + let secret = "some secret"; + Repository::init(repository_path.path(), secret)?; - let mut backup_repository = Repository::open(repository_path.path())?; + let mut backup_repository = Repository::open(repository_path.path(), secret)?; source.write_random_bytes_to_file("file1", file_size1)?; backup_repository.store(&source.file_path("file1")?)?; diff --git a/src/test/assertions.rs b/src/test/assertions.rs index c3cf791..5f697a7 100644 --- a/src/test/assertions.rs +++ b/src/test/assertions.rs @@ -19,15 +19,15 @@ pub mod in_memory { pub fn assert_same_after_restore(source_path: &Path) -> Result<()> { let repository_path = tempdir()?; let restore_target = tempdir()?; - - Repository::init(repository_path.path())?; + let secret = "some secret"; + Repository::init(repository_path.path(), secret)?; { - let mut backup_repository = Repository::open(repository_path.path())?; + let mut backup_repository = Repository::open(repository_path.path(), secret)?; let mut backup_engine = backup::Engine::new(source_path, &mut backup_repository)?; backup_engine.backup()?; } { - let mut restore_repository = Repository::open(repository_path.path())?; + let mut restore_repository = Repository::open(repository_path.path(), secret)?; let mut restore_engine = restore::Engine::new(&mut restore_repository, restore_target.path())?; restore_engine.restore_all()?; @@ -37,8 +37,13 @@ pub mod in_memory { Ok(()) } - pub fn assert_restored_file_contents(repository_path: &Path, source_file_full_path: &Path, contents: &[u8]) -> Result<()> { - let mut restore_repository = Repository::open(repository_path)?; + pub fn assert_restored_file_contents( + repository_path: &Path, + secret: &str, + source_file_full_path: &Path, + contents: &[u8], + ) -> Result<()> { + let mut restore_repository = Repository::open(repository_path, secret)?; let item = restore_repository.newest_item_by_source_path(source_file_full_path)?; let restore_target = tempdir()?; let restore_engine = restore::Engine::new(&mut restore_repository, restore_target.path())?; @@ -51,11 +56,12 @@ pub mod in_memory { pub fn assert_restored_from_version_has_contents( repository_path: &Path, + secret: &str, source_file_full_path: &Path, old_contents: &[u8], old_id: &ItemId, ) -> Result<()> { - let mut restore_repository = Repository::open(repository_path)?; + let mut restore_repository = Repository::open(repository_path, secret)?; let old_item = restore_repository.item_by_id(old_id)?; let restore_target = tempdir()?; let restore_engine = restore::Engine::new(&mut restore_repository, restore_target.path())?; @@ -65,9 +71,9 @@ pub mod in_memory { assert_target_file_contents(&restored_file_path, old_contents) } - pub fn newest_item(repository_path: &Path, source_file_full_path: &Path) -> Result { + pub fn newest_item(repository_path: &Path, secret: &str, source_file_full_path: &Path) -> Result { let item = { - let reading_repository = Repository::open(repository_path)?; + let reading_repository = Repository::open(repository_path, secret)?; let item = reading_repository.newest_item_by_source_path(source_file_full_path)?; assert!(item.is_some()); item.unwrap() @@ -75,9 +81,9 @@ pub mod in_memory { Ok(item) } - pub fn restore_all_from_reloaded_repository(repository_path: &Path, restore_target: &Path) -> Result<()> { + pub fn restore_all_from_reloaded_repository(repository_path: &Path, secret: &str, restore_target: &Path) -> Result<()> { { - let mut restore_repository = Repository::open(repository_path)?; + let mut restore_repository = Repository::open(repository_path, secret)?; let mut restore_engine = restore::Engine::new(&mut restore_repository, restore_target)?; restore_engine.restore_all()?; Ok(()) @@ -87,22 +93,30 @@ pub mod in_memory { pub fn backup_file_with_text_contents( source: &TestSource, repository_path: &Path, + secret: &str, source_file_relative_path: &str, contents: &str, ) -> Result<()> { { - backup_file_with_byte_contents(source, repository_path, source_file_relative_path, contents.as_bytes()) + backup_file_with_byte_contents( + source, + repository_path, + secret, + source_file_relative_path, + contents.as_bytes(), + ) } } pub fn backup_file_with_byte_contents( source: &TestSource, repository_path: &Path, + secret: &str, source_file_relative_path: &str, contents: &[u8], ) -> Result<()> { { - let mut backup_repository = Repository::open(repository_path)?; + let mut backup_repository = Repository::open(repository_path, secret)?; let mut backup_engine = backup::Engine::new(source.path(), &mut backup_repository)?; source.write_bytes_to_file(source_file_relative_path, contents).unwrap(); @@ -111,9 +125,9 @@ pub mod in_memory { } } - pub fn data_weight(repository_path: &Path) -> Result { + pub fn data_weight(repository_path: &Path, secret: &str) -> Result { { - let repository = Repository::open(repository_path)?; + let repository = Repository::open(repository_path, secret)?; repository.data_weight() } } diff --git a/tests/concurrency_tests.rs b/tests/concurrency_tests.rs index 80f340a..2399eb6 100644 --- a/tests/concurrency_tests.rs +++ b/tests/concurrency_tests.rs @@ -23,18 +23,20 @@ mod must { let dir = tempdir()?; let repository_path = dir.path(); let repository_path = repository_path.join(&format!("repository-{}", getpid())); - Repository::init(&repository_path)?; + let secret = "some secret"; + Repository::init(&repository_path, secret)?; let parallel_backups_number = 16; let files_per_backup_number = 16; let total_number_of_files = parallel_backups_number * files_per_backup_number; - let finished_backup_runs = backup_in_parallel(&repository_path, parallel_backups_number, files_per_backup_number)?; + let finished_backup_runs = + backup_in_parallel(&repository_path, secret, parallel_backups_number, files_per_backup_number)?; assert_eq!(finished_backup_runs.len(), parallel_backups_number); - assert!(data_weight(&repository_path)? > 0); + assert!(data_weight(&repository_path, secret)? > 0); let target_path = tempdir()?; - let all_restored_files = restore_all(&repository_path, target_path.path())?; + let all_restored_files = restore_all(&repository_path, secret, target_path.path())?; assert_eq!(all_restored_files.len(), total_number_of_files); assert_all_files_in_place(parallel_backups_number, files_per_backup_number, &all_restored_files)?; @@ -62,6 +64,7 @@ mod must { fn backup_in_parallel( repository_path: &Path, + secret: &str, parallel_backups_number: usize, files_per_backup_number: usize, ) -> Result> { @@ -73,7 +76,7 @@ mod must { child_pids.push(child); } Ok(ForkResult::Child) => { - backup_process(*task_number, repository_path, files_per_backup_number)?; + backup_process(*task_number, repository_path, secret, files_per_backup_number)?; std::process::exit(0); } @@ -93,8 +96,8 @@ mod must { Ok(task_numbers) } - fn backup_process(task_number: usize, repository_path: &Path, files_per_backup_number: usize) -> Result<()> { - let mut repository = Repository::open(repository_path)?; + fn backup_process(task_number: usize, repository_path: &Path, secret: &str, files_per_backup_number: usize) -> Result<()> { + let mut repository = Repository::open(repository_path, secret)?; let source = TestSource::new().unwrap(); let mut backup_engine = backup::Engine::new(source.path(), &mut repository)?; for i in 0..files_per_backup_number { @@ -105,8 +108,8 @@ mod must { Ok(()) } - fn restore_all(repository_path: &Path, restore_target: &Path) -> Result> { - let mut restore_repository = Repository::open(repository_path)?; + fn restore_all(repository_path: &Path, secret: &str, restore_target: &Path) -> Result> { + let mut restore_repository = Repository::open(repository_path, secret)?; let mut restore_engine = restore::Engine::new(&mut restore_repository, restore_target)?; restore_engine.restore_all()?; get_sorted_files_recursively(restore_target) diff --git a/tests/deduplication_tests.rs b/tests/deduplication_tests.rs index db961df..3f48db2 100644 --- a/tests/deduplication_tests.rs +++ b/tests/deduplication_tests.rs @@ -11,19 +11,20 @@ mod must { let source = TestSource::new().unwrap(); let dir = tempdir().unwrap(); let repository_path = dir.path(); - Repository::init(repository_path).unwrap(); - assert_eq!(data_weight(repository_path).unwrap(), 0); + let secret = "some secret"; + Repository::init(repository_path, secret).unwrap(); + assert_eq!(data_weight(repository_path, secret).unwrap(), 0); - backup_file_with_byte_contents(&source, repository_path, "1", &contents).unwrap(); - let first_weight = data_weight(repository_path).unwrap(); + backup_file_with_byte_contents(&source, repository_path, secret, "1", &contents).unwrap(); + let first_weight = data_weight(repository_path, secret).unwrap(); assert!(first_weight > 0); - backup_file_with_byte_contents(&source, repository_path, "2", &contents).unwrap(); - let second_weight = data_weight(repository_path).unwrap(); + backup_file_with_byte_contents(&source, repository_path, secret, "2", &contents).unwrap(); + let second_weight = data_weight(repository_path, secret).unwrap(); assert_eq!(first_weight, second_weight); - assert_restored_file_contents(repository_path, &source.file_path("1").unwrap(), &contents).unwrap(); - assert_restored_file_contents(repository_path, &source.file_path("2").unwrap(), &contents).unwrap(); + assert_restored_file_contents(repository_path, secret, &source.file_path("1").unwrap(), &contents).unwrap(); + assert_restored_file_contents(repository_path, secret, &source.file_path("2").unwrap(), &contents).unwrap(); } } } diff --git a/tests/system_tests.rs b/tests/system_tests.rs index 4f0063a..d28266d 100644 --- a/tests/system_tests.rs +++ b/tests/system_tests.rs @@ -1,5 +1,8 @@ #[cfg(test)] mod must { + use std::fs::File; + use std::io::Read; + use bakare::backup; use bakare::test::assertions::in_memory::*; use bakare::{repository::Repository, test::source::TestSource}; @@ -26,18 +29,18 @@ mod must { let dir = tempdir()?; let repository_path = dir.path(); let restore_target = tempdir()?; - - Repository::init(repository_path)?; + let secret = "some secret"; + Repository::init(repository_path, secret)?; let source_file_relative_path = "some file path"; let original_contents = "some old contents"; - backup_file_with_text_contents(&source, repository_path, source_file_relative_path, original_contents)?; + backup_file_with_text_contents(&source, repository_path, secret, source_file_relative_path, original_contents)?; - restore_all_from_reloaded_repository(repository_path, restore_target.path())?; + restore_all_from_reloaded_repository(repository_path, secret, restore_target.path())?; let source_file_full_path = &source.file_path(source_file_relative_path)?; - assert_restored_file_contents(repository_path, source_file_full_path, original_contents.as_bytes()) + assert_restored_file_contents(repository_path, secret, source_file_full_path, original_contents.as_bytes()) } #[test] @@ -45,21 +48,28 @@ mod must { let source = TestSource::new().unwrap(); let dir = tempdir()?; let repository_path = dir.path(); - Repository::init(repository_path)?; + let secret = "some secret"; + Repository::init(repository_path, secret)?; let source_file_relative_path = "some path"; let source_file_full_path = source.file_path(source_file_relative_path)?; let old_contents = "some old contents"; - backup_file_with_text_contents(&source, repository_path, source_file_relative_path, old_contents)?; + backup_file_with_text_contents(&source, repository_path, secret, source_file_relative_path, old_contents)?; - let old_item = newest_item(repository_path, &source_file_full_path)?; + let old_item = newest_item(repository_path, secret, &source_file_full_path)?; let old_id = old_item.id(); let new_contents = "totally new contents"; - backup_file_with_text_contents(&source, repository_path, source_file_relative_path, new_contents)?; + backup_file_with_text_contents(&source, repository_path, secret, source_file_relative_path, new_contents)?; - assert_restored_from_version_has_contents(repository_path, &source_file_full_path, old_contents.as_bytes(), old_id) + assert_restored_from_version_has_contents( + repository_path, + secret, + &source_file_full_path, + old_contents.as_bytes(), + old_id, + ) } #[test] @@ -67,19 +77,20 @@ mod must { let source = TestSource::new().unwrap(); let dir = tempdir()?; let repository_path = dir.path(); - Repository::init(repository_path)?; + let secret = "some secret"; + Repository::init(repository_path, secret)?; let source_file_relative_path = "some path"; let source_file_full_path = source.file_path(source_file_relative_path)?; - backup_file_with_text_contents(&source, repository_path, source_file_relative_path, "old")?; + backup_file_with_text_contents(&source, repository_path, secret, source_file_relative_path, "old")?; - let old_item = newest_item(repository_path, &source_file_full_path)?; + let old_item = newest_item(repository_path, secret, &source_file_full_path)?; let old_version = old_item.version(); - backup_file_with_text_contents(&source, repository_path, source_file_relative_path, "new")?; + backup_file_with_text_contents(&source, repository_path, secret, source_file_relative_path, "new")?; - let new_item = newest_item(repository_path, &source_file_full_path)?; + let new_item = newest_item(repository_path, secret, &source_file_full_path)?; let new_version = new_item.version(); assert!(new_version > old_version); @@ -92,23 +103,25 @@ mod must { let source = TestSource::new().unwrap(); let dir = tempdir()?; let repository_path = dir.path(); - Repository::init(repository_path)?; + let secret = "some secret"; + Repository::init(repository_path, secret)?; let source_file_relative_path = "some path"; - backup_file_with_text_contents(&source, repository_path, source_file_relative_path, "old contents")?; - backup_file_with_text_contents(&source, repository_path, source_file_relative_path, "newer contents")?; - backup_file_with_text_contents(&source, repository_path, source_file_relative_path, "newest contents")?; + backup_file_with_text_contents(&source, repository_path, secret, source_file_relative_path, "old contents")?; + backup_file_with_text_contents(&source, repository_path, secret, source_file_relative_path, "newer contents")?; + backup_file_with_text_contents(&source, repository_path, secret, source_file_relative_path, "newest contents")?; let source_file_full_path = &source.file_path(source_file_relative_path)?; - assert_restored_file_contents(repository_path, source_file_full_path, b"newest contents") + assert_restored_file_contents(repository_path, secret, source_file_full_path, b"newest contents") } #[test] fn forbid_backup_of_paths_within_repository() -> Result<()> { let dir = tempdir()?; let repository_path = dir.path(); - Repository::init(repository_path)?; - let mut repository = Repository::open(repository_path)?; + let secret = "some secret"; + Repository::init(repository_path, secret)?; + let mut repository = Repository::open(repository_path, secret)?; let error = backup::Engine::new(repository_path, &mut repository); assert!(error.is_err()); @@ -121,24 +134,26 @@ mod must { let source = TestSource::new().unwrap(); let dir = tempdir()?; let repository_path = dir.path(); - Repository::init(repository_path).unwrap(); + let secret = "some secret"; + Repository::init(repository_path, secret).unwrap(); - backup_file_with_text_contents(&source, repository_path, &filename, "some contents").unwrap(); + backup_file_with_text_contents(&source, repository_path, secret, &filename, "some contents").unwrap(); - let repository = Repository::open(repository_path).unwrap(); + let repository = Repository::open(repository_path, secret).unwrap(); let second_file = repository.find_latest_by_path_fragment(&filename).unwrap().unwrap(); assert_eq!(second_file.original_source_path(), source.file_path(&filename).unwrap().as_os_str()); } #[test] - fn not_leak_file_names(filename in "[a-zA-Z]{4,}"){ + fn not_leak_file_names_via_file_names(filename in "[a-zA-Z]{4,}"){ let source = TestSource::new().unwrap(); let dir = tempdir()?; let repository_path = dir.path(); - Repository::init(repository_path).unwrap(); + let secret = "some secret"; + Repository::init(repository_path, secret).unwrap(); - backup_file_with_text_contents(&source, repository_path, &filename, "some contents").unwrap(); + backup_file_with_text_contents(&source, repository_path, secret, &filename, "some contents").unwrap(); let walker = WalkDir::new(repository_path); let matching_paths = walker @@ -148,6 +163,30 @@ mod must { assert_eq!(matching_paths.count(), 0); } + + #[test] + fn not_leak_file_names_via_file_contents(test_filename in "[a-zA-Z]{8,}"){ + let source = TestSource::new().unwrap(); + let dir = tempdir()?; + let repository_path = dir.path(); + let secret = "some secret"; + Repository::init(repository_path, secret).unwrap(); + + backup_file_with_text_contents(&source, repository_path, secret, &test_filename, "some contents").unwrap(); + + let all_repo_files = get_sorted_files_recursively(repository_path).unwrap(); + assert!(all_repo_files.len() > 0); + + for filepath in all_repo_files { + let mut file = File::open(&filepath).unwrap(); + let filename = &filepath.as_os_str().to_string_lossy(); + let mut contents = vec![]; + file.read_to_end(&mut contents).unwrap(); + let test_filename_bytes = test_filename.as_bytes(); + let contains = contents.windows(test_filename_bytes.len()).any(move |sub_slice| sub_slice == test_filename_bytes); + assert!(!contains, "file {} in the repository directory contains plain text file name '{}' that was previously backed up", filename, test_filename); + } + } } // TODO: encryption // TODO: resume from sleep while backup in progress