From 9c1b9630d1eb68e5e1af7fa0446c70a5c6893d5a Mon Sep 17 00:00:00 2001 From: Radu Marias Date: Fri, 3 May 2024 11:23:29 +0300 Subject: [PATCH] use ring crate for encryption, speed is much better, up to 800 MBps add Result on some methods which makes sense switched tests to .unwrap() to panic --- Cargo.lock | 233 ++++------------------ Cargo.toml | 7 +- examples/README.md | 3 +- examples/aes_stream.rs | 2 +- examples/aes_stream_speed.rs | 24 ++- examples/cryptostream_speed.rs | 2 +- examples/ring.rs | 21 ++ examples/ring_crypto.rs | 108 ++++++++++ examples/ring_speed.rs | 107 ++++++++-- src/crypto.rs | 208 ++++++++++--------- src/crypto/buf_mut.rs | 107 ++++++++++ src/crypto/decryptor.rs | 121 +++++++++-- src/crypto/encryptor.rs | 135 +++++++++++-- src/encryptedfs.rs | 296 +++++++++++++++------------ src/encryptedfs/moved_test.rs | 354 +++++++++++++++++---------------- src/encryptedfs_fuse3.rs | 35 ++-- src/expire_value.rs | 4 + src/lib.rs | 8 +- src/stream_util.rs | 23 ++- 19 files changed, 1124 insertions(+), 674 deletions(-) create mode 100644 examples/ring.rs create mode 100644 examples/ring_crypto.rs create mode 100644 src/crypto/buf_mut.rs diff --git a/Cargo.lock b/Cargo.lock index f548e38f..79f5e1e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,16 +29,6 @@ dependencies = [ "opaque-debug", ] -[[package]] -name = "aes-stream" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e2533cd0b6abfc7687517ba71d820f57945de2aeb7ca6cadfde8a53765328d" -dependencies = [ - "rand 0.3.23", - "rust-crypto", -] - [[package]] name = "aho-corasick" version = "1.1.3" @@ -595,16 +585,7 @@ checksum = "b7aa2ec04f5120b830272a481e8d9d8ba4dda140d2cda59b0f1110d5eb93c38e" dependencies = [ "getrandom", "hybrid-array", - "rand_core 0.6.4", -] - -[[package]] -name = "cryptostream" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caac2d3a3f824b0ad4c8ebec6aafe324bad3efecd15be0558bc8b0a51d532991" -dependencies = [ - "openssl", + "rand_core", ] [[package]] @@ -786,27 +767,6 @@ version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "fuchsia-cprng" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" - [[package]] name = "fuse3" version = "0.7.1" @@ -919,12 +879,6 @@ dependencies = [ "slab", ] -[[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.7" @@ -943,7 +897,7 @@ checksum = "a06fddc2749e0528d2813f95e050e87e52c8cbbae56223b9babf73b3e53b0cc6" dependencies = [ "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", ] [[package]] @@ -1158,7 +1112,7 @@ checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "log", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "windows-sys 0.48.0", ] @@ -1319,44 +1273,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" -[[package]] -name = "openssl" -version = "0.10.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" -dependencies = [ - "bitflags 2.5.0", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.57", -] - -[[package]] -name = "openssl-sys" -version = "0.9.102" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "ordered-stream" version = "0.2.0" @@ -1386,7 +1302,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", - "rand_core 0.6.4", + "rand_core", "subtle", ] @@ -1413,12 +1329,6 @@ dependencies = [ "futures-io", ] -[[package]] -name = "pkg-config" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" - [[package]] name = "polling" version = "2.8.0" @@ -1490,29 +1400,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "rand" -version = "0.3.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" -dependencies = [ - "libc", - "rand 0.4.6", -] - -[[package]] -name = "rand" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" -dependencies = [ - "fuchsia-cprng", - "libc", - "rand_core 0.3.1", - "rdrand", - "winapi", -] - [[package]] name = "rand" version = "0.8.5" @@ -1521,7 +1408,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", - "rand_core 0.6.4", + "rand_core", ] [[package]] @@ -1531,24 +1418,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.4", + "rand_core", ] -[[package]] -name = "rand_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -dependencies = [ - "rand_core 0.4.2", -] - -[[package]] -name = "rand_core" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" - [[package]] name = "rand_core" version = "0.6.4" @@ -1558,15 +1430,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "rdrand" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" -dependencies = [ - "rand_core 0.3.1", -] - [[package]] name = "regex" version = "1.10.4" @@ -1615,24 +1478,23 @@ checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" name = "rencfs" version = "0.3.2" dependencies = [ - "aes-stream", "anyhow", "argon2", "base64", "bincode", "bytes", "clap", - "cryptostream", "ctrlc", "fuse3", "futures-util", + "hex", "keyring", "libc", "mio", "num-format", - "openssl", - "rand 0.8.5", + "rand", "retainer", + "ring", "rpassword", "secrecy", "serde", @@ -1657,7 +1519,22 @@ dependencies = [ "async-lock 2.8.0", "async-timer", "log", - "rand 0.8.5", + "rand", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", ] [[package]] @@ -1681,31 +1558,12 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "rust-crypto" -version = "0.2.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" -dependencies = [ - "gcc", - "libc", - "rand 0.3.23", - "rustc-serialize", - "time 0.1.45", -] - [[package]] name = "rustc-demangle" version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" -[[package]] -name = "rustc-serialize" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe834bc780604f4674073badbad26d7219cadfb4a2275802db12cbae17498401" - [[package]] name = "rustix" version = "0.37.27" @@ -1761,7 +1619,7 @@ dependencies = [ "hkdf", "num", "once_cell", - "rand 0.8.5", + "rand", "serde", "sha2 0.10.8", "zbus", @@ -1907,6 +1765,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "static_assertions" version = "1.1.0" @@ -2008,17 +1872,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "time" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" -dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", -] - [[package]] name = "time" version = "0.3.36" @@ -2126,7 +1979,7 @@ checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" dependencies = [ "crossbeam-channel", "thiserror", - "time 0.3.36", + "time", "tracing-subscriber", ] @@ -2237,6 +2090,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "utf8parse" version = "0.2.1" @@ -2249,12 +2108,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "version_check" version = "0.9.4" @@ -2267,12 +2120,6 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -2551,7 +2398,7 @@ dependencies = [ "nix 0.26.4", "once_cell", "ordered-stream", - "rand 0.8.5", + "rand", "serde", "serde_repr", "sha1", diff --git a/Cargo.toml b/Cargo.toml index a851f582..75853c0f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ bincode = "1.3.3" thiserror = "1.0.58" rand = "0.8.5" base64 = "=0.13.1" -openssl = "=0.10.64" +#openssl = "=0.10.64" fuse3 = { version = "0.7.1", features = ["tokio-runtime", "unprivileged"] } tokio = { version = "1.36", features = ["macros", "rt-multi-thread", "time", "signal", "process", "rt"] } tokio-stream = { version = "0.1.15", features = ["fs"] } @@ -39,14 +39,15 @@ sha2 = "0.11.0-pre.3" strum = "0.26.2" strum_macros = "0.26.2" rpassword = "7.3.1" -cryptostream = "0.3.2" +#cryptostream = "0.3.2" anyhow = "1.0.82" argon2 = "0.5.3" keyring = "2.3.2" secrecy = "0.8.0" retainer = "0.3.0" num-format = "0.4.4" -aes-stream = "0.2.1" +ring = "0.17.8" +hex = "0.4.3" [package.metadata.aur] depends = ["fuse3"] diff --git a/examples/README.md b/examples/README.md index ae539651..2b19294a 100644 --- a/examples/README.md +++ b/examples/README.md @@ -6,6 +6,5 @@ hex = "0.4.3" fuser = "0.14.0" env_logger = "0.11.3" log = "0.4.21" -rust-crypto = "=0.2.36" - +rust-crypto = { version = "0.2.36", package = "rust-crypto" } ``` diff --git a/examples/aes_stream.rs b/examples/aes_stream.rs index 9fab5ad8..62938d53 100644 --- a/examples/aes_stream.rs +++ b/examples/aes_stream.rs @@ -2,7 +2,7 @@ use std::fs::OpenOptions; use std::io::{Read, Seek, Write}; use aesstream::{AesReader, AesWriter}; -use crypto::aessafe::{AesSafe256Decryptor, AesSafe256Encryptor}; +use rust_crypto::aessafe::{AesSafe256Decryptor, AesSafe256Encryptor}; fn main() { let key: [u8; 32] = "a".repeat(32).as_bytes().try_into().unwrap(); diff --git a/examples/aes_stream_speed.rs b/examples/aes_stream_speed.rs index 600e752e..9db5e2ff 100644 --- a/examples/aes_stream_speed.rs +++ b/examples/aes_stream_speed.rs @@ -1,16 +1,16 @@ use std::fs::OpenOptions; use std::io; +use std::io::Write; use std::path::Path; use std::time::Instant; -use aesstream::AesWriter; -use crypto::aessafe::AesSafe256Encryptor; +use aesstream::{AesReader, AesWriter}; +use rust_crypto::aessafe::{AesSafe256Decryptor, AesSafe256Encryptor}; fn main() -> io::Result<()> { - let mut input = OpenOptions::new().read(true).open("/home/gnome/Downloads/Zero.Days.2016.720p.WEBRip.x264.AAC-ETRG/Zero.Days.2016.720p.WEBRip.x264.AAC-ETRG.mp4").unwrap(); - let out_path = Path::new("./encrypted.enc"); - let out = OpenOptions::new().create(true).write(true).open(out_path.clone())?; - let read_out = OpenOptions::new().read(true).open(out_path)?; + let mut input = OpenOptions::new().read(true).open("/home/gnome/Downloads/bfg-1.14.0.jar").unwrap(); + let out_path = Path::new("/tmp/encrypted.enc"); + let out = OpenOptions::new().create(true).write(true).truncate(true).open(out_path.clone())?; let key: [u8; 32] = "a".repeat(32).as_bytes().try_into().unwrap(); let encryptor = AesSafe256Encryptor::new(&key); @@ -23,5 +23,17 @@ fn main() -> io::Result<()> { let file_size = input.metadata()?.len(); println!("speed MB/s {}", (file_size as f64 / end.duration_since(start).as_secs_f64()) / 1024.0 / 1024.0); + let input = OpenOptions::new().read(true).open(out_path).unwrap(); + let out_path = Path::new("/tmp/encrypted.dec"); + let mut out = OpenOptions::new().create(true).write(true).truncate(true).open(out_path.clone())?; + let decryptor = AesSafe256Decryptor::new(&key); + let mut reader = AesReader::new(input, decryptor)?; + let start = Instant::now(); + io::copy(&mut reader, &mut out)?; + let end = Instant::now(); + println!("Time elapsed: {:?}", end.duration_since(start)); + let file_size = out_path.metadata()?.len(); + println!("speed MB/s {}", (file_size as f64 / end.duration_since(start).as_secs_f64()) / 1024.0 / 1024.0); + Ok(()) } \ No newline at end of file diff --git a/examples/cryptostream_speed.rs b/examples/cryptostream_speed.rs index 246b54a3..baaed86b 100644 --- a/examples/cryptostream_speed.rs +++ b/examples/cryptostream_speed.rs @@ -15,7 +15,7 @@ fn main() -> io::Result<()> { let mut input = OpenOptions::new().read(true).open("/home/gnome/Downloads/Zero.Days.2016.720p.WEBRip.x264.AAC-ETRG/Zero.Days.2016.720p.WEBRip.x264.AAC-ETRG.mp4").unwrap(); let out_path = Path::new("./encrypted.enc"); - let out = OpenOptions::new().create(true).write(true).open(out_path)?; + let out = OpenOptions::new().create(true).write(true).truncate(true).open(out_path)?; let mut encryptor = write::Encryptor::new(out, Cipher::chacha20(), &key, &iv).unwrap(); diff --git a/examples/ring.rs b/examples/ring.rs new file mode 100644 index 00000000..e3911827 --- /dev/null +++ b/examples/ring.rs @@ -0,0 +1,21 @@ +use ring::aead::{Aad, CHACHA20_POLY1305, LessSafeKey, Nonce, UnboundKey}; + +fn main() { + let key = [42; 32]; + let nonce_data = [124; 12]; // Just an example + let mut data = b"hello, this is my secret message".to_vec(); + + let key = UnboundKey::new(&CHACHA20_POLY1305, &key).unwrap(); + let key = LessSafeKey::new(key); + println!("{data:?}"); + + // encoding + let nonce = Nonce::assume_unique_for_key(nonce_data); + key.seal_in_place_append_tag(nonce, Aad::empty(), &mut data).unwrap(); + println!("{data:?}"); + + // decoding + let nonce = Nonce::assume_unique_for_key(nonce_data); + let data = key.open_in_place(nonce, Aad::empty(), &mut data).unwrap(); + println!("{data:?}"); +} \ No newline at end of file diff --git a/examples/ring_crypto.rs b/examples/ring_crypto.rs new file mode 100644 index 00000000..19e44e68 --- /dev/null +++ b/examples/ring_crypto.rs @@ -0,0 +1,108 @@ +use std::fs::{File, OpenOptions}; +use std::io::{Read, Write}; +use std::path::PathBuf; + +use argon2::password_hash::rand_core::RngCore; +use rand::thread_rng; +use ring::aead::{AES_256_GCM, CHACHA20_POLY1305}; +use secrecy::{ExposeSecret, SecretString, SecretVec}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use tokio::fs; + +use rencfs::crypto; +use rencfs::crypto::Cipher; +use rencfs::crypto::encryptor::CryptoWriter; +use rencfs::encryptedfs::FsError; + +fn main() { + // let password = SecretString::new("password".to_string()); + // let salt = crypto::hash_secret(&password); + // let cipher = Cipher::ChaCha20; + // let key = crypto::derive_key(&password, &cipher, salt).unwrap(); + // + // let cipher = Cipher::ChaCha20; + // + // let path = PathBuf::from("/tmp/test.txt"); + // let mut writer = crypto::create_crypto_writer(OpenOptions::new().read(true).write(true).create(true).open(path.clone()).unwrap(), + // &cipher, &key); + // let x = "Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! Hello, world!".to_string(); + // bincode::serialize_into(&mut writer, &x).unwrap(); + // // writer.write_all(x.as_bytes()).unwrap(); + // writer.flush().unwrap(); + // writer.finish().unwrap(); + // + // let mut reader = crypto::create_crypto_reader(File::open(path).unwrap(), &cipher, &key); + // let mut buf = vec![0; x.len()]; + // // reader.read_exact(&mut buf).unwrap(); + // let dec: String = bincode::deserialize_from(&mut reader).unwrap(); + // // let dec = String::from_utf8(buf).unwrap(); + // println!("{}", dec); + // assert_eq!(dec, x); + + // derive key from password + let password = SecretString::new("password".to_string()); + let salt = crypto::hash_secret(&password); + let cipher = Cipher::ChaCha20; + let derived_key = crypto::derive_key(&password, &cipher, salt).unwrap(); + let path = PathBuf::from("/tmp/key.enc"); + let _ = fs::remove_file(&path); + + // first time, create a random key and encrypt it with the derived key from password + + let mut key: Vec = vec![]; + let key_len = match cipher { + Cipher::ChaCha20 => CHACHA20_POLY1305.key_len(), + Cipher::Aes256Gcm => AES_256_GCM.key_len(), + }; + key.resize(key_len, 0); + thread_rng().fill_bytes(&mut key); + println!("key: {:?}", key); + let key = SecretVec::new(key); + let key_store = KeyStore::new(key); + println!("hash {:?}", key_store.hash); + let mut writer = crypto::create_crypto_writer(OpenOptions::new().read(true).write(true).create(true).open(path.clone()).unwrap(), + &cipher, &derived_key); + bincode::serialize_into(&mut writer, &key_store).unwrap(); + writer.flush().unwrap(); + writer.finish().unwrap(); + + // read key + + let reader = crypto::create_crypto_reader(File::open(path).unwrap(), &cipher, &derived_key); + let key_store: KeyStore = bincode::deserialize_from(reader).map_err(|_| FsError::InvalidPassword).unwrap(); + println!("key {:?}", key_store.key.expose_secret()); + println!("hash {:?}", key_store.hash); + // check hash + if key_store.hash != crypto::hash(key_store.key.expose_secret()) { + eprintln!("Invalid password"); + return; + } +} + +fn key_serialize(key: &SecretVec, s: S) -> Result + where + S: Serializer, +{ + s.collect_seq(key.expose_secret()) +} + +fn key_unserialize<'de, D>(deserializer: D) -> Result, D::Error> + where D: Deserializer<'de> { + let vec = Vec::deserialize(deserializer)?; + Ok(SecretVec::new(vec)) +} + +#[derive(Serialize, Deserialize)] +struct KeyStore { + #[serde(serialize_with = "key_serialize")] + #[serde(deserialize_with = "key_unserialize")] + key: SecretVec, + hash: [u8; 32], +} + +impl KeyStore { + fn new(key: SecretVec) -> Self { + let hash = crypto::hash(key.expose_secret()); + Self { key, hash } + } +} diff --git a/examples/ring_speed.rs b/examples/ring_speed.rs index 6741626b..5903d9ee 100644 --- a/examples/ring_speed.rs +++ b/examples/ring_speed.rs @@ -1,10 +1,11 @@ use std::fs::OpenOptions; -use std::io::{Read, Write}; +use std::io; +use std::io::{BufReader, BufWriter, Read, Write}; use std::path::Path; use ring::error::Unspecified; use ring::rand::SecureRandom; use ring::rand::SystemRandom; -use ring::aead::Algorithm; +use ring::aead::{Algorithm, LessSafeKey}; use ring::aead::AES_128_GCM; use ring::aead::AES_256_GCM; use ring::aead::CHACHA20_POLY1305; @@ -28,17 +29,17 @@ impl NonceSequence for CounterNonceSequence { let bytes = self.0.to_be_bytes(); nonce_bytes[8..].copy_from_slice(&bytes); - println!("nonce_bytes = {}", hex::encode(&nonce_bytes)); + // println!("nonce_bytes = {}", hex::encode(&nonce_bytes)); self.0 += 1; // advance the counter Nonce::try_assume_unique_for_key(&nonce_bytes) } } -fn main() -> std::io::Result<()> { +fn main() -> io::Result<()> { let mut input = OpenOptions::new().read(true).open("/home/gnome/Downloads/Zero.Days.2016.720p.WEBRip.x264.AAC-ETRG/Zero.Days.2016.720p.WEBRip.x264.AAC-ETRG.mp4").unwrap(); - let out_path = Path::new("./encrypted.enc"); - let mut out = OpenOptions::new().create(true).write(true).open(out_path)?; + let out_path = Path::new("/tmp/encrypted.enc"); + let mut out = OpenOptions::new().create(true).write(true).truncate(true).open(out_path)?; // Create a new instance of SystemRandom to be used as the single source of entropy let rand = SystemRandom::new(); @@ -59,31 +60,107 @@ fn main() -> std::io::Result<()> { let mut sealing_key = SealingKey::new(unbound_key, nonce_sequence); // This data will be authenticated but not encrypted -//let associated_data = Aad::empty(); // is optional so can be empty - let associated_data = Aad::from(b"additional public data"); + // let associated_data = Aad::empty(); // is optional so can be empty + // let associated_data = Aad::from(b"additional public data"); + + let file_size = input.metadata()?.len(); + + let mut input = BufReader::new(input); + let mut out = BufWriter::new(out); let start = std::time::Instant::now(); - let mut buffer = [0; 4096]; + let mut buffer = vec![0; 4096]; loop { - let len = input.read(&mut buffer).unwrap(); + let len = { + let mut pos = 0; + loop { + match input.read(&mut buffer[pos..]) { + Ok(read) => { + pos += read; + if read == 0 { + break; + } + } + Err(err) => return Err(err), + } + } + pos + }; if len == 0 { break; } + if len != buffer.len() { + println!("len = {}", len); + } // Data to be encrypted - let data = buffer[..len].to_vec(); + let mut data = &mut buffer[..len]; +// let mut data = buffer[..len].to_vec(); // Create a mutable copy of the data that will be encrypted in place - let mut in_out = data.clone(); +// let mut in_out = data.clone(); // Encrypt the data with AEAD using the AES_256_GCM algorithm - let tag = sealing_key.seal_in_place_separate_tag(associated_data, &mut in_out).unwrap(); + let tag = sealing_key.seal_in_place_separate_tag(Aad::empty(), &mut data).unwrap(); - out.write(&in_out).unwrap(); + out.write(&data).unwrap(); + out.write(tag.as_ref()).unwrap(); } out.flush().unwrap(); let end = std::time::Instant::now(); let duration = end.duration_since(start); - let file_size = input.metadata()?.len(); + println!("duration = {:?}", duration); + println!("speed MB/s {}", (file_size as f64 / duration.as_secs_f64()) / 1024.0 / 1024.0); + + // decrypt + let unbound_key = UnboundKey::new(&CHACHA20_POLY1305, &key_bytes).unwrap(); + let nonce_sequence = CounterNonceSequence(1); + let mut opening_key = OpeningKey::new(unbound_key, nonce_sequence); + + let input = OpenOptions::new().read(true).open(out_path).unwrap(); + let out = OpenOptions::new().create(true).write(true).truncate(true).open(Path::new("/tmp/encrypted.dec"))?; + + let start = std::time::Instant::now(); + let mut buffer = vec![0; 4096 + CHACHA20_POLY1305.tag_len()]; + let mut input = BufReader::new(input); + let mut out = BufWriter::new(out); + loop { + let len = { + let mut pos = 0; + loop { + match input.read(&mut buffer[pos..]) { + Ok(read) => { + pos += read; + if read == 0 { + break; + } + } + Err(err) => return Err(err), + } + } + pos + }; + if len == 0 { + break; + } + if len != buffer.len() { + println!("len = {}", len); + } +// Data to be encrypted + let mut data = &mut buffer[..len]; + +// Create a mutable copy of the data that will be encrypted in place +// let mut in_out = data.clone(); + +// Encrypt the data with AEAD using the AES_256_GCM algorithm + let dec = opening_key.open_within(Aad::empty(), &mut data, 0..).unwrap(); + // let dec = opening_key.open_in_place(Aad::empty(), &mut data).unwrap(); + + out.write(&dec).unwrap(); + } + out.flush().unwrap(); + let end = std::time::Instant::now(); + let duration = end.duration_since(start); +// let file_size = input.metadata()?.len(); println!("duration = {:?}", duration); println!("speed MB/s {}", (file_size as f64 / duration.as_secs_f64()) / 1024.0 / 1024.0); diff --git a/src/crypto.rs b/src/crypto.rs index f5eb0193..7dca1667 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -1,28 +1,28 @@ -pub mod decryptor; -pub mod encryptor; - -use std::fs::{File, OpenOptions}; -use cryptostream::{read, write}; -use std::os::unix::fs::MetadataExt; -use rand::thread_rng; +use std::fs::OpenOptions; use std::io::{Read, Write}; -use base64::decode; use std::io; use std::path::PathBuf; + use argon2::Argon2; -use argon2::password_hash::rand_core::RngCore; +use hex::FromHexError; use num_format::{Locale, ToFormattedString}; -use openssl::sha::sha256; +use ring::aead::{AES_256_GCM, CHACHA20_POLY1305}; use secrecy::{ExposeSecret, SecretString, SecretVec}; -use thiserror::Error; -use tracing::{debug, error, info, instrument}; -use strum_macros::{Display, EnumIter, EnumString}; use serde::{Deserialize, Serialize}; -use openssl::error::ErrorStack; -use crate::crypto::decryptor::{Decryptor, CryptostreamDecryptor}; -use crate::crypto::encryptor::{Encryptor, CryptostreamEncryptor}; +use sha2::{Digest, Sha256}; +use strum_macros::{Display, EnumIter, EnumString}; +use thiserror::Error; +use tracing::{debug, error, instrument}; + +use crate::crypto::decryptor::{CryptoReader, RingCryptoReader}; +use crate::crypto::encryptor::{CryptoWriter, RingCryptoWriter}; +use crate::encryptedfs::FsResult; use crate::stream_util; +pub mod decryptor; +pub mod encryptor; +pub mod buf_mut; + #[derive(Debug, Clone, EnumIter, EnumString, Display, Serialize, Deserialize, PartialEq)] pub enum Cipher { ChaCha20, @@ -31,10 +31,22 @@ pub enum Cipher { #[derive(Debug, Error)] pub enum Error { - #[error("cryptostream error: {source}")] - OpenSsl { + // #[error("cryptostream error: {source}")] + // OpenSsl { + // #[from] + // source: ErrorStack, + // // backtrace: Backtrace, + // }, + #[error("IO error: {source}")] + Io { #[from] - source: ErrorStack, + source: io::Error, + // backtrace: Backtrace, + }, + #[error("hex error: {source}")] + Hex { + #[from] + source: FromHexError, // backtrace: Backtrace, }, #[error("crypto error: {0}")] @@ -43,74 +55,95 @@ pub enum Error { pub type Result = std::result::Result; -pub fn create_encryptor(mut file: File, cipher: &Cipher, key: &SecretVec) -> impl Encryptor { - let iv_len = match cipher { - Cipher::ChaCha20 => 16, - Cipher::Aes256Gcm => 16, +pub fn create_crypto_writer(writer: W, cipher: &Cipher, key: &SecretVec) -> impl CryptoWriter { + // create_cryptostream_crypto_writer(file, cipher, key) + create_ring_crypto_writer(writer, cipher, key) +} + +fn create_ring_crypto_writer(writer: W, cipher: &Cipher, key: &SecretVec) -> RingCryptoWriter { + let algorithm = match cipher { + Cipher::ChaCha20 => &CHACHA20_POLY1305, + Cipher::Aes256Gcm => &AES_256_GCM, }; - let mut iv: Vec = vec![0; iv_len]; - if file.metadata().unwrap().size() == 0 { - // generate random IV - thread_rng().fill_bytes(&mut iv); - file.write_all(&iv).unwrap(); - } else { - // read IV from file - file.read_exact(&mut iv).unwrap(); - } - CryptostreamEncryptor::new(file, get_cipher(cipher), &key.expose_secret(), &iv).unwrap() + RingCryptoWriter::new(writer, algorithm, &key.expose_secret()) } -#[instrument(skip(key))] -pub fn create_decryptor(mut file: File, cipher: &Cipher, key: &SecretVec) -> impl Decryptor { - let iv_len = match cipher { - Cipher::ChaCha20 => 16, - Cipher::Aes256Gcm => 16, +fn create_ring_crypto_reader(reader: R, cipher: &Cipher, key: &SecretVec) -> RingCryptoReader { + let algorithm = match cipher { + Cipher::ChaCha20 => &CHACHA20_POLY1305, + Cipher::Aes256Gcm => &AES_256_GCM, }; - let mut iv: Vec = vec![0; iv_len]; - if file.metadata().unwrap().size() == 0 { - // generate random IV - thread_rng().fill_bytes(&mut iv); - file.write_all(&iv).map_err(|err| { - error!("{err}"); - err - }).unwrap(); - } else { - // read IV from file - file.read_exact(&mut iv).map_err(|err| { - error!("{err}"); - err - }).unwrap(); - } - CryptostreamDecryptor::new(file, get_cipher(cipher), &key.expose_secret(), &iv).unwrap() + RingCryptoReader::new(reader, algorithm, &key.expose_secret()) } -pub fn encrypt_string(s: &SecretString, cipher: &Cipher, key: &SecretVec) -> String { - // use the same IV so the same string will be encrypted to the same value - let iv: Vec<_> = decode("dB0Ej+7zWZWTS5JUCldWMg==").unwrap(); +// fn _create_cryptostream_crypto_writer(mut file: File, cipher: &Cipher, key: &SecretVec) -> impl CryptoWriter { +// let iv_len = match cipher { +// Cipher::ChaCha20 => 16, +// Cipher::Aes256Gcm => 16, +// }; +// let mut iv: Vec = vec![0; iv_len]; +// if file.metadata().unwrap().size() == 0 { +// // generate random IV +// thread_rng().fill_bytes(&mut iv); +// file.write_all(&iv).unwrap(); +// } else { +// // read IV from file +// file.read_exact(&mut iv).unwrap(); +// } +// CryptostreamCryptoWriter::new(file, get_cipher(cipher), &key.expose_secret(), &iv).unwrap() +// } + +#[instrument(skip(reader, key))] +pub fn create_crypto_reader(reader: R, cipher: &Cipher, key: &SecretVec) -> impl CryptoReader { + // create_cryptostream_crypto_reader(file, cipher, &key) + create_ring_crypto_reader(reader, cipher, &key) +} +// fn _create_cryptostream_crypto_reader(mut file: File, cipher: &Cipher, key: &SecretVec) -> CryptostreamCryptoReader { +// let iv_len = match cipher { +// Cipher::ChaCha20 => 16, +// Cipher::Aes256Gcm => 16, +// }; +// let mut iv: Vec = vec![0; iv_len]; +// if file.metadata().unwrap().size() == 0 { +// // generate random IV +// thread_rng().fill_bytes(&mut iv); +// file.write_all(&iv).map_err(|err| { +// error!("{err}"); +// err +// }).unwrap(); +// } else { +// // read IV from file +// file.read_exact(&mut iv).map_err(|err| { +// error!("{err}"); +// err +// }).unwrap(); +// } +// CryptostreamCryptoReader::new(file, get_cipher(cipher), &key.expose_secret(), &iv).unwrap() +// } + +pub fn encrypt_string(s: &SecretString, cipher: &Cipher, key: &SecretVec) -> Result { let mut cursor = io::Cursor::new(vec![]); - let mut encryptor = write::Encryptor::new(cursor, get_cipher(cipher), key.expose_secret(), &iv).unwrap(); - encryptor.write_all(s.expose_secret().as_bytes()).unwrap(); - cursor = encryptor.finish().unwrap(); - base64::encode(&cursor.into_inner()) + let mut writer = create_crypto_writer(cursor, cipher, key); + writer.write_all(s.expose_secret().as_bytes()).unwrap(); + writer.flush().unwrap(); + cursor = writer.finish()?.unwrap(); + Ok(hex::encode(&cursor.into_inner())) } -pub fn decrypt_string(s: &str, cipher: &Cipher, key: &SecretVec) -> SecretString { - // use the same IV so the same string will be encrypted to the same value&SecretString::from_str( - let iv: Vec<_> = decode("dB0Ej+7zWZWTS5JUCldWMg==").unwrap(); - - let vec = decode(s).unwrap(); +pub fn decrypt_string(s: &str, cipher: &Cipher, key: &SecretVec) -> Result { + let vec = hex::decode(s)?; let cursor = io::Cursor::new(vec); - let mut decryptor = read::Decryptor::new(cursor, get_cipher(cipher), &key.expose_secret(), &iv).unwrap(); + let mut reader = create_crypto_reader(cursor, cipher, key); let mut decrypted = String::new(); - decryptor.read_to_string(&mut decrypted).unwrap(); - SecretString::new(decrypted) + reader.read_to_string(&mut decrypted)?; + Ok(SecretString::new(decrypted)) } -pub fn decrypt_and_unnormalize_end_file_name(name: &str, cipher: &Cipher, key: &SecretVec) -> SecretString { - let name = String::from(name).replace("|", "/"); +pub fn decrypt_and_unnormalize_end_file_name(name: &str, cipher: &Cipher, key: &SecretVec) -> Result { + // let name = String::from(name).replace("|", "/"); decrypt_string(&name, cipher, key) } @@ -127,29 +160,24 @@ pub fn derive_key(password: &SecretString, cipher: &Cipher, salt: SecretVec) Ok(SecretVec::new(dk)) } -pub fn normalize_end_encrypt_file_name(name: &SecretString, cipher: &Cipher, key: &SecretVec) -> String { +pub fn encrypt_file_name(name: &SecretString, cipher: &Cipher, key: &SecretVec) -> FsResult { if name.expose_secret() != "$." && name.expose_secret() != "$.." { let normalized_name = SecretString::new(name.expose_secret().replace("/", " ").replace("\\", " ")); - let mut encrypted = encrypt_string(&normalized_name, cipher, key); - encrypted = encrypted.replace("/", "|"); - return encrypted; + let encrypted = encrypt_string(&normalized_name, cipher, key)?; + // encrypted = encrypted.replace("/", "|"); + return Ok(encrypted); } - name.expose_secret().to_owned() + Ok(name.expose_secret().to_owned()) } pub fn hash(data: &[u8]) -> [u8; 32] { - sha256(data) + let mut hasher = Sha256::new(); + hasher.update(data); + hasher.finalize().into() } pub fn hash_secret(data: &SecretString) -> SecretVec { - SecretVec::new(sha256(data.expose_secret().as_bytes()).to_vec()) -} - -fn get_cipher(cipher: &Cipher) -> openssl::symm::Cipher { - match cipher { - Cipher::ChaCha20 => openssl::symm::Cipher::chacha20(), - Cipher::Aes256Gcm => openssl::symm::Cipher::aes_256_gcm(), - } + SecretVec::new(hash(data.expose_secret().as_bytes()).to_vec()) } /// Copy from `pos` position in file `len` bytes @@ -160,13 +188,13 @@ pub fn copy_from_file_exact(w: &mut impl Write, pos: u64, len: u64, cipher: &Cip // no-op return Ok(()); } - // create a new decryptor by reading from the beginning of the file - let mut decryptor = create_decryptor(OpenOptions::new().read(true).open(file)?, cipher, key); + // create a new reader by reading from the beginning of the file + let mut reader = create_crypto_reader(OpenOptions::new().read(true).open(file)?, cipher, key); // move read position to the write position - stream_util::read_seek_forward_exact(&mut decryptor, pos)?; + stream_util::read_seek_forward_exact(&mut reader, pos)?; // copy the rest of the file - stream_util::copy_exact(&mut decryptor, w, len)?; - decryptor.finish(); + stream_util::copy_exact(&mut reader, w, len)?; + reader.finish(); Ok(()) } diff --git a/src/crypto/buf_mut.rs b/src/crypto/buf_mut.rs new file mode 100644 index 00000000..c088d60e --- /dev/null +++ b/src/crypto/buf_mut.rs @@ -0,0 +1,107 @@ +use std::cmp::min; +use std::io; +use std::io::{Read, Seek, SeekFrom, Write}; + +use secrecy::Zeroize; + +pub struct BufMut { + buf: Vec, + pos: usize, + read_pos: usize, +} + +impl BufMut { + pub fn new(from: Vec) -> Self { + Self { + buf: from, + pos: 0, + read_pos: 0, + } + } + + pub fn remaining(&self) -> usize { + self.buf.len() - self.pos + } + + pub fn as_mut(&mut self) -> &mut [u8] { + &mut self.buf[..self.pos] + } + + pub fn as_mut_read(&mut self) -> &mut [u8] { + &mut self.buf[self.pos..] + } + + pub fn as_ref(&self) -> &[u8] { + &self.buf[..self.pos] + } + + pub fn clear(&mut self) { + self.pos = 0; + self.read_pos = 0; + } + + pub fn pos(&self) -> usize { + self.pos + } + + pub fn available(&self) -> usize { + self.pos() + } +} + +impl Write for BufMut { + fn write(&mut self, buf: &[u8]) -> io::Result { + let len = min(self.remaining(), buf.len()); + self.buf[self.pos..self.pos + len].copy_from_slice(&buf[..len]); + self.pos += len; + Ok(len) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl Seek for BufMut { + fn seek(&mut self, pos: SeekFrom) -> io::Result { + match pos { + SeekFrom::Start(pos) => { + if pos as usize > self.buf.len() { + return Err(io::Error::new(io::ErrorKind::InvalidInput, "position is out of bounds")); + } + self.pos = pos as usize; + } + SeekFrom::End(pos) => { + if (self.buf.len() as i64 + pos) < 0 || (self.buf.len() as i64 + pos) > self.buf.len() as i64 { + return Err(io::Error::new(io::ErrorKind::InvalidInput, "position is out of bounds")); + } + self.pos = (self.buf.len() as i64 + pos) as usize; + } + SeekFrom::Current(pos) => { + if (self.pos as i64 + pos) < 0 || (self.pos as i64 + pos) > self.buf.len() as i64 { + return Err(io::Error::new(io::ErrorKind::InvalidInput, "position is out of bounds")); + } + self.pos = (self.pos as i64 + pos) as usize; + } + } + Ok(self.pos as u64) + } +} + +impl Read for BufMut { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let len = min(self.pos() - self.read_pos, buf.len()); + if len == 0 { + return Ok(0); + } + buf[..len].copy_from_slice(&self.buf[self.read_pos..self.read_pos + len]); + self.read_pos += len; + Ok(len) + } +} + +impl Drop for BufMut { + fn drop(&mut self) { + self.buf.zeroize() + } +} \ No newline at end of file diff --git a/src/crypto/decryptor.rs b/src/crypto/decryptor.rs index b0040d67..960cc5d9 100644 --- a/src/crypto/decryptor.rs +++ b/src/crypto/decryptor.rs @@ -1,37 +1,116 @@ -use std::io::Read; +use std::io; +use std::io::{Read, Seek, SeekFrom}; -use openssl::symm::Cipher; +use ring::aead::{Aad, Algorithm, BoundKey, OpeningKey, UnboundKey}; -use crate::crypto; +use crate::crypto::buf_mut::BufMut; +use crate::crypto::encryptor::{BUF_SIZE, CounterNonceSequence}; -pub trait Decryptor: Read + Send + Sync { - fn finish(&mut self) -> R; +pub trait CryptoReader: Read + Seek + Send + Sync { + fn finish(&mut self) -> Option; } -pub struct CryptostreamDecryptor { - inner: Option>, +/// cryptostream + +// pub struct CryptostreamCryptoReader { +// inner: Option>, +// } +// +// impl CryptostreamCryptoReader { +// pub fn new(reader: R, cipher: Cipher, key: &[u8], iv: &[u8]) -> crypto::Result { +// Ok(Self { +// inner: Some(cryptostream::read::Decryptor::new(reader, cipher, key, iv)?), +// }) +// } +// } +// +// impl Read for CryptostreamCryptoReader { +// fn read(&mut self, buf: &mut [u8]) -> io::Result { +// self.inner.as_mut().unwrap().read(buf) +// } +// } +// +// impl Seek for CryptostreamCryptoReader { +// fn seek(&mut self, pos: SeekFrom) -> io::Result { +// todo!() +// } +// } +// +// impl CryptoReader for CryptostreamCryptoReader { +// fn finish(&mut self) -> Option { +// Some(self.inner.take().unwrap().finish()) +// } +// } + +/// ring + +pub struct RingCryptoReader { + input: Option, + opening_key: OpeningKey, + buf: BufMut, } -impl CryptostreamDecryptor { - pub fn new(reader: R, cipher: Cipher, key: &[u8], iv: &[u8]) -> crypto::Result { - Ok(Self { - inner: Some(cryptostream::read::Decryptor::new(reader, cipher, key, iv)?), - }) +impl RingCryptoReader { + pub fn new<'a: 'static>(r: R, algorithm: &'a Algorithm, key: &[u8]) -> Self { + let unbound_key = UnboundKey::new(algorithm, &key).unwrap(); + let nonce_sequence = CounterNonceSequence(1); + let opening_key = OpeningKey::new(unbound_key, nonce_sequence); + let buf = BufMut::new(vec![0; BUF_SIZE + algorithm.tag_len()]); + Self { + input: Some(r), + opening_key, + buf, + } } } -impl Read for CryptostreamDecryptor { - fn read(&mut self, buf: &mut [u8]) -> std::io::Result { - self.inner.as_mut().unwrap().read(buf) +impl Read for RingCryptoReader { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + // first try to read remaining decrypted data + let len = self.buf.read(buf)?; + if len != 0 { + return Ok(len); + } + // we read all the data from the buffer, so we need to read a new block and decrypt it + let pos = { + self.buf.clear(); + let buffer = self.buf.as_mut_read(); + let len = { + let mut pos = 0; + loop { + match self.input.as_mut().unwrap().read(&mut buffer[pos..]) { + Ok(read) => { + pos += read; + if read == 0 { + break; + } + } + Err(err) => return Err(err), + } + } + pos + }; + if len == 0 { + return Ok(0); + } + let mut data = &mut buffer[..len]; + let plaintext = self.opening_key.open_within(Aad::empty(), &mut data, 0..).unwrap(); + plaintext.len() + }; + self.buf.seek(SeekFrom::Start(pos as u64)).unwrap(); + let len = self.buf.read(buf)?; + Ok(len) } } -impl Decryptor for CryptostreamDecryptor { - fn finish(&mut self) -> R { - self.inner.take().unwrap().finish() +impl Seek for RingCryptoReader { + fn seek(&mut self, pos: SeekFrom) -> io::Result { + todo!() } } -pub struct AesStreamDecryptor { - inner: Option>, -} +impl CryptoReader for RingCryptoReader { + fn finish(&mut self) -> Option { + Some(self.input.take().unwrap()) + } +} \ No newline at end of file diff --git a/src/crypto/encryptor.rs b/src/crypto/encryptor.rs index fc7f72b5..26570967 100644 --- a/src/crypto/encryptor.rs +++ b/src/crypto/encryptor.rs @@ -1,36 +1,135 @@ use std::io; -use std::io::Write; -use openssl::symm::Cipher; -use crate::crypto; +use std::io::{BufWriter, Write}; -pub trait Encryptor: Write + Sync + Send { - fn finish(&mut self) -> io::Result; +use ring::aead::{Aad, Algorithm, BoundKey, Nonce, NONCE_LEN, NonceSequence, SealingKey, UnboundKey}; +use ring::error::Unspecified; +use tracing::{error, instrument}; + +use crate::crypto::buf_mut::BufMut; + +pub trait CryptoWriter: Write + Sync + Send { + fn finish(&mut self) -> io::Result>; } -pub struct CryptostreamEncryptor { - inner: Option>, +/// cryptostream + +// pub struct CryptostreamCryptoWriter { +// inner: Option>, +// } +// +// impl CryptostreamCryptoWriter { +// pub fn new(writer: W, cipher: Cipher, key: &[u8], iv: &[u8]) -> crypto::Result { +// Ok(Self { +// inner: Some(cryptostream::write::Encryptor::new(writer, cipher, key, iv)?), +// }) +// } +// } +// +// impl Write for CryptostreamCryptoWriter { +// fn write(&mut self, buf: &[u8]) -> io::Result { +// self.inner.as_mut().unwrap().write(buf) +// } +// +// fn flush(&mut self) -> io::Result<()> { +// self.inner.as_mut().unwrap().flush() +// } +// } +// +// impl CryptoWriter for CryptostreamCryptoWriter { +// fn finish(&mut self) -> io::Result> { +// Ok(Some(self.inner.take().unwrap().finish()?)) +// } +// } + +/// ring +#[cfg(test)] +pub(crate) const BUF_SIZE: usize = 256 * 1024; +// 256 KB buffer, smaller for tests because they all run in parallel +#[cfg(not(test))] +pub(crate) const BUF_SIZE: usize = 1024 * 1024; // 1 MB buffer + +pub struct RingCryptoWriter { + out: Option>, + sealing_key: SealingKey, + buf: BufMut, } -impl CryptostreamEncryptor { - pub fn new(writer: W, cipher: Cipher, key: &[u8], iv: &[u8]) -> crypto::Result { - Ok(Self { - inner: Some(cryptostream::write::Encryptor::new(writer, cipher, key, iv)?), - }) +impl RingCryptoWriter { + pub fn new<'a: 'static>(w: W, algorithm: &'a Algorithm, key: &[u8]) -> Self { + // todo: param for start nonce sequence + let unbound_key = UnboundKey::new(&algorithm, &key).unwrap(); + let nonce_sequence = CounterNonceSequence(1); + let sealing_key = SealingKey::new(unbound_key, nonce_sequence); + let buf = BufMut::new(vec![0; BUF_SIZE]); + Self { + out: Some(BufWriter::new(w)), + sealing_key, + buf, + } } } -impl Write for CryptostreamEncryptor { +impl Write for RingCryptoWriter { fn write(&mut self, buf: &[u8]) -> io::Result { - self.inner.as_mut().unwrap().write(buf) + if self.buf.remaining() == 0 { + self.flush()?; + } + let len = self.buf.write(buf)?; + Ok(len) } + #[instrument(name = "RingEncryptor::flush", skip(self))] fn flush(&mut self) -> io::Result<()> { - self.inner.as_mut().unwrap().flush() + if self.buf.available() == 0 { + return Ok(()); + } + if self.buf.remaining() == 0 { + // encrypt and write when we have a full buffer + self.encrypt_and_write()?; + } + + Ok(()) + } +} + +impl RingCryptoWriter { + fn encrypt_and_write(&mut self) -> io::Result<()> { + let mut data = self.buf.as_mut(); + let tag = self.sealing_key.seal_in_place_separate_tag(Aad::empty(), &mut data).map_err(|err| { + error!("error sealing in place: {}", err); + io::Error::from(io::ErrorKind::Other) + })?; + self.out.as_mut().unwrap().write_all(&data)?; + self.buf.clear(); + self.out.as_mut().unwrap().write_all(tag.as_ref())?; + self.out.as_mut().unwrap().flush()?; + Ok(()) } } -impl Encryptor for CryptostreamEncryptor { - fn finish(&mut self) -> io::Result { - Ok(self.inner.take().unwrap().finish()?) +impl CryptoWriter for RingCryptoWriter { + fn finish(&mut self) -> io::Result> { + self.flush()?; + if self.buf.available() > 0 { + // encrypt and write last block, use as many bytes we have + self.encrypt_and_write()?; + } + Ok(Some(self.out.take().unwrap().into_inner()?)) } } + +pub(crate) struct CounterNonceSequence(pub(crate) u32); + +impl NonceSequence for CounterNonceSequence { + // called once for each seal operation + fn advance(&mut self) -> Result { + let mut nonce_bytes = vec![0; NONCE_LEN]; + + let bytes = self.0.to_be_bytes(); + nonce_bytes[8..].copy_from_slice(&bytes); + // println!("nonce_bytes = {}", hex::encode(&nonce_bytes)); + + self.0 += 1; // advance the counter + Nonce::try_assume_unique_for_key(&nonce_bytes) + } +} \ No newline at end of file diff --git a/src/encryptedfs.rs b/src/encryptedfs.rs index e9f84fb5..8113ec3d 100644 --- a/src/encryptedfs.rs +++ b/src/encryptedfs.rs @@ -1,5 +1,5 @@ use std::{fs, io}; -use std::cmp::{max, min}; +use std::cmp::max; use std::collections::{HashMap, HashSet}; use std::fmt::Debug; use std::fs::{File, OpenOptions, ReadDir}; @@ -15,18 +15,19 @@ use argon2::password_hash::rand_core::RngCore; use futures_util::TryStreamExt; use num_format::{Locale, ToFormattedString}; use rand::thread_rng; +use ring::aead::{AES_256_GCM, CHACHA20_POLY1305}; use secrecy::{ExposeSecret, SecretString, SecretVec}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use thiserror::Error; use tokio::sync::{Mutex, MutexGuard, RwLock}; use tokio_stream::wrappers::ReadDirStream; -use tracing::{debug, error, info, instrument, warn}; +use tracing::{debug, error, instrument, warn}; +use crate::{crypto, stream_util}; use crate::arc_hashmap::{ArcHashMap, Guard}; -use crate::{crypto, expire_value, stream_util}; use crate::crypto::Cipher; -use crate::crypto::decryptor::Decryptor; -use crate::crypto::encryptor::Encryptor; +use crate::crypto::decryptor::CryptoReader; +use crate::crypto::encryptor::CryptoWriter; use crate::expire_value::{ExpireValue, Provider}; #[cfg(test)] @@ -374,15 +375,22 @@ impl Iterator for DirectoryEntryIterator { let name = entry.file_name().to_string_lossy().to_string(); let name = { if name == "$." { - SecretString::from_str(".").unwrap() + Some(SecretString::from_str(".").unwrap()) } else if name == "$.." { - SecretString::from_str("..").unwrap() + Some(SecretString::from_str("..").unwrap()) } else { - crypto::decrypt_and_unnormalize_end_file_name(&name, &self.1, &self.2) + crypto::decrypt_and_unnormalize_end_file_name(&name, &self.1, &self.2).map_err(|err| { + error!(err = %err, "decrypting and unnormalizing end file name"); + err + }).ok() } }; + if name.is_none() { + return Some(Err(FsError::InvalidInput("invalid file name".to_string()))); + } + let name = name.unwrap(); - let res: bincode::Result<(u64, FileType)> = bincode::deserialize_from(crypto::create_decryptor(file, &self.1, &self.2)); + let res: bincode::Result<(u64, FileType)> = bincode::deserialize_from(crypto::create_crypto_reader(file, &self.1, &self.2)); if let Err(e) = res { return Some(Err(e.into())); } @@ -420,14 +428,22 @@ impl Iterator for DirectoryEntryPlusIterator { let name = entry.file_name().to_string_lossy().to_string(); let name = { if name == "$." { - SecretString::from_str(".").unwrap() + Some(SecretString::from_str(".").unwrap()) } else if name == "$.." { - SecretString::from_str("..").unwrap() + Some(SecretString::from_str("..").unwrap()) } else { - crypto::decrypt_and_unnormalize_end_file_name(&name, &self.2, &self.3) + crypto::decrypt_and_unnormalize_end_file_name(&name, &self.2, &self.3).map_err(|err| { + error!(err = %err, "decrypting and unnormalizing end file name"); + err + }).ok() } }; - let res: bincode::Result<(u64, FileType)> = bincode::deserialize_from(crypto::create_decryptor(file, &self.2, &self.3)); + if name.is_none() { + return Some(Err(FsError::InvalidInput("invalid file name".to_string()))); + } + let name = name.unwrap(); + + let res: bincode::Result<(u64, FileType)> = bincode::deserialize_from(crypto::create_crypto_reader(file, &self.2, &self.3)); if let Err(e) = res { error!(err = %e, "deserializing directory entry"); return Some(Err(e.into())); @@ -444,7 +460,7 @@ impl Iterator for DirectoryEntryPlusIterator { return Some(Err(e.into())); } let file = file.unwrap(); - let attr = bincode::deserialize_from(crypto::create_decryptor(file, &self.2, &self.3)); + let attr = bincode::deserialize_from(crypto::create_crypto_reader(file, &self.2, &self.3)); if let Err(e) = attr { error!(err = %e, "deserializing file attr"); return Some(Err(e.into())); @@ -491,7 +507,7 @@ struct ReadHandleContext { ino: u64, attr: TimeAndSizeFileAttr, pos: u64, - decryptor: Option>>, + reader: Option>>, _lock: Guard>, // we don't use it but just keep a reference to keep it alive in `read_write_inode_locks` while handle is open } @@ -539,7 +555,7 @@ struct WriteHandleContext { attr: TimeAndSizeFileAttr, path: PathBuf, pos: u64, - encryptor: Option>>, + writer: Option>>, _lock: Guard>, // we don't use it but just keep a reference to keep it alive in `read_write_inode_locks` while handle is open } @@ -549,7 +565,7 @@ struct KeyProvider { cipher: Cipher, } -impl expire_value::Provider, FsError> for KeyProvider { +impl Provider, FsError> for KeyProvider { fn provide(&self) -> Result, FsError> { let password = self.password_provider.get_password().ok_or(FsError::InvalidPassword)?; EncryptedFs::read_or_create_key(&self.path, &password, &self.cipher) @@ -579,12 +595,6 @@ pub struct EncryptedFs { key: ExpireValue, FsError, KeyProvider>, } -#[cfg(test)] -const BUF_SIZE: usize = 256 * 1024; -// 256 KB buffer, smaller for tests because they all run in parallel -#[cfg(not(test))] -const BUF_SIZE: usize = 1024 * 1024; // 1 MB buffer - impl EncryptedFs { pub async fn new(data_dir: &str, password_provider: Box, cipher: Cipher) -> FsResult { let path = PathBuf::from(&data_dir); @@ -613,7 +623,7 @@ impl EncryptedFs { key: ExpireValue::new(key_provider, Duration::from_secs(10 * 60)).await, }; - let _ = fs.ensure_root_exists().await; + fs.ensure_root_exists().await?; Ok(fs) } @@ -724,9 +734,9 @@ impl EncryptedFs { SecretString::new(name.expose_secret().to_owned()) } }; - let name = crypto::normalize_end_encrypt_file_name(&name, &self.cipher, &*self.key.get().await?); + let name = crypto::encrypt_file_name(&name, &self.cipher, &*self.key.get().await?)?; let file = File::open(self.data_dir.join(CONTENTS_DIR).join(parent.to_string()).join(name))?; - let (inode, _): (u64, FileType) = bincode::deserialize_from(crypto::create_decryptor(file, &self.cipher, &*self.key.get().await?))?; + let (inode, _): (u64, FileType) = bincode::deserialize_from(crypto::create_crypto_reader(file, &self.cipher, &*self.key.get().await?))?; Ok(Some(self.get_inode(inode).await?)) } @@ -823,7 +833,7 @@ impl EncryptedFs { SecretString::new(name.expose_secret().to_owned()) } }; - let name = crypto::normalize_end_encrypt_file_name(&name, &self.cipher, &*self.key.get().await?); + let name = crypto::encrypt_file_name(&name, &self.cipher, &*self.key.get().await?)?; Ok(self.data_dir.join(CONTENTS_DIR).join(parent.to_string()).join(name).exists()) } @@ -856,7 +866,7 @@ impl EncryptedFs { let path = self.data_dir.join(INODES_DIR).join(ino.to_string()); let file = OpenOptions::new().read(true).write(true).open(path).map_err(|_| { FsError::InodeNotFound })?; - Ok(bincode::deserialize_from::>, FileAttr>(Box::new(crypto::create_decryptor(file, &self.cipher, key)))?) + Ok(bincode::deserialize_from::>, FileAttr>(Box::new(crypto::create_crypto_reader(file, &self.cipher, key)))?) } pub async fn get_inode(&self, ino: u64) -> FsResult { @@ -871,7 +881,10 @@ impl EncryptedFs { for fh in fhs { if let Some(ctx) = self.read_handles.read().await.get(&fh) { let ctx = ctx.lock().await; - merge_attr(&mut attr, ctx.attr.clone().into()); + let mut attr1: SetFileAttr = ctx.attr.clone().into(); + // we don't want to set size because readers don't change the size and we might have an older version + attr1.size.take(); + merge_attr(&mut attr, attr1); } } } @@ -916,7 +929,10 @@ impl EncryptedFs { .create(true) .truncate(true) .open(&path)?; - bincode::serialize_into(crypto::create_encryptor(file, &self.cipher, key), &attr)?; + let mut writer = crypto::create_crypto_writer(file, &self.cipher, key); + bincode::serialize_into(&mut writer, &attr)?; + writer.flush()?; + writer.finish()?; Ok(()) } @@ -970,7 +986,7 @@ impl EncryptedFs { if ctx.pos > offset { // if we need an offset before the current position, we can't seek back, we need // to read from the beginning until the desired offset - debug!("seeking back, recreating decryptor"); + debug!("seeking back, recreating reader"); self.do_with_read_handle(handle, ReadHandleContextOperation::RecreateDecryptor { existing: ctx }).await?; ctx = guard.get(&handle).unwrap().lock().await; } @@ -979,17 +995,17 @@ impl EncryptedFs { debug!(pos = pos.to_formatted_string(&Locale::en), offset = offset.to_formatted_string(&Locale::en), file_size = ctx.attr.size.to_formatted_string(&Locale::en), actual_file_size = actual_file_size.to_formatted_string(&Locale::en), "seeking"); let len = offset - pos; - stream_util::read_seek_forward_exact(&mut ctx.decryptor.as_mut().unwrap(), len)?; + stream_util::read_seek_forward_exact(&mut ctx.reader.as_mut().unwrap(), len)?; ctx.pos += len; } if offset + buf.len() as u64 > ctx.attr.size { buf = &mut buf[..(ctx.attr.size - offset) as usize]; } - ctx.decryptor.as_mut().unwrap().read_exact(&mut buf).map_err(|err| { - error!(err = %err, pos = ctx.pos.to_formatted_string(&Locale::en), offset = offset.to_formatted_string(&Locale::en), file_size = ctx.attr.size.to_formatted_string(&Locale::en), "reading from decryptor"); + let len = ctx.reader.as_mut().unwrap().read(&mut buf).map_err(|err| { + error!(err = %err, pos = ctx.pos.to_formatted_string(&Locale::en), offset = offset.to_formatted_string(&Locale::en), file_size = ctx.attr.size.to_formatted_string(&Locale::en), "reading from reader"); err })?; - ctx.pos += buf.len() as u64; + ctx.pos += len as u64; ctx.attr.atime = SystemTime::now(); @@ -1009,19 +1025,25 @@ impl EncryptedFs { if let Some(ctx) = ctx { let mut ctx = ctx.lock().await; + { + let mut opened_files_for_read = self.opened_files_for_read.write().await; + opened_files_for_read.get_mut(&ctx.ino).and_then(|set| { + set.remove(&handle); + Some(()) + }); + if opened_files_for_read.get(&ctx.ino).unwrap().is_empty() { + opened_files_for_read.remove(&ctx.ino); + } + } + // write attr only here to avoid serializing it multiple times while reading // it will merge time fields with existing data because it might got change while we kept the handle - self.update_inode(ctx.ino, ctx.attr.clone().into()).await?; + let mut attr: SetFileAttr = ctx.attr.clone().into(); + // we don't want to set size because readers don't change the size and we might have an older version + attr.size.take(); + self.update_inode(ctx.ino, attr).await?; - ctx.decryptor.take().unwrap().finish(); - let mut opened_files_for_read = self.opened_files_for_read.write().await; - opened_files_for_read.get_mut(&ctx.ino).and_then(|set| { - set.remove(&handle); - Some(()) - }); - if opened_files_for_read.get(&ctx.ino).unwrap().is_empty() { - opened_files_for_read.remove(&ctx.ino); - } + ctx.reader.take().unwrap().finish(); valid_fh = true; } @@ -1038,12 +1060,12 @@ impl EncryptedFs { let file_size = ctx.attr.size; let pos = ctx.pos; let len = file_size - pos; - crypto::copy_from_file_exact(&mut ctx.encryptor.as_mut().unwrap(), pos, len, &self.cipher, &*self.key.get().await?, self.data_dir.join(CONTENTS_DIR).join(ino_str))?; + crypto::copy_from_file_exact(&mut ctx.writer.as_mut().unwrap(), pos, len, &self.cipher, &*self.key.get().await?, self.data_dir.join(CONTENTS_DIR).join(ino_str))?; ctx.pos += len; } - debug!("finishing encryptor"); - ctx.encryptor.take().unwrap().finish()?; + debug!("finishing writer"); + ctx.writer.take().unwrap().finish()?; // if we are in tmp file move it to actual file let mut recreate_readers = false; if ctx.path.to_str().unwrap().ends_with(".tmp") { @@ -1092,8 +1114,8 @@ impl EncryptedFs { /// If we write outside of file size, we fill up with zeros until offset. /// If the file is not opened for write, it will return an error of type ['FsError::InvalidFileHandle']. #[instrument(skip(self, buf))] - pub async fn write_all(&self, ino: u64, offset: u64, buf: &[u8], handle: u64) -> FsResult<()> { - debug!("write_all"); + pub async fn write(&self, ino: u64, offset: u64, buf: &[u8], handle: u64) -> FsResult { + debug!(""); // lock for writing let lock = { let map_guard = self.read_write_inode_locks.lock().await; @@ -1117,7 +1139,7 @@ impl EncryptedFs { // write lock to avoid writing from multiple threads // let binding = self.get_read_write_inode_lock(ino).await; // let _guard = binding.write(); - debug!("write_all after lock"); + debug!("after lock"); if self.is_dir(ino) { return Err(FsError::InvalidInodeType); @@ -1131,7 +1153,7 @@ impl EncryptedFs { } if buf.len() == 0 { // no-op - return Ok(()); + return Ok(0); } let (path, pos, ino, size) = { @@ -1139,6 +1161,9 @@ impl EncryptedFs { let ctx = guard.get(&handle).unwrap().lock().await; (ctx.path.clone(), ctx.pos, ctx.ino, ctx.attr.size) }; + if size == 0 { + debug!("file size is 0"); + } if pos != offset { debug!("seeking to offset {} from pos {}",offset.to_formatted_string(&Locale::en),pos.to_formatted_string(&Locale::en)); if pos < offset && pos > 0 { @@ -1148,13 +1173,14 @@ impl EncryptedFs { let guard = self.write_handles.read().await; let mut ctx = guard.get(&handle).unwrap().lock().await; let ino_str = ino.to_string(); - let len = offset - pos; - crypto::copy_from_file_exact(&mut ctx.encryptor.as_mut().unwrap(), pos, len, &self.cipher, &*self.key.get().await?, self.data_dir.join(CONTENTS_DIR).join(ino_str))?; + let offset_in_bounds = offset.min(size); + let len = offset_in_bounds - pos; + crypto::copy_from_file_exact(&mut ctx.writer.as_mut().unwrap(), pos, len, &self.cipher, &*self.key.get().await?, self.data_dir.join(CONTENTS_DIR).join(ino_str))?; ctx.pos += len; } else { debug!("seeking backward or from the beginning of the file"); // we need to seek backward, or we can't seek forward, but we cannot do that, we need to recreate all stream from the beginning until the desired offset - // for that we create a new encryptor into a tmp file reading from original file and writing to tmp one + // for that we create a new writer into a tmp file reading from original file and writing to tmp one // when we release the handle we will move this tmp file to the actual file // if we have dirty content (ctx.pos > 0) and position is before file end we copy the rest of the file from position to the end @@ -1165,7 +1191,7 @@ impl EncryptedFs { let guard = self.write_handles.read().await; let mut ctx = guard.get(&handle).unwrap().lock().await; let len = file_size - pos; - crypto::copy_from_file_exact(&mut ctx.encryptor.as_mut().unwrap(), pos, len, &self.cipher, &*self.key.get().await?, self.data_dir.join(CONTENTS_DIR).join(ino_str))?; + crypto::copy_from_file_exact(&mut ctx.writer.as_mut().unwrap(), pos, len, &self.cipher, &*self.key.get().await?, self.data_dir.join(CONTENTS_DIR).join(ino_str))?; ctx.pos += len; } { @@ -1173,7 +1199,7 @@ impl EncryptedFs { let mut ctx = guard.get(&handle).unwrap().lock().await; // finish the current writer so we flush all data to the file debug!("finishing current writer"); - ctx.encryptor.take().unwrap().finish()?; + ctx.writer.take().unwrap().finish()?; } // if we are already in the tmp file first copy tmp to actual file @@ -1201,30 +1227,24 @@ impl EncryptedFs { let tmp_path = self.data_dir.join(CONTENTS_DIR).join(tmp_path_str); let tmp_file = OpenOptions::new().write(true).create(true).truncate(true).open(tmp_path.clone())?; - let encryptor = crypto::create_encryptor(tmp_file, &self.cipher, &*self.key.get().await?); - debug!("recreating encryptor"); - ctx.encryptor.replace(Box::new(encryptor)); + let writer = crypto::create_crypto_writer(tmp_file, &self.cipher, &*self.key.get().await?); + debug!("recreating writer"); + ctx.writer.replace(Box::new(writer)); ctx.pos = 0; ctx.path = tmp_path; let ino_str = ctx.ino.to_string(); - crypto::copy_from_file_exact(&mut ctx.encryptor.as_mut().unwrap(), 0, offset, &self.cipher, &*self.key.get().await?, self.data_dir.join(CONTENTS_DIR).join(ino_str))?; - ctx.pos += offset; + let offset_in_bounds = offset.min(size); + crypto::copy_from_file_exact(&mut ctx.writer.as_mut().unwrap(), 0, offset_in_bounds, &self.cipher, &*self.key.get().await?, self.data_dir.join(CONTENTS_DIR).join(ino_str))?; + ctx.pos += offset_in_bounds; } let guard = self.write_handles.read().await; let mut ctx = guard.get(&handle).unwrap().lock().await; // if offset is after current position (max file size) we fill up with zeros until offset if offset > ctx.pos { - debug!("filling up with zeros until offset"); - let buffer = vec![0; BUF_SIZE]; - loop { - let len = min(buffer.len(), (offset - ctx.pos) as usize); - ctx.encryptor.as_mut().unwrap().write_all(&buffer[..len])?; - ctx.pos += len as u64; - if ctx.pos == offset { - break; - } - } + debug!("filling up with zeros from {} until offset {}", ctx.pos.to_formatted_string(&Locale::en), offset.to_formatted_string(&Locale::en)); + let pos = ctx.pos; + stream_util::fill_zeros(&mut ctx.writer.as_mut().unwrap(), offset - pos)?; } } @@ -1233,8 +1253,8 @@ impl EncryptedFs { // now write the new data debug!("writing new data"); - ctx.encryptor.as_mut().unwrap().write_all(buf)?; - ctx.pos += buf.len() as u64; + let len = ctx.writer.as_mut().unwrap().write(buf)?; + ctx.pos += len as u64; if ctx.pos > ctx.attr.size { // if we write pass file size set the new size @@ -1246,7 +1266,7 @@ impl EncryptedFs { ctx.attr.mtime = SystemTime::now(); ctx.attr.ctime = SystemTime::now(); - Ok(()) + Ok(len) } /// Flush the data to the underlying storage. @@ -1259,7 +1279,7 @@ impl EncryptedFs { return Ok(()); } if let Some(ctx) = self.write_handles.read().await.get(&handle) { - ctx.lock().await.encryptor.as_mut().unwrap().flush()?; + ctx.lock().await.writer.as_mut().unwrap().flush()?; return Ok(()); } @@ -1274,8 +1294,18 @@ impl EncryptedFs { let mut buf = vec![0; size]; let len = self.read(src_ino, src_offset, &mut buf, src_fh).await?; - self.write_all(dest_ino, dest_offset, &buf[..len], dest_fh).await?; - + if len == 0 { + return Ok(0); + } + let mut copied = 0; + while copied < size { + let len = self.write(dest_ino, dest_offset, &buf[copied..len], dest_fh).await?; + if len == 0 && copied < size { + error!(len, "Failed to copy all read bytes"); + return Err(FsError::Other("Failed to copy all read bytes".to_string())); + } + copied += len; + } Ok(len) } @@ -1289,25 +1319,27 @@ impl EncryptedFs { } let map_guard = self.read_write_inode_locks.lock().await; - let mut handle = 0_u64; + let mut handle: Option = None; if read { let lock = map_guard.get_or_insert_with(ino, || Mutex::new(false)); - handle = self.allocate_next_handle(); - self.do_with_read_handle(handle, ReadHandleContextOperation::Create { ino, lock }).await?; + handle = Some(self.allocate_next_handle()); + self.do_with_read_handle(*handle.as_ref().unwrap(), ReadHandleContextOperation::Create { ino, lock }).await?; } if write { let lock = map_guard.get_or_insert_with(ino, || Mutex::new(false)); - handle = self.allocate_next_handle(); - let res = self.do_with_write_handle(handle, WriteHandleContextOperation::Create { ino, lock }).await; + if let None = handle { + handle = Some(self.allocate_next_handle()); + } + let res = self.do_with_write_handle(*handle.as_ref().unwrap(), WriteHandleContextOperation::Create { ino, lock }).await; if res.is_err() && read { // on error remove the read handle if it was added above // remove the read handle if it was added above - self.read_handles.write().await.remove(&handle); + self.read_handles.write().await.remove(&handle.as_ref().unwrap()); return Err(FsError::AlreadyOpenForWrite); } res?; } - Ok(handle) + Ok(handle.unwrap()) } pub async fn truncate(&self, ino: u64, size: u64) -> FsResult<()> { @@ -1317,7 +1349,7 @@ impl EncryptedFs { }; let _guard = lock.lock().await; - let mut attr = self.get_inode(ino).await?; + let attr = self.get_inode(ino).await?; if matches!(attr.kind, FileType::Directory) { return Err(FsError::InvalidInodeType); } @@ -1339,16 +1371,18 @@ impl EncryptedFs { let in_path = self.data_dir.join(CONTENTS_DIR).join(attr.ino.to_string()); let in_file = OpenOptions::new().read(true).write(true).open(in_path.clone())?; let key = self.key.get().await?; - let mut decryptor = crypto::create_decryptor(in_file, &self.cipher, &*key); + let mut reader = crypto::create_crypto_reader(in_file, &self.cipher, &*key); let tmp_path_str = format!("{}.truncate.tmp", attr.ino.to_string()); let tmp_path = self.data_dir.join(CONTENTS_DIR).join(tmp_path_str); let tmp_file = OpenOptions::new().write(true).create(true).truncate(true).open(tmp_path.clone())?; - let mut encryptor = crypto::create_encryptor(tmp_file, &self.cipher, &*key); + let mut writer = crypto::create_crypto_writer(tmp_file, &self.cipher, &*key); // copy existing data until new size debug!("copying data until new size"); - stream_util::copy_exact(&mut decryptor, &mut encryptor, size)?; + stream_util::copy_exact(&mut reader, &mut writer, size)?; + writer.flush()?; + writer.finish()?; debug!("rename from tmp file"); tokio::fs::rename(tmp_path, self.data_dir.join(CONTENTS_DIR).join(attr.ino.to_string())).await?; } else { @@ -1360,23 +1394,22 @@ impl EncryptedFs { let in_path = self.data_dir.join(CONTENTS_DIR).join(attr.ino.to_string()); let in_file = OpenOptions::new().read(true).write(true).open(in_path.clone())?; - let mut decryptor = crypto::create_decryptor(in_file, &self.cipher, &*self.key.get().await?); + let mut reader = crypto::create_crypto_reader(in_file, &self.cipher, &*self.key.get().await?); let tmp_path_str = format!("{}.truncate.tmp", attr.ino.to_string()); let tmp_path = self.data_dir.join(CONTENTS_DIR).join(tmp_path_str); let tmp_file = OpenOptions::new().write(true).create(true).truncate(true).open(tmp_path.clone())?; - let mut encryptor = crypto::create_encryptor(tmp_file, &self.cipher, &*self.key.get().await?); + let mut writer = crypto::create_crypto_writer(tmp_file, &self.cipher, &*self.key.get().await?); // copy existing data debug!("copying existing data"); - stream_util::copy_exact(&mut decryptor, &mut encryptor, attr.size)?; - attr.size = size; + stream_util::copy_exact(&mut reader, &mut writer, attr.size)?; // now fill up with zeros until new size debug!("filling up with zeros until new size"); - stream_util::fill_zeros(&mut encryptor, size - attr.size)?; + stream_util::fill_zeros(&mut writer, size - attr.size)?; - encryptor.finish()?; + writer.finish()?; debug!("rename from tmp file"); tokio::fs::rename(tmp_path, self.data_dir.join(CONTENTS_DIR).join(attr.ino.to_string())).await?; } @@ -1424,7 +1457,7 @@ impl EncryptedFs { let file_size = ctx.attr.size; let pos = ctx.pos; let len = file_size - pos; - crypto::copy_from_file_exact(&mut ctx.encryptor.as_mut().unwrap(), pos, len, &self.cipher, &*self.key.get().await?, self.data_dir.join(CONTENTS_DIR).join(ino_str))?; + crypto::copy_from_file_exact(&mut ctx.writer.as_mut().unwrap(), pos, len, &self.cipher, &*self.key.get().await?, self.data_dir.join(CONTENTS_DIR).join(ino_str))?; ctx.pos += len; } } @@ -1432,7 +1465,7 @@ impl EncryptedFs { let guard = self.write_handles.read().await; let mut ctx = guard.get(&key).unwrap().lock().await; debug!("finishing current writer"); - ctx.encryptor.as_mut().unwrap().finish()?; + ctx.writer.as_mut().unwrap().finish()?; } // if we are in tmp file move it to actual file let (path, ino) = { @@ -1518,29 +1551,29 @@ impl EncryptedFs { Ok(()) } - /// Create an encryptor using internal encryption info. - pub async fn create_encryptor(&self, file: File) -> FsResult> { - Ok(crypto::create_encryptor(file, &self.cipher, &*self.key.get().await?)) + /// Create a crypto writer using internal encryption info. + pub async fn create_crypto_writer(&self, file: File) -> FsResult> { + Ok(crypto::create_crypto_writer(file, &self.cipher, &*self.key.get().await?)) } - /// Create a decryptor using internal encryption info. - pub async fn create_decryptor(&self, file: File) -> FsResult> { - Ok(crypto::create_decryptor(file, &self.cipher, &*self.key.get().await?)) + /// Create a crypto reader using internal encryption info. + pub async fn create_crypto_reader(&self, file: File) -> FsResult> { + Ok(crypto::create_crypto_reader(file, &self.cipher, &*self.key.get().await?)) } /// Encrypts a string using internal encryption info. pub async fn encrypt_string(&self, s: &SecretString) -> FsResult { - Ok(crypto::encrypt_string(s, &self.cipher, &*self.key.get().await?)) + Ok(crypto::encrypt_string(s, &self.cipher, &*self.key.get().await?)?) } /// Decrypts a string using internal encryption info. pub async fn decrypt_string(&self, s: &str) -> FsResult { - Ok(crypto::decrypt_string(s, &self.cipher, &*self.key.get().await?)) + Ok(crypto::decrypt_string(s, &self.cipher, &*self.key.get().await?)?) } /// Normalize and encrypt a file name. pub async fn normalize_end_encrypt_file_name(&self, name: &SecretString) -> FsResult { - Ok(crypto::normalize_end_encrypt_file_name(name, &self.cipher, &*self.key.get().await?)) + Ok(crypto::encrypt_file_name(name, &self.cipher, &*self.key.get().await?)?) } /// Change the password of the filesystem used to access the encryption key. @@ -1553,8 +1586,8 @@ impl EncryptedFs { let salt = crypto::hash_secret(&old_password); let initial_key = crypto::derive_key(&old_password, &cipher, salt)?; let enc_file = data_dir.join(SECURITY_DIR).join(KEY_ENC_FILENAME); - let decryptor = crypto::create_decryptor(File::open(enc_file.clone())?, &cipher, &initial_key); - let key_store: KeyStore = bincode::deserialize_from(decryptor).map_err(|_| FsError::InvalidPassword)?; + let reader = crypto::create_crypto_reader(File::open(enc_file.clone())?, &cipher, &initial_key); + let key_store: KeyStore = bincode::deserialize_from(reader).map_err(|_| FsError::InvalidPassword)?; // check hash if key_store.hash != crypto::hash(key_store.key.expose_secret()) { return Err(FsError::InvalidPassword); @@ -1564,9 +1597,11 @@ impl EncryptedFs { let salt = crypto::hash_secret(&new_password); let new_key = crypto::derive_key(&new_password, &cipher, salt)?; tokio::fs::remove_file(enc_file.clone()).await?; - let mut encryptor = crypto::create_encryptor(OpenOptions::new().read(true).write(true).create(true).truncate(true).open(enc_file.clone())?, - &cipher, &new_key); - bincode::serialize_into(&mut encryptor, &key_store)?; + let mut writer = crypto::create_crypto_writer(OpenOptions::new().read(true).write(true).create(true).truncate(true).open(enc_file.clone())?, + &cipher, &new_key); + bincode::serialize_into(&mut writer, &key_store)?; + writer.flush()?; + writer.finish()?; Ok(()) } @@ -1619,7 +1654,7 @@ impl EncryptedFs { let ino = op.get_ino(); let path = self.data_dir.join(CONTENTS_DIR).join(ino.to_string()); let file = OpenOptions::new().read(true).write(true).open(path)?; - let decryptor = crypto::create_decryptor(file, &self.cipher, &*self.key.get().await?); + let reader = crypto::create_crypto_reader(file, &self.cipher, &*self.key.get().await?); let attr = self.get_inode_from_storage(ino, &*self.key.get().await?).await?; match op { ReadHandleContextOperation::Create { ino, lock } => { @@ -1628,7 +1663,7 @@ impl EncryptedFs { ino, attr, pos: 0, - decryptor: Some(Box::new(decryptor)), + reader: Some(Box::new(reader)), _lock: lock, }; self.read_handles.write().await.insert(handle, Mutex::new(ctx)); @@ -1636,7 +1671,7 @@ impl EncryptedFs { } ReadHandleContextOperation::RecreateDecryptor { mut existing } => { existing.pos = 0; - existing.decryptor.replace(Box::new(decryptor)); + existing.reader.replace(Box::new(reader)); existing.attr.size = attr.size; } } @@ -1650,7 +1685,7 @@ impl EncryptedFs { .read(true) .write(true) .open(path.clone())?; - let encryptor = crypto::create_encryptor(file, &self.cipher, &*self.key.get().await?); + let writer = crypto::create_crypto_writer(file, &self.cipher, &*self.key.get().await?); match op { WriteHandleContextOperation::Create { ino, lock } => { debug!("creating write handle"); @@ -1660,7 +1695,7 @@ impl EncryptedFs { attr, path, pos: 0, - encryptor: Some(Box::new(encryptor)), + writer: Some(Box::new(writer)), _lock: lock, }; self.write_handles.write().await.insert(handle, Mutex::new(ctx)); @@ -1668,8 +1703,8 @@ impl EncryptedFs { } WriteHandleContextOperation::RecreateEncryptor { mut existing, reset_size } => { existing.pos = 0; - debug!("recreating encryptor"); - existing.encryptor.replace(Box::new(encryptor)); + debug!("recreating writer"); + existing.writer.replace(Box::new(writer)); existing.path = path.clone(); if reset_size { let attr = self.get_inode_from_storage(ino, &*self.key.get().await?).await?; @@ -1716,7 +1751,7 @@ impl EncryptedFs { async fn insert_directory_entry(&self, parent: u64, entry: DirectoryEntry, key: &SecretVec) -> FsResult<()> { let parent_path = self.data_dir.join(CONTENTS_DIR).join(parent.to_string()); - let name = crypto::normalize_end_encrypt_file_name(&entry.name, &self.cipher, &*self.key.get().await?); + let name = crypto::encrypt_file_name(&entry.name, &self.cipher, &*self.key.get().await?)?; let file_path = parent_path.join(name); let map = self.serialize_dir_entries_locks.write().unwrap(); @@ -1731,14 +1766,17 @@ impl EncryptedFs { // write inode and file type let entry = (entry.ino, entry.kind); - bincode::serialize_into(crypto::create_encryptor(file, &self.cipher, key), &entry)?; + let mut writer = crypto::create_crypto_writer(file, &self.cipher, key); + bincode::serialize_into(&mut writer, &entry)?; + writer.flush()?; + writer.finish()?; Ok(()) } async fn remove_directory_entry(&self, parent: u64, name: &SecretString) -> FsResult<()> { let parent_path = self.data_dir.join(CONTENTS_DIR).join(parent.to_string()); - let name = crypto::normalize_end_encrypt_file_name(name, &self.cipher, &*self.key.get().await?); + let name = crypto::encrypt_file_name(name, &self.cipher, &*self.key.get().await?)?; let file_path = parent_path.join(name); let map = self.serialize_dir_entries_locks.write().unwrap(); @@ -1771,8 +1809,8 @@ impl EncryptedFs { if path.exists() { // read key - let decryptor = crypto::create_decryptor(File::open(path)?, cipher, &derived_key); - let key_store: KeyStore = bincode::deserialize_from(decryptor).map_err(|_| FsError::InvalidPassword)?; + let reader = crypto::create_crypto_reader(File::open(path)?, cipher, &derived_key); + let key_store: KeyStore = bincode::deserialize_from(reader).map_err(|_| FsError::InvalidPassword)?; // check hash if key_store.hash != crypto::hash(key_store.key.expose_secret()) { return Err(FsError::InvalidPassword); @@ -1783,16 +1821,18 @@ impl EncryptedFs { let mut key: Vec = vec![]; let key_len = match cipher { - Cipher::ChaCha20 => 32, - Cipher::Aes256Gcm => 32, + Cipher::ChaCha20 => CHACHA20_POLY1305.key_len(), + Cipher::Aes256Gcm => AES_256_GCM.key_len(), }; key.resize(key_len, 0); thread_rng().fill_bytes(&mut key); let key = SecretVec::new(key); let key_store = KeyStore::new(key); - let mut encryptor = crypto::create_encryptor(OpenOptions::new().read(true).write(true).create(true).open(path)?, - cipher, &derived_key); - bincode::serialize_into(&mut encryptor, &key_store)?; + let mut writer = crypto::create_crypto_writer(OpenOptions::new().read(true).write(true).create(true).open(path)?, + cipher, &derived_key); + bincode::serialize_into(&mut writer, &key_store)?; + writer.flush()?; + writer.finish()?; Ok(key_store.key) } } diff --git a/src/encryptedfs/moved_test.rs b/src/encryptedfs/moved_test.rs index 29605a9f..b3de59a1 100644 --- a/src/encryptedfs/moved_test.rs +++ b/src/encryptedfs/moved_test.rs @@ -11,6 +11,7 @@ use secrecy::{ExposeSecret, SecretString}; use tokio::sync::Mutex; use crate::encryptedfs::{Cipher, CONTENTS_DIR, CreateFileAttr, DirectoryEntry, DirectoryEntryPlus, EncryptedFs, FileType, FsError, FsResult, PasswordProvider, ROOT_INODE}; +use crate::stream_util::write_all_bytes_to_fs; const TESTS_DATA_DIR: &str = "/tmp/rencfs-test-data/"; @@ -24,12 +25,12 @@ struct SetupResult { setup: TestSetup, } -async fn setup(setup: TestSetup) -> FsResult { +async fn setup(setup: TestSetup) -> SetupResult { let path = setup.data_path.as_str(); if fs::metadata(path).is_ok() { - fs::remove_dir_all(path)?; + fs::remove_dir_all(path).unwrap(); } - fs::create_dir_all(path)?; + fs::create_dir_all(path).unwrap(); struct PasswordProviderImpl {} impl PasswordProvider for PasswordProviderImpl { @@ -38,12 +39,12 @@ async fn setup(setup: TestSetup) -> FsResult { } } - let fs = EncryptedFs::new(path, Box::new(PasswordProviderImpl {}), Cipher::ChaCha20).await?; + let fs = EncryptedFs::new(path, Box::new(PasswordProviderImpl {}), Cipher::ChaCha20).await.unwrap(); - Ok(SetupResult { + SetupResult { fs: Some(fs), setup, - }) + } } async fn teardown() -> Result<(), io::Error> { @@ -54,14 +55,14 @@ async fn teardown() -> Result<(), io::Error> { Ok(()) } -async fn run_test(init: TestSetup, t: T) -> FsResult<()> +async fn run_test(init: TestSetup, t: T) where T: std::future::Future // + std::panic::UnwindSafe { { let s = SETUP_RESULT.with(|s| Arc::clone(s)); let mut s = s.lock().await; - *s.deref_mut() = Some(setup(init).await?); + *s.deref_mut() = Some(setup(init).await); } // let res = std::panic::catch_unwind(|| { @@ -71,9 +72,7 @@ async fn run_test(init: TestSetup, t: T) -> FsResult<()> // }); // }); - teardown().await?; - - Ok(()) + teardown().await.unwrap(); // assert!(res.is_ok()); } @@ -93,83 +92,103 @@ fn create_attr_from_type(kind: FileType) -> CreateFileAttr { async fn read_to_string(path: PathBuf, fs: &EncryptedFs) -> String { let mut buf: Vec = vec![]; - fs.create_decryptor(OpenOptions::new().read(true).write(true).open(path).unwrap()).await.unwrap().read_to_end(&mut buf).unwrap(); + fs.create_crypto_reader(OpenOptions::new().read(true).write(true).open(path).unwrap()).await.unwrap().read_to_end(&mut buf).unwrap(); String::from_utf8(buf).unwrap() } +async fn copy_all_file_range(fs: &EncryptedFs, src_ino: u64, src_offset: u64, dest_ino: u64, dest_offset: u64, size: usize, src_fh: u64, dest_fh: u64) { + let mut copied = 0; + while copied < size { + let len = fs.copy_file_range(src_ino, src_offset + copied as u64, dest_ino, dest_offset + copied as u64, size - copied, src_fh, dest_fh).await.unwrap(); + if len == 0 && copied < size { + panic!("Failed to copy all bytes"); + } + copied += len; + } +} + +async fn read_exact(fs: &EncryptedFs, ino: u64, offset: u64, mut buf: &mut [u8], handle: u64) { + let mut read = 0; + while read < buf.len() { + let len = fs.read(ino, offset + read as u64, &mut buf[read..], handle).await.unwrap(); + if len == 0 && read < buf.len() { + panic!("Failed to read all bytes"); + } + read += len; + } +} + #[tokio::test] -async fn test_write_all() -> FsResult<()> { +async fn test_write_all() { run_test(TestSetup { data_path: format!("{TESTS_DATA_DIR}test_write_all") }, async { let fs = SETUP_RESULT.with(|s| Arc::clone(s)); let mut fs = fs.lock().await; let fs = fs.as_mut().unwrap().fs.as_ref().unwrap(); let test_file = SecretString::from_str("test-file").unwrap(); - let (fh, attr) = fs.create_nod(ROOT_INODE, &test_file, create_attr_from_type(FileType::RegularFile), false, true).await?; + let (fh, attr) = fs.create_nod(ROOT_INODE, &test_file, create_attr_from_type(FileType::RegularFile), false, true).await.unwrap(); let data = "test-42"; - fs.write_all(attr.ino, 0, data.as_bytes(), fh).await?; - fs.flush(fh).await?; - fs.release(fh).await?; + write_all_bytes_to_fs(&fs, attr.ino, 0, data.as_bytes(), fh).await.unwrap(); + fs.flush(fh).await.unwrap(); + fs.release(fh).await.unwrap(); assert_eq!(data, read_to_string(fs.data_dir.join(CONTENTS_DIR).join(attr.ino.to_string()), &fs).await); - let attr = fs.get_inode(attr.ino).await?; + let attr = fs.get_inode(attr.ino).await.unwrap(); assert_eq!(data.len() as u64, attr.size); // offset greater than current position let data = "37"; - let fh = fs.open(attr.ino, false, true).await?; - fs.write_all(attr.ino, 5, data.as_bytes(), fh).await?; - fs.flush(fh).await?; - fs.release(fh).await?; + let fh = fs.open(attr.ino, false, true).await.unwrap(); + write_all_bytes_to_fs(&fs, attr.ino, 5, data.as_bytes(), fh).await.unwrap(); + fs.flush(fh).await.unwrap(); + fs.release(fh).await.unwrap(); assert_eq!(data, &read_to_string(fs.data_dir.join(CONTENTS_DIR).join(attr.ino.to_string()), &fs).await[5..]); // offset after file end let data = "37"; - let fh = fs.open(attr.ino, false, true).await?; - fs.write_all(attr.ino, 42, data.as_bytes(), fh).await?; - fs.flush(fh).await?; - fs.release(fh).await?; + let fh = fs.open(attr.ino, false, true).await.unwrap(); + write_all_bytes_to_fs(&fs, attr.ino, 42, data.as_bytes(), fh).await.unwrap(); + fs.flush(fh).await.unwrap(); + fs.release(fh).await.unwrap(); assert_eq!(format!("test-37{}37", "\0".repeat(35)), read_to_string(fs.data_dir.join(CONTENTS_DIR).join(attr.ino.to_string()), &fs).await); // offset before current position, several blocks let test_file_2 = SecretString::from_str("test-file-2").unwrap(); - let (fh, attr) = fs.create_nod(ROOT_INODE, &test_file_2, create_attr_from_type(FileType::RegularFile), false, true).await?; + let (fh, attr) = fs.create_nod(ROOT_INODE, &test_file_2, create_attr_from_type(FileType::RegularFile), false, true).await.unwrap(); let data = "test-42-37-42"; - fs.write_all(attr.ino, 0, data.as_bytes(), fh).await?; + write_all_bytes_to_fs(&fs, attr.ino, 0, data.as_bytes(), fh).await.unwrap(); let data1 = "01"; - fs.write_all(attr.ino, 5, data1.as_bytes(), fh).await?; + write_all_bytes_to_fs(&fs, attr.ino, 5, data1.as_bytes(), fh).await.unwrap(); let data2 = "02"; - fs.write_all(attr.ino, 8, data2.as_bytes(), fh).await?; - fs.flush(fh).await?; - fs.release(fh).await?; + write_all_bytes_to_fs(&fs, attr.ino, 8, data2.as_bytes(), fh).await.unwrap(); + fs.flush(fh).await.unwrap(); + fs.release(fh).await.unwrap(); assert_eq!("test-01-02-42", &read_to_string(fs.data_dir.join(CONTENTS_DIR).join(attr.ino.to_string()), &fs).await); // write before current position then write to the end, also check it preserves the content from // the first write to offset to end of the file let test_file_3 = SecretString::from_str("test-file-3").unwrap(); - let (fh, attr) = fs.create_nod(ROOT_INODE, &test_file_3, create_attr_from_type(FileType::RegularFile), false, true).await?; + let (fh, attr) = fs.create_nod(ROOT_INODE, &test_file_3, create_attr_from_type(FileType::RegularFile), false, true).await.unwrap(); let data = "test-42-37"; - fs.write_all(attr.ino, 0, data.as_bytes(), fh).await?; - fs.write_all(attr.ino, 5, b"37", fh).await?; - fs.write_all(attr.ino, data.len() as u64, b"-42", fh).await?; - fs.flush(fh).await?; - fs.release(fh).await?; + write_all_bytes_to_fs(&fs, attr.ino, 0, data.as_bytes(), fh).await.unwrap(); + write_all_bytes_to_fs(&fs, attr.ino, 5, b"37", fh).await.unwrap(); + write_all_bytes_to_fs(&fs, attr.ino, data.len() as u64, b"-42", fh).await.unwrap(); + fs.flush(fh).await.unwrap(); + fs.release(fh).await.unwrap(); let new_content = read_to_string(fs.data_dir.join(CONTENTS_DIR).join(attr.ino.to_string()), &fs).await; assert_eq!("test-37-37-42", new_content); let buf = [0; 0]; - let fh = fs.open(attr.ino, false, true).await?; - assert!(matches!(fs.write_all(ROOT_INODE, 0, &buf, fh).await, Err(FsError::InvalidInodeType))); - assert!(matches!(fs.write_all(0, 0, &buf, fh).await, Err(FsError::InodeNotFound))); + let fh = fs.open(attr.ino, false, true).await.unwrap(); + assert!(matches!(fs.write(ROOT_INODE, 0, &buf, fh).await, Err(FsError::InvalidInodeType))); + assert!(matches!(fs.write(0, 0, &buf, fh).await, Err(FsError::InodeNotFound))); let test_dir = SecretString::from_str("test-dir").unwrap(); - let (fh, dir_attr) = fs.create_nod(ROOT_INODE, &test_dir, create_attr_from_type(FileType::Directory), false, true).await?; - assert!(matches!(fs.write_all(dir_attr.ino, 0, &buf, fh).await, Err(FsError::InvalidInodeType))); - - Ok::<(), FsError>(()) + let (fh, dir_attr) = fs.create_nod(ROOT_INODE, &test_dir, create_attr_from_type(FileType::Directory), false, true).await.unwrap(); + assert!(matches!(fs.write(dir_attr.ino, 0, &buf, fh).await, Err(FsError::InvalidInodeType))); }).await } #[tokio::test] -async fn test_read() -> FsResult<()> { +async fn test_read() { run_test(TestSetup { data_path: format!("{TESTS_DATA_DIR}test_read") }, async { let fs = SETUP_RESULT.with(|s| Arc::clone(s)); let mut fs = fs.lock().await; @@ -177,89 +196,88 @@ async fn test_read() -> FsResult<()> { let test_test_file = SecretString::from_str("test-file").unwrap(); let test_file = test_test_file; - let (fh, attr) = fs.create_nod(ROOT_INODE, &test_file, create_attr_from_type(FileType::RegularFile), false, true).await?; + let (fh, attr) = fs.create_nod(ROOT_INODE, &test_file, create_attr_from_type(FileType::RegularFile), false, true).await.unwrap(); let data = b"test-42"; let mut buf = [0; 7]; - fs.write_all(attr.ino, 0, data, fh).await?; - fs.flush(fh).await?; - fs.release(fh).await?; - let fh = fs.open(attr.ino, true, false).await?; - let len = fs.read(attr.ino, 0, &mut buf, fh).await?; - assert_eq!(len, 7); + write_all_bytes_to_fs(&fs, attr.ino, 0, data, fh).await.unwrap(); + fs.flush(fh).await.unwrap(); + fs.release(fh).await.unwrap(); + let fh = fs.open(attr.ino, true, false).await.unwrap(); + read_exact(fs, attr.ino, 0, &mut buf, fh).await; assert_eq!(data, &buf); // larger buffer - let len = fs.read(attr.ino, 0, &mut [0; 42], fh).await?; + let len = fs.read(attr.ino, 0, &mut [0; 42], fh).await.unwrap(); assert_eq!(len, 7); // offset let data = b"test-37"; let mut buf = [0; 2]; - let fh = fs.open(attr.ino, false, true).await?; - fs.write_all(attr.ino, 0, data, fh).await?; - fs.flush(fh).await?; - fs.release(fh).await?; - let fh = fs.open(attr.ino, true, false).await?; - fs.read(attr.ino, 5, &mut buf, fh).await?; + let fh = fs.open(attr.ino, false, true).await.unwrap(); + write_all_bytes_to_fs(&fs, attr.ino, 0, data, fh).await.unwrap(); + fs.flush(fh).await.unwrap(); + fs.release(fh).await.unwrap(); + let fh = fs.open(attr.ino, true, false).await.unwrap(); + read_exact(fs, attr.ino, 5, &mut buf, fh).await; assert_eq!(b"37", &buf); // offset after file end - let fh = fs.open(attr.ino, true, false).await?; - let len = fs.read(attr.ino, 42, &mut [0, 1], fh).await?; + let fh = fs.open(attr.ino, true, false).await.unwrap(); + let len = fs.read(attr.ino, 42, &mut [0, 1], fh).await.unwrap(); assert_eq!(len, 0); // if it picks up new value after a write after current read position let test_file_2 = SecretString::from_str("test-file-2").unwrap(); - let (fh, attr) = fs.create_nod(ROOT_INODE, &test_file_2, create_attr_from_type(FileType::RegularFile), false, true).await?; + let (fh, attr) = fs.create_nod(ROOT_INODE, &test_file_2, create_attr_from_type(FileType::RegularFile), false, true).await.unwrap(); let data = "test-42"; - fs.write_all(attr.ino, 0, data.as_bytes(), fh).await?; - fs.flush(fh).await?; - fs.release(fh).await?; - let fh = fs.open(attr.ino, true, false).await?; - fs.read(attr.ino, 0, &mut [0_u8; 1], fh).await?; - let fh_2 = fs.open(attr.ino, false, true).await?; + write_all_bytes_to_fs(&fs, attr.ino, 0, data.as_bytes(), fh).await.unwrap(); + fs.flush(fh).await.unwrap(); + fs.release(fh).await.unwrap(); + let fh = fs.open(attr.ino, true, false).await.unwrap(); + read_exact(fs, attr.ino, 0, &mut [0_u8; 1], fh).await; + let fh_2 = fs.open(attr.ino, false, true).await.unwrap(); let new_data = "37"; - fs.write_all(attr.ino, 5, new_data.as_bytes(), fh_2).await?; - fs.flush(fh_2).await?; - fs.release(fh_2).await?; + write_all_bytes_to_fs(&fs, attr.ino, 5, new_data.as_bytes(), fh_2).await.unwrap(); + fs.flush(fh_2).await.unwrap(); + fs.release(fh_2).await.unwrap(); let mut buf = [0_u8; 2]; - fs.read(attr.ino, 5, &mut buf, fh).await?; + read_exact(fs, attr.ino, 5, &mut buf, fh).await; assert_eq!(new_data, String::from_utf8(buf.to_vec()).unwrap()); // if it picks up new value after a write before current read position let test_file_3 = SecretString::from_str("test-file-3").unwrap(); - let (fh, attr) = fs.create_nod(ROOT_INODE, &test_file_3, create_attr_from_type(FileType::RegularFile), false, true).await?; + let (fh, attr) = fs.create_nod(ROOT_INODE, &test_file_3, create_attr_from_type(FileType::RegularFile), false, true).await.unwrap(); let data = "test-42-37"; - fs.write_all(attr.ino, 0, data.as_bytes(), fh).await?; - fs.flush(fh).await?; - fs.release(fh).await?; - let fh = fs.open(attr.ino, true, false).await?; - fs.read(attr.ino, 8, &mut [0_u8; 1], fh).await?; - let fh_2 = fs.open(attr.ino, false, true).await?; + write_all_bytes_to_fs(&fs, attr.ino, 0, data.as_bytes(), fh).await.unwrap(); + fs.flush(fh).await.unwrap(); + fs.release(fh).await.unwrap(); + let fh = fs.open(attr.ino, true, false).await.unwrap(); + read_exact(fs, attr.ino, 8, &mut [0_u8; 1], fh).await; + let fh_2 = fs.open(attr.ino, false, true).await.unwrap(); let new_data = "37"; - fs.write_all(attr.ino, 5, new_data.as_bytes(), fh_2).await?; - fs.flush(fh_2).await?; - fs.release(fh_2).await?; + write_all_bytes_to_fs(&fs, attr.ino, 5, new_data.as_bytes(), fh_2).await.unwrap(); + fs.flush(fh_2).await.unwrap(); + fs.release(fh_2).await.unwrap(); let mut buf = [0_u8; 2]; - fs.read(attr.ino, 5, &mut buf, fh).await?; + read_exact(fs, attr.ino, 5, &mut buf, fh).await; assert_eq!(new_data, String::from_utf8(buf.to_vec()).unwrap()); // if it continues to read correctly after a write before current read position let test_file_4 = SecretString::from_str("test-file-4").unwrap(); - let (fh, attr) = fs.create_nod(ROOT_INODE, &test_file_4, create_attr_from_type(FileType::RegularFile), false, true).await?; + let (fh, attr) = fs.create_nod(ROOT_INODE, &test_file_4, create_attr_from_type(FileType::RegularFile), false, true).await.unwrap(); let data = "test-42-37"; - fs.write_all(attr.ino, 0, data.as_bytes(), fh).await?; - fs.flush(fh).await?; - fs.release(fh).await?; - let fh = fs.open(attr.ino, true, false).await?; - fs.read(attr.ino, 7, &mut [0_u8; 1], fh).await?; - let fh_2 = fs.open(attr.ino, false, true).await?; + write_all_bytes_to_fs(&fs, attr.ino, 0, data.as_bytes(), fh).await.unwrap(); + fs.flush(fh).await.unwrap(); + fs.release(fh).await.unwrap(); + let fh = fs.open(attr.ino, true, false).await.unwrap(); + read_exact(fs, attr.ino, 7, &mut [0_u8; 1], fh).await; + let fh_2 = fs.open(attr.ino, false, true).await.unwrap(); let new_data = "37"; - fs.write_all(attr.ino, 5, new_data.as_bytes(), fh_2).await?; - fs.flush(fh_2).await?; - fs.release(fh_2).await?; + write_all_bytes_to_fs(&fs, attr.ino, 5, new_data.as_bytes(), fh_2).await.unwrap(); + fs.flush(fh_2).await.unwrap(); + fs.release(fh_2).await.unwrap(); let mut buf = [0_u8; 2]; - fs.read(attr.ino, 8, &mut buf, fh).await?; + read_exact(fs, attr.ino, 8, &mut buf, fh).await; assert_eq!(new_data, String::from_utf8(buf.to_vec()).unwrap()); // invalid values @@ -267,118 +285,110 @@ async fn test_read() -> FsResult<()> { assert!(matches!(fs.read(ROOT_INODE, 0, &mut buf, fh).await, Err(FsError::InvalidInodeType))); assert!(matches!(fs.read(0, 0,&mut buf, fh).await, Err(FsError::InodeNotFound))); let test_dir = SecretString::from_str("test-dir").unwrap(); - let (fh, dir_attr) = fs.create_nod(ROOT_INODE, &test_dir, create_attr_from_type(FileType::Directory), true, false).await?; + let (fh, dir_attr) = fs.create_nod(ROOT_INODE, &test_dir, create_attr_from_type(FileType::Directory), true, false).await.unwrap(); assert!(matches!(fs.read(dir_attr.ino, 0, &mut buf, fh).await, Err(FsError::InvalidInodeType))); - - Ok::<(), FsError>(()) }).await } #[tokio::test] -async fn test_truncate() -> FsResult<()> { +async fn test_truncate() { run_test(TestSetup { data_path: format!("{TESTS_DATA_DIR}test_truncate") }, async { let fs = SETUP_RESULT.with(|s| Arc::clone(s)); let mut fs = fs.lock().await; let fs = fs.as_mut().unwrap().fs.as_ref().unwrap(); let test_file = SecretString::from_str("test-file").unwrap(); - let (fh, attr) = fs.create_nod(ROOT_INODE, &test_file, create_attr_from_type(FileType::RegularFile), false, true).await?; + let (fh, attr) = fs.create_nod(ROOT_INODE, &test_file, create_attr_from_type(FileType::RegularFile), false, true).await.unwrap(); let data = "test-42"; - fs.write_all(attr.ino, 0, data.as_bytes(), fh).await?; - fs.flush(fh).await?; - fs.release(fh).await?; + write_all_bytes_to_fs(&fs, attr.ino, 0, data.as_bytes(), fh).await.unwrap(); + fs.flush(fh).await.unwrap(); + fs.release(fh).await.unwrap(); // size increase, preserve opened writer content - let fh = fs.open(attr.ino, false, true).await?; + let fh = fs.open(attr.ino, false, true).await.unwrap(); let data = "37"; - fs.write_all(attr.ino, 5, data.as_bytes(), fh).await?; - fs.truncate(attr.ino, 10).await?; - assert_eq!(10, fs.get_inode(attr.ino).await?.size); + write_all_bytes_to_fs(&fs, attr.ino, 5, data.as_bytes(), fh).await.unwrap(); + fs.truncate(attr.ino, 10).await.unwrap(); + assert_eq!(10, fs.get_inode(attr.ino).await.unwrap().size); assert_eq!(format!("test-37{}", "\0".repeat(3)), read_to_string(fs.data_dir.join(CONTENTS_DIR).join(attr.ino.to_string()), &fs).await); - fs.release(fh).await?; + fs.release(fh).await.unwrap(); // size doesn't change - fs.truncate(attr.ino, 10).await?; - assert_eq!(10, fs.get_inode(attr.ino).await?.size); + fs.truncate(attr.ino, 10).await.unwrap(); + assert_eq!(10, fs.get_inode(attr.ino).await.unwrap().size); assert_eq!(format!("test-37{}", "\0".repeat(3)), read_to_string(fs.data_dir.join(CONTENTS_DIR).join(attr.ino.to_string()), &fs).await); // size decrease, preserve opened writer content - let fh = fs.open(attr.ino, false, true).await?; + let fh = fs.open(attr.ino, false, true).await.unwrap(); let data = "37"; - fs.write_all(attr.ino, 0, data.as_bytes(), fh).await?; - fs.truncate(attr.ino, 4).await?; - assert_eq!(4, fs.get_inode(attr.ino).await?.size); + write_all_bytes_to_fs(&fs, attr.ino, 0, data.as_bytes(), fh).await.unwrap(); + fs.truncate(attr.ino, 4).await.unwrap(); + assert_eq!(4, fs.get_inode(attr.ino).await.unwrap().size); assert_eq!("37st", read_to_string(fs.data_dir.join(CONTENTS_DIR).join(attr.ino.to_string()), &fs).await); - fs.release(fh).await?; + fs.release(fh).await.unwrap(); // size decrease to 0 - fs.truncate(attr.ino, 0).await?; - assert_eq!(0, fs.get_inode(attr.ino).await?.size); + fs.truncate(attr.ino, 0).await.unwrap(); + assert_eq!(0, fs.get_inode(attr.ino).await.unwrap().size); assert_eq!("".to_string(), read_to_string(fs.data_dir.join(CONTENTS_DIR).join(attr.ino.to_string()), &fs).await); - - Ok::<(), FsError>(()) }).await } #[tokio::test] #[traced_test] -async fn test_copy_file_range() -> FsResult<()> { +async fn test_copy_file_range() { run_test(TestSetup { data_path: format!("{TESTS_DATA_DIR}test_copy_file_range") }, async { let fs = SETUP_RESULT.with(|s| Arc::clone(s)); let mut fs = fs.lock().await; let fs = fs.as_mut().unwrap().fs.as_ref().unwrap(); let test_file_1 = SecretString::from_str("test-file-1").unwrap(); - let (fh, attr_1) = fs.create_nod(ROOT_INODE, &test_file_1, create_attr_from_type(FileType::RegularFile), true, true).await?; + let (fh, attr_1) = fs.create_nod(ROOT_INODE, &test_file_1, create_attr_from_type(FileType::RegularFile), true, true).await.unwrap(); let data = "test-42"; - fs.write_all(attr_1.ino, 0, data.as_bytes(), fh).await?; - fs.flush(fh).await?; - fs.release(fh).await?; - let fh = fs.open(attr_1.ino, true, false).await?; + write_all_bytes_to_fs(fs, attr_1.ino, 0, data.as_bytes(), fh).await.unwrap(); + fs.flush(fh).await.unwrap(); + fs.release(fh).await.unwrap(); + let fh = fs.open(attr_1.ino, true, false).await.unwrap(); let test_file_2 = SecretString::from_str("test-file-2").unwrap(); - let (fh2, attr_2) = fs.create_nod(ROOT_INODE, &test_file_2, create_attr_from_type(FileType::RegularFile), true, true).await?; + let (fh2, attr_2) = fs.create_nod(ROOT_INODE, &test_file_2, create_attr_from_type(FileType::RegularFile), true, true).await.unwrap(); // whole file - let len = fs.copy_file_range(attr_1.ino, 0, attr_2.ino, 0, 7, fh, fh2).await?; - fs.flush(fh2).await?; - fs.release(fh2).await?; - assert_eq!(len, 7); + copy_all_file_range(fs, attr_1.ino, 0, attr_2.ino, 0, 7, fh, fh2).await; + fs.flush(fh2).await.unwrap(); + fs.release(fh2).await.unwrap(); let mut buf = [0; 7]; - let fh = fs.open(attr_2.ino, true, false).await?; - fs.read(attr_2.ino, 0, &mut buf, fh).await?; + let fh = fs.open(attr_2.ino, true, false).await.unwrap(); + read_exact(fs, attr_2.ino, 0, &mut buf, fh).await; assert_eq!(data, String::from_utf8(buf.to_vec()).unwrap()); // offset let data_37 = "37"; - let fh = fs.open(attr_1.ino, false, true).await?; - fs.write_all(attr_1.ino, 7, data_37.as_bytes(), fh).await?; - fs.flush(fh).await?; - fs.release(fh).await?; - let fh = fs.open(attr_1.ino, true, false).await?; - let fh_2 = fs.open(attr_2.ino, false, true).await?; - let len = fs.copy_file_range(attr_1.ino, 7, attr_2.ino, 5, 2, fh, fh_2).await?; - fs.flush(fh_2).await?; - fs.release(fh_2).await?; - assert_eq!(len, 2); - let fh = fs.open(attr_2.ino, true, false).await?; - fs.read(attr_2.ino, 0, &mut buf, fh).await?; + let fh = fs.open(attr_1.ino, false, true).await.unwrap(); + write_all_bytes_to_fs(&fs, attr_1.ino, 7, data_37.as_bytes(), fh).await.unwrap(); + fs.flush(fh).await.unwrap(); + fs.release(fh).await.unwrap(); + let fh = fs.open(attr_1.ino, true, false).await.unwrap(); + let fh_2 = fs.open(attr_2.ino, false, true).await.unwrap(); + copy_all_file_range(fs, attr_1.ino, 7, attr_2.ino, 5, 2, fh, fh_2).await; + fs.flush(fh_2).await.unwrap(); + fs.release(fh_2).await.unwrap(); + let fh = fs.open(attr_2.ino, true, false).await.unwrap(); + read_exact(fs, attr_2.ino, 0, &mut buf, fh).await; assert_eq!("test-37", String::from_utf8(buf.to_vec()).unwrap()); // out of bounds - let fh = fs.open(attr_1.ino, true, false).await?; - let fh_2 = fs.open(attr_2.ino, false, true).await?; - let len = fs.copy_file_range(attr_1.ino, 42, attr_2.ino, 0, 2, fh, fh_2).await?; + let fh = fs.open(attr_1.ino, true, false).await.unwrap(); + let fh_2 = fs.open(attr_2.ino, false, true).await.unwrap(); + let len = fs.copy_file_range(attr_1.ino, 42, attr_2.ino, 0, 2, fh, fh_2).await.unwrap(); assert_eq!(len, 0); // invalid inodes assert!(matches!(fs.copy_file_range(0, 0, 0, 0, 0, fh, fh_2).await, Err(FsError::InodeNotFound))); - - Ok::<(), FsError>(()) }).await } #[tokio::test] -async fn test_read_dir() -> FsResult<()> { +async fn test_read_dir() { run_test(TestSetup { data_path: format!("{TESTS_DATA_DIR}test_read_dir") }, async { let fs = SETUP_RESULT.with(|s| Arc::clone(s)); let mut fs = fs.lock().await; @@ -386,11 +396,11 @@ async fn test_read_dir() -> FsResult<()> { // file and directory in root let test_file = SecretString::from_str("test-file").unwrap(); - let (_fh, file_attr) = fs.create_nod(ROOT_INODE, &test_file, create_attr_from_type(FileType::RegularFile), false, false).await?; + let (_fh, file_attr) = fs.create_nod(ROOT_INODE, &test_file, create_attr_from_type(FileType::RegularFile), false, false).await.unwrap(); let test_dir = SecretString::from_str("test-dir").unwrap(); - let (_fh, dir_attr) = fs.create_nod(ROOT_INODE, &test_dir, create_attr_from_type(FileType::Directory), false, false).await?; - let mut entries: Vec> = fs.read_dir(dir_attr.ino).await?.collect(); + let (_fh, dir_attr) = fs.create_nod(ROOT_INODE, &test_dir, create_attr_from_type(FileType::Directory), false, false).await.unwrap(); + let mut entries: Vec> = fs.read_dir(dir_attr.ino).await.unwrap().collect(); entries.sort_by(|a, b| a.as_ref().unwrap().name.expose_secret().cmp(&b.as_ref().unwrap().name.expose_secret())); let entries: Vec = entries.into_iter().map(|e| e.unwrap()).collect(); assert_eq!(entries.len(), 2); @@ -407,7 +417,7 @@ async fn test_read_dir() -> FsResult<()> { }, ], entries); - let iter = fs.read_dir(ROOT_INODE).await?; + let iter = fs.read_dir(ROOT_INODE).await.unwrap(); let mut entries: Vec> = iter.into_iter().collect(); entries.sort_by(|a, b| a.as_ref().unwrap().name.expose_secret().cmp(&b.as_ref().unwrap().name.expose_secret())); let entries: Vec = entries.into_iter().map(|e| e.unwrap()).collect(); @@ -434,11 +444,11 @@ async fn test_read_dir() -> FsResult<()> { // file and directory in another directory let parent = dir_attr.ino; let test_file_2 = SecretString::from_str("test-file-2").unwrap(); - let (_fh, file_attr) = fs.create_nod(parent, &test_file_2, create_attr_from_type(FileType::RegularFile), false, false).await?; + let (_fh, file_attr) = fs.create_nod(parent, &test_file_2, create_attr_from_type(FileType::RegularFile), false, false).await.unwrap(); let test_dir_2 = SecretString::from_str("test-dir-2").unwrap(); - let (_fh, dir_attr) = fs.create_nod(parent, &test_dir_2, create_attr_from_type(FileType::Directory), false, false).await?; - let mut entries: Vec> = fs.read_dir(dir_attr.ino).await?.collect(); + let (_fh, dir_attr) = fs.create_nod(parent, &test_dir_2, create_attr_from_type(FileType::Directory), false, false).await.unwrap(); + let mut entries: Vec> = fs.read_dir(dir_attr.ino).await.unwrap().collect(); entries.sort_by(|a, b| a.as_ref().unwrap().name.expose_secret().cmp(&b.as_ref().unwrap().name.expose_secret())); let entries: Vec = entries.into_iter().map(|e| e.unwrap()).collect(); assert_eq!(entries.len(), 2); @@ -455,7 +465,7 @@ async fn test_read_dir() -> FsResult<()> { }, ], entries); - let iter = fs.read_dir(parent).await?; + let iter = fs.read_dir(parent).await.unwrap(); let mut entries: Vec = iter.map(|e| e.unwrap()).collect(); entries.sort_by(|a, b| a.name.expose_secret().cmp(&b.name.expose_secret())); let mut sample = vec![ @@ -482,13 +492,11 @@ async fn test_read_dir() -> FsResult<()> { sample.sort_by(|a, b| a.name.expose_secret().cmp(&b.name.expose_secret())); assert_eq!(entries.len(), 4); assert_eq!(sample, entries); - - Ok::<(), FsError>(()) }).await } #[tokio::test] -async fn test_read_dir_plus() -> FsResult<()> { +async fn test_read_dir_plus() { run_test(TestSetup { data_path: format!("{TESTS_DATA_DIR}test_read_dir_plus") }, async { let fs = SETUP_RESULT.with(|s| Arc::clone(s)); let mut fs = fs.lock().await; @@ -496,15 +504,15 @@ async fn test_read_dir_plus() -> FsResult<()> { // file and directory in root let test_file = SecretString::from_str("test-file").unwrap(); - let (_fh, file_attr) = fs.create_nod(ROOT_INODE, &test_file, create_attr_from_type(FileType::RegularFile), false, false).await?; + let (_fh, file_attr) = fs.create_nod(ROOT_INODE, &test_file, create_attr_from_type(FileType::RegularFile), false, false).await.unwrap(); let test_dir = SecretString::from_str("test-dir").unwrap(); - let (_fh, dir_attr) = fs.create_nod(ROOT_INODE, &test_dir, create_attr_from_type(FileType::Directory), false, false).await?; - let mut entries: Vec> = fs.read_dir_plus(dir_attr.ino).await?.collect(); + let (_fh, dir_attr) = fs.create_nod(ROOT_INODE, &test_dir, create_attr_from_type(FileType::Directory), false, false).await.unwrap(); + let mut entries: Vec> = fs.read_dir_plus(dir_attr.ino).await.unwrap().collect(); entries.sort_by(|a, b| a.as_ref().unwrap().name.expose_secret().cmp(&b.as_ref().unwrap().name.expose_secret())); let entries: Vec = entries.into_iter().map(|e| e.unwrap()).collect(); assert_eq!(entries.len(), 2); - let attr_root = fs.get_inode(ROOT_INODE).await?; + let attr_root = fs.get_inode(ROOT_INODE).await.unwrap(); assert_eq!(vec![ DirectoryEntryPlus { ino: dir_attr.ino, @@ -520,7 +528,7 @@ async fn test_read_dir_plus() -> FsResult<()> { }, ], entries); - let iter = fs.read_dir_plus(ROOT_INODE).await?; + let iter = fs.read_dir_plus(ROOT_INODE).await.unwrap(); let mut entries: Vec> = iter.into_iter().collect(); entries.sort_by(|a, b| a.as_ref().unwrap().name.expose_secret().cmp(&b.as_ref().unwrap().name.expose_secret())); let entries: Vec = entries.into_iter().map(|e| e.unwrap()).collect(); @@ -551,14 +559,14 @@ async fn test_read_dir_plus() -> FsResult<()> { let parent = dir_attr.ino; let attr_parent = dir_attr; let test_file_2 = SecretString::from_str("test-file-2").unwrap(); - let (_fh, file_attr) = fs.create_nod(parent, &test_file_2, create_attr_from_type(FileType::RegularFile), false, false).await?; + let (_fh, file_attr) = fs.create_nod(parent, &test_file_2, create_attr_from_type(FileType::RegularFile), false, false).await.unwrap(); let test_dir_2 = SecretString::from_str("test-dir-2").unwrap(); - let (_fh, dir_attr) = fs.create_nod(parent, &test_dir_2, create_attr_from_type(FileType::Directory), false, false).await?; + let (_fh, dir_attr) = fs.create_nod(parent, &test_dir_2, create_attr_from_type(FileType::Directory), false, false).await.unwrap(); // for some reason the tv_nsec is not the same between what create_nod() and read_dir_plus() returns, so we reload it again - let dir_attr = fs.get_inode(dir_attr.ino).await?; - let attr_parent = fs.get_inode(attr_parent.ino).await?; - let mut entries: Vec> = fs.read_dir_plus(dir_attr.ino).await?.collect(); + let dir_attr = fs.get_inode(dir_attr.ino).await.unwrap(); + let attr_parent = fs.get_inode(attr_parent.ino).await.unwrap(); + let mut entries: Vec> = fs.read_dir_plus(dir_attr.ino).await.unwrap().collect(); entries.sort_by(|a, b| a.as_ref().unwrap().name.expose_secret().cmp(&b.as_ref().unwrap().name.expose_secret())); let entries: Vec = entries.into_iter().map(|e| e.unwrap()).collect(); assert_eq!(entries.len(), 2); @@ -577,7 +585,7 @@ async fn test_read_dir_plus() -> FsResult<()> { }, ], entries); - let iter = fs.read_dir_plus(parent).await?; + let iter = fs.read_dir_plus(parent).await.unwrap(); let mut entries: Vec = iter.map(|e| e.unwrap()).collect(); entries.sort_by(|a, b| a.name.expose_secret().cmp(&b.name.expose_secret())); let mut sample = vec![ @@ -608,7 +616,5 @@ async fn test_read_dir_plus() -> FsResult<()> { sample.sort_by(|a, b| a.name.expose_secret().cmp(&b.name.expose_secret())); assert_eq!(entries.len(), 4); assert_eq!(sample, entries); - - Ok::<(), FsError>(()) }).await } \ No newline at end of file diff --git a/src/encryptedfs_fuse3.rs b/src/encryptedfs_fuse3.rs index f32b08c4..747a8c0a 100644 --- a/src/encryptedfs_fuse3.rs +++ b/src/encryptedfs_fuse3.rs @@ -15,7 +15,7 @@ use fuse3::raw::prelude::{DirectoryEntry, DirectoryEntryPlus, ReplyAttr, ReplyCo use fuse3::raw::{Filesystem, Request}; use futures_util::stream; use futures_util::stream::Iter; -use libc::{EACCES, EBADF, EEXIST, EIO, ENAMETOOLONG, ENOENT, ENOTDIR, ENOTEMPTY, EPERM}; +use libc::{EACCES, EEXIST, EIO, EISDIR, ENAMETOOLONG, ENOENT, ENOTDIR, ENOTEMPTY, EPERM}; use secrecy::{ExposeSecret, SecretString}; use tracing::{debug, error, instrument, trace, warn}; use crate::crypto::Cipher; @@ -36,7 +36,7 @@ const STATFS: ReplyStatFs = ReplyStatFs { const FMODE_EXEC: i32 = 0x20; -const MAX_NAME_LENGTH: u32 = 255; +// const MAX_NAME_LENGTH: u32 = 255; // Flags returned by the open request const FOPEN_DIRECT_IO: u32 = 1 << 0; // bypass page cache for this open file @@ -249,10 +249,10 @@ impl Filesystem for EncryptedFsFuse3 { async fn lookup(&self, req: Request, parent: u64, name: &OsStr) -> Result { trace!(""); - if name.len() > MAX_NAME_LENGTH as usize { - warn!(name = %name.to_str().unwrap(), "name too long"); - return Err(ENAMETOOLONG.into()); - } + // if name.len() > MAX_NAME_LENGTH as usize { + // warn!(name = %name.to_str().unwrap(), "name too long"); + // return Err(ENAMETOOLONG.into()); + // } match self.get_fs().get_inode(parent).await { Err(err) => { @@ -370,7 +370,7 @@ impl Filesystem for EncryptedFsFuse3 { set_attr2 = set_attr2.with_atime(SystemTime::now()); self.get_fs().update_inode(inode, set_attr2).await.map_err(|err| { error!(err = %err); - Errno::from(EBADF) + Errno::from(EIO) })?; return Ok(ReplyAttr { ttl: TTL, @@ -422,7 +422,7 @@ impl Filesystem for EncryptedFsFuse3 { set_attr2 = set_attr2.with_atime(SystemTime::now()); self.get_fs().update_inode(inode, set_attr2).await.map_err(|err| { error!(err = %err); - Errno::from(EBADF) + Errno::from(EIO) })?; return Ok(ReplyAttr { ttl: TTL, @@ -483,7 +483,7 @@ impl Filesystem for EncryptedFsFuse3 { self.get_fs().update_inode(inode, set_attr2).await.map_err(|err| { error!(err = %err); - Errno::from(EBADF) + Errno::from(EIO) })?; Ok(ReplyAttr { @@ -682,7 +682,10 @@ impl Filesystem for EncryptedFsFuse3 { if let Err(err) = self.get_fs().remove_dir(parent, &SecretString::from_str(name.to_str().unwrap()).unwrap()).await { error!(err = %err); - return Err(EBADF.into()); + return match err { + FsError::NotEmpty => Err(EISDIR.into()), + _ => Err(EIO.into()), + } } Ok(()) @@ -813,7 +816,7 @@ impl Filesystem for EncryptedFsFuse3 { let attr = self.get_fs().get_inode(inode).await.map_err(|err| { error!(err = %err); - EBADF + EIO })?; // if check_access(attr.uid, attr.gid, attr.perm, req.uid, req.gid, access_mask) { @@ -826,7 +829,7 @@ impl Filesystem for EncryptedFsFuse3 { let open_flags = if self.direct_io { FOPEN_DIRECT_IO } else { 0 }; let fh = self.get_fs().open(inode, read, write).await.map_err(|err| { error!(err = %err); - EBADF + EIO })?; debug!(fh, "opened handle"); Ok(ReplyOpen { fh, flags: open_flags }) @@ -875,14 +878,14 @@ impl Filesystem for EncryptedFsFuse3 { trace!(""); debug!(size = data.len()); - self.get_fs().write_all(inode, offset, data, fh).await.map_err(|err| { + let len = self.get_fs().write(inode, offset, data, fh).await.map_err(|err| { error!("{err:#?}"); error!(err = %err); EIO })?; Ok(ReplyWrite { - written: data.len() as u32, + written: len as u32, }) } @@ -933,7 +936,7 @@ impl Filesystem for EncryptedFsFuse3 { set_attr = set_attr.with_perm(clear_suid_sgid(attr.perm)); fs.update_inode(inode, set_attr).await.map_err(|err| { error!(err = %err, "replace attr"); - Errno::from(EBADF) + Errno::from(EIO) })?; } @@ -1128,7 +1131,7 @@ impl Filesystem for EncryptedFsFuse3 { .copy_file_range(inode, off_in, inode_out, off_out, length as usize, fh_in, fh_out).await { Err(err) => { error!(err = %err); - return Err(EBADF.into()); + return Err(EIO.into()); } Ok(len) => { Ok(ReplyCopyFileRange { diff --git a/src/expire_value.rs b/src/expire_value.rs index 40d0c080..5c6e980c 100644 --- a/src/expire_value.rs +++ b/src/expire_value.rs @@ -67,6 +67,10 @@ impl> ExpireValu } None } + + pub async fn clear(&self) { + self.cache.clear().await; + } } impl> Drop for ExpireValue { diff --git a/src/lib.rs b/src/lib.rs index 335445e7..cca065d8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -92,6 +92,7 @@ //! use secrecy::SecretString; //! use rencfs::encryptedfs::{EncryptedFs, FileAttr, FileType, PasswordProvider, CreateFileAttr}; //! use rencfs::crypto::Cipher; +//! use anyhow::Result; //! //! const ROOT_INODE: u64 = 1; //! @@ -104,8 +105,9 @@ //! } //! //! #[tokio::main] -//! async fn main() -> anyhow::Result<()> { -//! let data_dir = "/tmp/rencfs_data_test"; +//! async fn main() -> Result<()> { +//! use rencfs::stream_util::write_all_string_to_fs; +//! let data_dir = "/tmp/rencfs_data_test"; //! let _ = fs::remove_dir_all(data_dir); //! let password = SecretString::from_str("password").unwrap(); //! let cipher = Cipher::ChaCha20; @@ -114,7 +116,7 @@ //! let file1 = SecretString::from_str("file1").unwrap(); //! let (fh, attr) = fs.create_nod(ROOT_INODE, &file1, file_attr(), false, true).await?; //! let data = "Hello, world!"; -//! fs.write_all(attr.ino, 0, data.as_bytes(), fh).await?; +//! write_all_string_to_fs( &fs, attr.ino, 0,data, fh).await?; //! fs.flush(fh).await?; //! fs.release(fh).await?; //! let fh = fs.open(attr.ino, true, false).await?; diff --git a/src/stream_util.rs b/src/stream_util.rs index de81edcc..611ad954 100644 --- a/src/stream_util.rs +++ b/src/stream_util.rs @@ -2,7 +2,8 @@ use std::cmp::min; use std::io; use std::io::{Read, Write}; use num_format::{Locale, ToFormattedString}; -use tracing::{debug, error, info, instrument}; +use tracing::{debug, error, instrument}; +use crate::encryptedfs::EncryptedFs; #[cfg(test)] const BUF_SIZE: usize = 256 * 1024; @@ -73,7 +74,7 @@ pub fn fill_zeros(w: &mut impl Write, len: u64) -> io::Result<()> { if len == 0 { return Ok(()); } - let mut buffer = vec![0; BUF_SIZE]; + let buffer = vec![0; BUF_SIZE]; let mut written = 0_u64; loop { let buf_len = min(buffer.len(), (len - written) as usize); @@ -84,4 +85,20 @@ pub fn fill_zeros(w: &mut impl Write, len: u64) -> io::Result<()> { } } Ok(()) -} \ No newline at end of file +} + +pub async fn write_all_string_to_fs(fs: &EncryptedFs, ino: u64, offset: u64, s: &str, fh: u64) -> anyhow::Result<()> { + write_all_bytes_to_fs(fs, ino, offset, s.as_bytes(), fh).await +} + +pub async fn write_all_bytes_to_fs(fs: &EncryptedFs, ino: u64, offset: u64, buf: &[u8], fh: u64) -> anyhow::Result<()> { + let mut pos = 0_usize; + loop { + let len = fs.write(ino, offset, &buf[pos..], fh).await.unwrap(); + pos += len; + if pos == buf.len() { + break; + } + } + Ok(()) +}