Skip to content

Commit

Permalink
use anyhow crate
Browse files Browse the repository at this point in the history
save encryption key with a hash so we can correctly validate if pass is ok on fs init or on chang epass
added examples for changing pass
check dir/key structure if data-dir is an existing non-empty dir
  • Loading branch information
radumarias committed Apr 26, 2024
1 parent 298b1ec commit 389124e
Show file tree
Hide file tree
Showing 6 changed files with 402 additions and 269 deletions.
9 changes: 8 additions & 1 deletion Cargo.lock

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

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "rencfs"
description = "An encrypted file system that mounts with FUSE on Linux. It can be used to create encrypted directories."
version = "0.1.35"
version = "0.1.36"
edition = "2021"
license = "Apache-2.0"
authors = ["Radu Marias <[email protected]>"]
Expand Down Expand Up @@ -40,6 +40,7 @@ strum_macros = "0.26.2"
rpassword = "7.3.1"
cryptostream = "0.3.2"
weak-table = "0.3.2"
anyhow = "1.0.82"

[package.metadata.aur]
depends = ["fuse3"]
104 changes: 75 additions & 29 deletions src/encryptedfs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use std::sync::{Arc, Mutex, RwLock, Weak};
use std::sync::atomic::AtomicU64;
use std::time::SystemTime;

use cryptostream::{read, write};
use cryptostream::read;
use cryptostream::read::Decryptor;
use cryptostream::write::Encryptor;
use openssl::error::ErrorStack;
Expand Down Expand Up @@ -126,9 +126,12 @@ pub enum FsError {

#[error("invalid password")]
InvalidPassword,

#[error("invalid structure of data directory")]
InvalidDataDirStructure,
}

#[derive(Debug, Clone, EnumIter, EnumString, Display)]
#[derive(Debug, Clone, EnumIter, EnumString, Display, Serialize, Deserialize, PartialEq)]
pub enum Cipher {
ChaCha20,
Aes256Gcm,
Expand Down Expand Up @@ -278,6 +281,12 @@ impl Iterator for DirectoryEntryPlusIterator {
}
}

#[derive(Debug, Serialize, Deserialize)]
struct KeyStore {
key: Vec<u8>,
hash: [u8; 32],
}

/// 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,
Expand Down Expand Up @@ -1073,12 +1082,12 @@ impl EncryptedFs {
})?;

let mut parent_attr = self.get_inode(parent)?;
parent_attr.mtime = std::time::SystemTime::now();
parent_attr.ctime = std::time::SystemTime::now();
parent_attr.mtime = SystemTime::now();
parent_attr.ctime = SystemTime::now();

let mut new_parent_attr = self.get_inode(new_parent)?;
new_parent_attr.mtime = std::time::SystemTime::now();
new_parent_attr.ctime = std::time::SystemTime::now();
new_parent_attr.mtime = SystemTime::now();
new_parent_attr.ctime = SystemTime::now();

attr.ctime = std::time::SystemTime::now();

Expand Down Expand Up @@ -1106,7 +1115,7 @@ impl EncryptedFs {
}

/// Create an encryptor using internal encryption info.
pub fn create_encryptor(&self, file: File) -> write::Encryptor<File> {
pub fn create_encryptor(&self, file: File) -> Encryptor<File> {
crypto_util::create_encryptor(file, &self.cipher, &self.key)
}

Expand All @@ -1133,21 +1142,24 @@ impl EncryptedFs {
pub fn change_password(data_dir: &str, old_password: &str, new_password: &str, cipher: Cipher, derive_key_hash_rounds: u32) -> FsResult<()> {
let data_dir = PathBuf::from(data_dir);

check_structure(&data_dir, false)?;

// decrypt key
let initial_key = crypto_util::derive_key(old_password, &cipher, derive_key_hash_rounds, "salt-42");
let enc_file = data_dir.join(SECURITY_DIR).join(KEY_ENC_FILENAME);
let mut decryptor = crypto_util::create_decryptor(File::open(enc_file.clone())?, &cipher, &initial_key);
let mut key: Vec<u8> = vec![];
decryptor.read_to_end(&mut key)?;
decryptor.finish();
let decryptor = crypto_util::create_decryptor(File::open(enc_file.clone())?, &cipher, &initial_key);
let key_store: KeyStore = bincode::deserialize_from(decryptor).map_err(|_| FsError::InvalidPassword)?;
// check hash
if key_store.hash != crypto_util::hash(key_store.key.as_slice()) {
return Err(FsError::InvalidPassword);
}

// encrypt it with new key derived from new password
let new_key = crypto_util::derive_key(new_password, &cipher, derive_key_hash_rounds, "salt-42");
fs::remove_file(enc_file.clone())?;
let mut encryptor = crypto_util::create_encryptor(OpenOptions::new().read(true).write(true).create(true).truncate(true).open(enc_file.clone())?,
&cipher, &new_key);
encryptor.write_all(&key)?;
encryptor.finish()?;
bincode::serialize_into(&mut encryptor, &key_store)?;

Ok(())
}
Expand Down Expand Up @@ -1190,11 +1202,12 @@ impl EncryptedFs {
}

fn check_password(&mut self) -> FsResult<()> {
match self.get_inode(ROOT_INODE) {
Ok(_) => Ok(()),
Err(FsError::SerializeError(_)) => Err(FsError::InvalidPassword),
Err(err) => Err(err),
}
self.get_inode(ROOT_INODE).map_err(|err| match err {
FsError::SerializeError(_) => FsError::InvalidPassword,
_ => err
})?;

Ok(())
}

fn create_read_handle(&mut self, ino: u64, attr: Option<TimeAndSizeFileAttr>, handle: u64, lock: Arc<RwLock<u8>>) -> FsResult<u64> {
Expand Down Expand Up @@ -1308,7 +1321,9 @@ impl EncryptedFs {
}

fn ensure_structure_created(data_dir: &PathBuf) -> FsResult<()> {
if !data_dir.exists() {
if data_dir.exists() {
check_structure(data_dir, true)?;
} else {
fs::create_dir_all(&data_dir)?;
}

Expand All @@ -1324,6 +1339,29 @@ fn ensure_structure_created(data_dir: &PathBuf) -> FsResult<()> {
Ok(())
}

fn check_structure(data_dir: &PathBuf, ignore_empty: bool) -> FsResult<()> {
if !data_dir.exists() || !data_dir.is_dir() {
return Err(FsError::InvalidDataDirStructure);
}
let mut vec1 = fs::read_dir(data_dir)?.into_iter().map(|dir| dir.unwrap().file_name().to_string_lossy().to_string()).collect::<Vec<String>>();
if vec1.len() == 0 && ignore_empty {
return Ok(());
}
if vec1.len() != 3 {
return Err(FsError::InvalidDataDirStructure);
}
// make sure existing structure is ok
vec1.sort();
let mut vec2 = vec![INODES_DIR, CONTENTS_DIR, SECURITY_DIR];
vec2.sort();
if vec1 != vec2 ||
!data_dir.join(SECURITY_DIR).join(KEY_ENC_FILENAME).is_file() {
return Err(FsError::InvalidDataDirStructure);
}

Ok(())
}

fn merge_attr(attr: &mut FileAttr, perm: u16, atime: SystemTime, mtime: SystemTime, ctime: SystemTime, crtime: SystemTime, uid: u32, gid: u32, size: u64, nlink: u32, flags: u32) {
attr.perm = perm;
merge_attr_times_and_set_size(attr, atime, mtime, ctime, crtime, size);
Expand Down Expand Up @@ -1351,25 +1389,33 @@ fn merge_attr_time_and_set_size_obj(attr: &mut FileAttr, from: &TimeAndSizeFileA

fn read_or_create_key(path: PathBuf, password: &str, cipher: &Cipher, rounds: u32) -> FsResult<Vec<u8>> {
let derived_key = crypto_util::derive_key(password, cipher, rounds, "salt-42");
if !path.exists() {
if path.exists() {
// read key

let decryptor = crypto_util::create_decryptor(File::open(path)?, cipher, &derived_key);
let key_store: KeyStore = bincode::deserialize_from(decryptor).map_err(|_| FsError::InvalidPassword)?;
// check hash
if key_store.hash != crypto_util::hash(key_store.key.as_slice()) {
return Err(FsError::InvalidPassword);
}
Ok(key_store.key)
} else {
// first time, create a random key and encrypt it with the derived key from password

let mut key: Vec<u8> = vec![];
let key_len = match cipher {
Cipher::ChaCha20 => 32,
Cipher::Aes256Gcm => 32,
};
key.resize(key_len, 0);
OsRng::new()?.fill_bytes(&mut key);
let key_store = KeyStore {
key: key.clone(),
hash: crypto_util::hash(key.as_slice()),
};
let mut encryptor = crypto_util::create_encryptor(OpenOptions::new().read(true).write(true).create(true).open(path.clone())?,
cipher, &derived_key);
encryptor.write_all(&key)?;
encryptor.finish()?;

return Ok(key);
bincode::serialize_into(&mut encryptor, &key_store)?;
Ok(key)
}
let mut decryptor = crypto_util::create_decryptor(File::open(path)?, cipher, &derived_key);
let mut key: Vec<u8> = vec![];
decryptor.read_to_end(&mut key)?;
decryptor.finish();
Ok(key)
}
7 changes: 6 additions & 1 deletion src/encryptedfs/crypto_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use rand::Rng;
use std::io::{Read, Write};
use base64::decode;
use std::io;
use openssl::sha::sha256;
use crate::encryptedfs::Cipher;

pub fn create_encryptor(mut file: File, cipher: &Cipher, key: &Vec<u8>) -> write::Encryptor<File> {
Expand Down Expand Up @@ -87,9 +88,13 @@ pub fn normalize_end_encrypt_file_name(name: &str, cipher: &Cipher, key: &Vec<u8
normalized_name
}

pub fn hash(data: &[u8]) -> [u8; 32] {
sha256(data)
}

fn get_cipher(cipher: &Cipher) -> openssl::symm::Cipher {
match cipher {
Cipher::ChaCha20 => openssl::symm::Cipher::chacha20(),
Cipher::Aes256Gcm => openssl::symm::Cipher::aes_256_gcm(),
}
}
}
Loading

0 comments on commit 389124e

Please sign in to comment.