encrypt index

This commit is contained in:
Cyryl Płotnicki 2021-10-31 00:25:37 +01:00
parent 94495b040f
commit 57b7ddef8b
8 changed files with 263 additions and 81 deletions

94
Cargo.lock generated
View file

@ -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"

View file

@ -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"

View file

@ -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<Self> {
pub fn load(repository_path: &Path, secret: &[u8]) -> Result<Self> {
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<Self> {
fn load_from_file(index_file_path: &Path, secret: &[u8]) -> Result<Self> {
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);

View file

@ -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<Repository> {
pub fn init(path: &Path, secret: &str) -> Result<Repository> {
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<Repository> {
let index = Index::load(path)?;
pub fn open(path: &Path, secret: &str) -> Result<Repository> {
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")?)?;

View file

@ -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<RepositoryItem> {
pub fn newest_item(repository_path: &Path, secret: &str, source_file_full_path: &Path) -> Result<RepositoryItem> {
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<u64> {
pub fn data_weight(repository_path: &Path, secret: &str) -> Result<u64> {
{
let repository = Repository::open(repository_path)?;
let repository = Repository::open(repository_path, secret)?;
repository.data_weight()
}
}

View file

@ -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<Vec<usize>> {
@ -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<Vec<PathBuf>> {
let mut restore_repository = Repository::open(repository_path)?;
fn restore_all(repository_path: &Path, secret: &str, restore_target: &Path) -> Result<Vec<PathBuf>> {
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)

View file

@ -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();
}
}
}

View file

@ -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