Skip to content

Commit

Permalink
fix #28 structure cli params with subcommand
Browse files Browse the repository at this point in the history
fix #29 have all writes to encrypted files done in tmp files and mv to location after that"
  • Loading branch information
radumarias committed May 5, 2024
1 parent 0d4feea commit ae509b5
Show file tree
Hide file tree
Showing 10 changed files with 275 additions and 187 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ num-format = "0.4.4"
ring = "0.17.8"
hex = "0.4.3"
rand_chacha = "0.3.1"
tempfile = "3.10.1"

[package.metadata.aur]
depends = ["fuse3"]
13 changes: 9 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,13 @@ cargo install rencfs
A basic example of how to use the encrypted file system is shown below

```
rencfs --mount-point MOUNT_POINT --data-dir DATA_DIR
rencfs mount --mount-point MOUNT_POINT --data-dir DATA_DIR --tmp-dir TMP_DIR
```
Where `MOUNT_POINT` is the directory where the encrypted file system will be mounted and `DATA_DIR` is the directory where the encrypted data will be stored.\
- `MOUNT_POINT` act as a client, and mount FUSE at given path
- `DATA_DIR` where to store the encrypted data
- `TMP_DIR` where keep temp data. This should be in a different directory than `DATA_DIR` as you don't want to sync this with the sync provider. But it needs to be on the same filesystem as the data-dir

It will prompt you to enter a password to encrypt/decrypt the data.

### Change Password
Expand All @@ -83,9 +87,10 @@ This is done by decrypting the key with the old password and re-encrypting it wi

To change the password, you can run the following command
```bash
rencfs --change-password --data-dir DATA_DIR
rencfs change-password --data-dir DATA_DIR
```
Where `DATA_DIR` is the directory where the encrypted data is stored.\
`DATA_DIR` where the encrypted data is stored

It will prompt you to enter the old password and then the new password.

### Encryption info
Expand Down
9 changes: 6 additions & 3 deletions src/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use strum_macros::{Display, EnumIter, EnumString};
use thiserror::Error;
use tracing::{debug, error, instrument};

use crate::{crypto, stream_util};
use crate::stream_util;
use crate::crypto::reader::{CryptoReader, RingCryptoReader};
use crate::crypto::writer::{CryptoWriter, RingCryptoWriter};
use crate::encryptedfs::FsResult;
Expand Down Expand Up @@ -147,7 +147,7 @@ pub fn encrypt_string_with_nonce_seed(s: &SecretString, cipher: &Cipher, key: Ar
let mut writer = create_writer(cursor, cipher, key, nonce_seed);
writer.write_all(s.expose_secret().as_bytes())?;
writer.flush()?;
cursor = writer.finish()?.unwrap();
cursor = writer.finish()?;
let v = cursor.into_inner();
if include_nonce_seed {
Ok(format!("{}.{}", base64::encode(v), nonce_seed))
Expand All @@ -163,7 +163,7 @@ pub fn encrypt_string(s: &SecretString, cipher: &Cipher, key: Arc<SecretVec<u8>>
let mut writer = create_writer(cursor, cipher, key, nonce_seed);
writer.write_all(s.expose_secret().as_bytes())?;
writer.flush()?;
cursor = writer.finish()?.unwrap();
cursor = writer.finish()?;
let v = cursor.into_inner();
Ok(format!("{}.{}", base64::encode(v), nonce_seed))
}
Expand Down Expand Up @@ -217,6 +217,9 @@ pub fn derive_key(password: &SecretString, cipher: &Cipher, salt: [u8; 32]) -> R

/// Encrypt a file name with provided nonce seed. It will **INCLUDE** the nonce seed in the result so that it can be used when decrypting.
pub fn encrypt_file_name(name: &SecretString, cipher: &Cipher, key: Arc<SecretVec<u8>>, nonce_seed: u64) -> FsResult<String> {
// in order not to add too much to filename length we keep just 3 digits from nonce seed
let nonce_seed = nonce_seed % 1000;

if name.expose_secret() != "$." && name.expose_secret() != "$.." {
let normalized_name = SecretString::new(name.expose_secret().replace("/", " ").replace("\\", " "));
let mut encrypted = encrypt_string_with_nonce_seed(&normalized_name, cipher, key, nonce_seed, true)?;
Expand Down
6 changes: 3 additions & 3 deletions src/crypto/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use tracing::{error, instrument};
use crate::crypto::buf_mut::BufMut;

pub trait CryptoWriter<W: Write>: Write + Sync + Send {
fn finish(&mut self) -> io::Result<Option<W>>;
fn finish(&mut self) -> io::Result<W>;
}

/// cryptostream
Expand Down Expand Up @@ -112,12 +112,12 @@ impl<W: Write> RingCryptoWriter<W> {
}

impl<W: Write + Send + Sync> CryptoWriter<W> for RingCryptoWriter<W> {
fn finish(&mut self) -> io::Result<Option<W>> {
fn finish(&mut self) -> io::Result<W> {
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()?))
Ok(self.out.take().unwrap().into_inner()?)
}
}

Expand Down
62 changes: 28 additions & 34 deletions src/encryptedfs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use rand::thread_rng;
use ring::aead::{AES_256_GCM, CHACHA20_POLY1305};
use secrecy::{ExposeSecret, SecretString, SecretVec};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use tempfile::NamedTempFile;
use thiserror::Error;
use tokio::sync::{Mutex, MutexGuard, RwLock};
use tokio_stream::wrappers::ReadDirStream;
Expand Down Expand Up @@ -630,6 +631,7 @@ pub trait PasswordProvider: Send + Sync + 'static {
/// Encrypted FS that stores encrypted files in a dedicated directory with a specific structure based on `inode`.
pub struct EncryptedFs {
pub(crate) data_dir: PathBuf,
tmp_dir: PathBuf,
write_handles: RwLock<HashMap<u64, Mutex<WriteHandleContext>>>,
read_handles: RwLock<HashMap<u64, Mutex<ReadHandleContext>>>,
current_handle: AtomicU64,
Expand All @@ -649,19 +651,18 @@ pub struct EncryptedFs {
}

impl EncryptedFs {
pub async fn new(data_dir: &str, password_provider: Box<dyn PasswordProvider>, cipher: Cipher) -> FsResult<Self> {
let path = PathBuf::from(&data_dir);

pub async fn new(data_dir: PathBuf, tmp_dir: PathBuf, password_provider: Box<dyn PasswordProvider>, cipher: Cipher) -> FsResult<Self> {
let key_provider = KeyProvider {
path: path.join(SECURITY_DIR).join(KEY_ENC_FILENAME),
path: data_dir.join(SECURITY_DIR).join(KEY_ENC_FILENAME),
password_provider,
cipher: cipher.clone(),
};

ensure_structure_created(&path.clone(), &key_provider).await?;
ensure_structure_created(&data_dir.clone(), &tmp_dir.clone(), &key_provider).await?;

let fs = EncryptedFs {
data_dir: path.clone(),
data_dir,
tmp_dir,
write_handles: RwLock::new(HashMap::new()),
read_handles: RwLock::new(HashMap::new()),
current_handle: AtomicU64::new(1),
Expand Down Expand Up @@ -787,9 +788,12 @@ impl EncryptedFs {
SecretString::new(name.expose_secret().to_owned())
}
};
let name = crypto::encrypt_file_name(&name, &self.cipher, self.key.get().await?, parent)?;
// in order not to add too much to filename length we keep just 3 digits from nonce seed
let nonce_seed = parent % 1000;
let name = crypto::encrypt_file_name(&name, &self.cipher, self.key.get().await?, nonce_seed)?;
let file = File::open(self.data_dir.join(CONTENTS_DIR).join(parent.to_string()).join(name))?;
let (inode, _): (u64, FileType) = bincode::deserialize_from(self.create_crypto_reader(file, parent).await?)?;
// attr are encrypted with same nonce seed as filename
let (inode, _): (u64, FileType) = bincode::deserialize_from(self.create_crypto_reader(file, nonce_seed).await?)?;
Ok(Some(self.get_inode(inode).await?))
}

Expand Down Expand Up @@ -887,6 +891,7 @@ impl EncryptedFs {
}
};
let name = crypto::encrypt_file_name(&name, &self.cipher, self.key.get().await?, parent)?;
info!(parent = parent, name = name);
Ok(self.data_dir.join(CONTENTS_DIR).join(parent.to_string()).join(name).exists())
}

Expand Down Expand Up @@ -976,16 +981,12 @@ impl EncryptedFs {
let _guard = lock.write().unwrap();

let path = self.data_dir.join(INODES_DIR).join(attr.ino.to_string());
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(&path)?;
let mut writer = crypto::create_writer(file, &self.cipher, key, attr.ino);
let tmp = NamedTempFile::new_in(self.tmp_dir.clone())?;
let mut writer = crypto::create_writer(tmp, &self.cipher, key, attr.ino);
bincode::serialize_into(&mut writer, &attr)?;
writer.flush()?;
writer.finish()?;
let tmp = writer.finish()?;
fs::rename(tmp.into_temp_path(), path)?;
Ok(())
}

Expand Down Expand Up @@ -1604,9 +1605,7 @@ impl EncryptedFs {
}

/// Change the password of the filesystem used to access the encryption key.
pub async fn change_password(data_dir: &str, old_password: SecretString, new_password: SecretString, cipher: Cipher) -> FsResult<()> {
let data_dir = PathBuf::from(data_dir);

pub async fn change_password(data_dir: PathBuf, old_password: SecretString, new_password: SecretString, cipher: Cipher) -> FsResult<()> {
check_structure(&data_dir, false).await?;

// decrypt key
Expand All @@ -1623,13 +1622,12 @@ impl EncryptedFs {
// encrypt it with new key derived from new password
let salt = crypto::hash_secret_string(&new_password);
let new_key = crypto::derive_key(&new_password, &cipher, salt)?;
tokio::fs::remove_file(enc_file.clone()).await?;
let mut writer = crypto::create_writer(OpenOptions::new().read(true).write(true).create(true).truncate(true).open(enc_file.clone())?,
&cipher, Arc::new(new_key), 42_u64);
let tmp = NamedTempFile::new_in(data_dir.join(SECURITY_DIR).join(KEY_ENC_FILENAME))?;
let mut writer = crypto::create_writer(tmp, &cipher, Arc::new(new_key), 42_u64);
bincode::serialize_into(&mut writer, &key_store)?;
writer.flush()?;
writer.finish()?;

let tmp = writer.finish()?;
fs::rename(tmp.into_temp_path(), enc_file)?;
Ok(())
}

Expand Down Expand Up @@ -1788,19 +1786,14 @@ impl EncryptedFs {
let lock = map.get_or_insert_with(file_path.to_str().unwrap().to_string(), || std::sync::RwLock::new(false));
let _guard = lock.write().unwrap();

let file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(&file_path)?;

// write inode and file type
let entry = (entry.ino, entry.kind);
let mut writer = crypto::create_writer(file, &self.cipher, key, nonce_seed);
let tmp = NamedTempFile::new_in(self.tmp_dir.clone())?;
let mut writer = crypto::create_writer(tmp, &self.cipher, key, nonce_seed);
bincode::serialize_into(&mut writer, &entry)?;
writer.flush()?;
writer.finish()?;

let tmp = writer.finish()?;
fs::rename(tmp.into_temp_path(), file_path)?;
Ok(())
}

Expand Down Expand Up @@ -1868,7 +1861,7 @@ impl EncryptedFs {
}
}

async fn ensure_structure_created(data_dir: &PathBuf, key_provider: &KeyProvider) -> FsResult<()> {
async fn ensure_structure_created(data_dir: &PathBuf, tmp_dir: &PathBuf, key_provider: &KeyProvider) -> FsResult<()> {
if data_dir.exists() {
check_structure(data_dir, true).await?;
} else {
Expand All @@ -1883,6 +1876,7 @@ async fn ensure_structure_created(data_dir: &PathBuf, key_provider: &KeyProvider
tokio::fs::create_dir_all(path).await?;
}
}
tokio::fs::create_dir_all(tmp_dir).await?;

// create encryption key
key_provider.provide()?;
Expand Down
12 changes: 6 additions & 6 deletions src/encryptedfs/moved_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::{fs, io};
use std::fs::OpenOptions;
use std::io::Read;
use std::ops::DerefMut;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::sync::Arc;

Expand All @@ -26,11 +26,11 @@ struct SetupResult {
}

async fn setup(setup: TestSetup) -> SetupResult {
let path = setup.data_path.as_str();
if fs::metadata(path).is_ok() {
fs::remove_dir_all(path).unwrap();
let data_dir_str = setup.data_path.as_str();
if fs::metadata(data_dir_str).is_ok() {
fs::remove_dir_all(data_dir_str).unwrap();
}
fs::create_dir_all(path).unwrap();
let tmp = Path::new(data_dir_str).join("tmp");

struct PasswordProviderImpl {}
impl PasswordProvider for PasswordProviderImpl {
Expand All @@ -39,7 +39,7 @@ async fn setup(setup: TestSetup) -> SetupResult {
}
}

let fs = EncryptedFs::new(path, Box::new(PasswordProviderImpl {}), Cipher::ChaCha20).await.unwrap();
let fs = EncryptedFs::new(Path::new(data_dir_str).to_path_buf(), tmp, Box::new(PasswordProviderImpl {}), Cipher::ChaCha20).await.unwrap();

SetupResult {
fs: Some(fs),
Expand Down
7 changes: 4 additions & 3 deletions src/encryptedfs_fuse3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::io::{BufRead, BufReader};
use std::iter::Skip;
use std::num::NonZeroU32;
use std::os::raw::c_int;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Arc;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
Expand Down Expand Up @@ -121,18 +122,18 @@ pub struct EncryptedFsFuse3 {
}

impl EncryptedFsFuse3 {
pub async fn new(data_dir: &str, password_provider: Box<dyn PasswordProvider>, cipher: Cipher,
pub async fn new(data_dir: PathBuf, tmp_dir: PathBuf, password_provider: Box<dyn PasswordProvider>, cipher: Cipher,
direct_io: bool, _suid_support: bool) -> FsResult<Self> {
#[cfg(feature = "abi-7-26")] {
Ok(Self {
fs: Arc::new(EncryptedFs::new(data_dir, password_provider, cipher).await?),
fs: Arc::new(EncryptedFs::new(data_dir, tmp_dir, password_provider, cipher).await?),
direct_io,
suid_support: _suid_support,
})
}
#[cfg(not(feature = "abi-7-26"))] {
Ok(Self {
fs: Arc::new(EncryptedFs::new(data_dir, password_provider, cipher).await?),
fs: Arc::new(EncryptedFs::new(data_dir, tmp_dir, password_provider, cipher).await?),
direct_io,
suid_support: false,
})
Expand Down
Loading

0 comments on commit ae509b5

Please sign in to comment.