Skip to content

Commit

Permalink
use StirngSecret for all sensitive strings like file names
Browse files Browse the repository at this point in the history
  • Loading branch information
radumarias committed Apr 27, 2024
1 parent 687efdb commit 6a824fb
Show file tree
Hide file tree
Showing 5 changed files with 373 additions and 346 deletions.
144 changes: 82 additions & 62 deletions src/encryptedfs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use std::fs::{File, OpenOptions, ReadDir};
use std::io::{Read, Write};
use std::os::unix::fs::MetadataExt;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::{Arc, Mutex, RwLock, Weak};
use std::sync::atomic::AtomicU64;
use std::time::SystemTime;
Expand Down Expand Up @@ -143,6 +144,7 @@ pub enum Cipher {
Aes256Gcm,
}

#[derive(Debug)]
struct TimeAndSizeFileAttr {
ino: u64,
atime: SystemTime,
Expand Down Expand Up @@ -177,22 +179,35 @@ impl TimeAndSizeFileAttr {
}
}

#[derive(Debug, PartialEq)]
#[derive(Debug)]
pub struct DirectoryEntry {
pub ino: u64,
pub name: String,
pub name: SecretString,
pub kind: FileType,
}

impl PartialEq for DirectoryEntry {
fn eq(&self, other: &Self) -> bool {
self.ino == other.ino && self.name.expose_secret() == other.name.expose_secret() && self.kind == other.kind
}

}

/// Like [`DirectoryEntry`] but with ['FileAttr'].
#[derive(Debug, PartialEq)]
#[derive(Debug)]
pub struct DirectoryEntryPlus {
pub ino: u64,
pub name: String,
pub name: SecretString,
pub kind: FileType,
pub attr: FileAttr,
}

impl PartialEq for DirectoryEntryPlus {
fn eq(&self, other: &Self) -> bool {
self.ino == other.ino && self.name.expose_secret() == other.name.expose_secret() && self.kind == other.kind && self.attr == other.attr
}
}

pub type FsResult<T> = Result<T, FsError>;

pub struct DirectoryEntryIterator(ReadDir, Cipher, SecretVec<u8>);
Expand All @@ -211,14 +226,16 @@ impl Iterator for DirectoryEntryIterator {
return Some(Err(e.into()));
}
let file = file.unwrap();
let mut name = entry.file_name().to_string_lossy().to_string();
if name == "$." {
name = ".".to_string();
} else if name == "$.." {
name = "..".to_string();
} else {
name = crypto_util::decrypt_and_unnormalize_end_file_name(&name, &self.1, &self.2);
}
let name = entry.file_name().to_string_lossy().to_string();
let name = {
if name == "$." {
SecretString::from_str(".").unwrap()
} else if name == "$.." {
SecretString::from_str("..").unwrap()
} else {
crypto_util::decrypt_and_unnormalize_end_file_name(&name, &self.1, &self.2)
}
};
let res: bincode::Result<(u64, FileType)> = bincode::deserialize_from(crypto_util::create_decryptor(file, &self.1, &self.2));
if let Err(e) = res {
return Some(Err(e.into()));
Expand Down Expand Up @@ -251,14 +268,16 @@ impl Iterator for DirectoryEntryPlusIterator {
return Some(Err(e.into()));
}
let file = file.unwrap();
let mut name = entry.file_name().to_string_lossy().to_string();
if name == "$." {
name = ".".to_string();
} else if name == "$.." {
name = "..".to_string();
} else {
name = crypto_util::decrypt_and_unnormalize_end_file_name(&name, &self.2, &self.3);
}
let name = entry.file_name().to_string_lossy().to_string();
let name = {
if name == "$." {
SecretString::from_str(".").unwrap()
} else if name == "$.." {
SecretString::from_str("..").unwrap()
} else {
crypto_util::decrypt_and_unnormalize_end_file_name(&name, &self.2, &self.3)
}
};
let res: bincode::Result<(u64, FileType)> = bincode::deserialize_from(crypto_util::create_decryptor(file, &self.2, &self.3));
if let Err(e) = res {
error!(err = %e, "deserializing directory entry");
Expand Down Expand Up @@ -347,7 +366,6 @@ impl EncryptedFs {
key: Self::read_or_create_key(path.join(SECURITY_DIR).join(KEY_ENC_FILENAME), password, &cipher)?,
};
let _ = fs.ensure_root_exists();
fs.check_password()?;

Ok(fs)
}
Expand All @@ -369,7 +387,10 @@ impl EncryptedFs {

/// Create a new node in the filesystem
/// You don't need to provide `attr.ino`, it will be auto-generated anyway.
pub fn create_nod(&mut self, parent: u64, name: &str, mut attr: FileAttr, read: bool, write: bool) -> FsResult<(u64, FileAttr)> {
pub fn create_nod(&mut self, parent: u64, name: &SecretString, mut attr: FileAttr, read: bool, write: bool) -> FsResult<(u64, FileAttr)> {
if name.expose_secret() == "." || name.expose_secret() == ".." {
return Err(FsError::InvalidInput("name cannot be '.' or '..'".to_string()));
}
if !self.node_exists(parent) {
return Err(FsError::InodeNotFound);
}
Expand Down Expand Up @@ -400,12 +421,12 @@ impl EncryptedFs {
// add "." and ".." entries
self.insert_directory_entry(attr.ino, DirectoryEntry {
ino: attr.ino,
name: "$.".to_string(),
name: SecretString::from_str("$.").unwrap(),
kind: FileType::Directory,
})?;
self.insert_directory_entry(attr.ino, DirectoryEntry {
ino: parent,
name: "$..".to_string(),
name: SecretString::from_str("$..").unwrap(),
kind: FileType::Directory,
})?;
}
Expand All @@ -414,7 +435,7 @@ impl EncryptedFs {
// edd entry in parent directory, used for listing
self.insert_directory_entry(parent, DirectoryEntry {
ino: attr.ino,
name: name.to_string(),
name: SecretString::new(name.expose_secret().to_owned()),
kind: attr.kind,
})?;

Expand All @@ -438,7 +459,7 @@ impl EncryptedFs {
Ok((handle, attr))
}

pub fn find_by_name(&mut self, parent: u64, mut name: &str) -> FsResult<Option<FileAttr>> {
pub fn find_by_name(&mut self, parent: u64, name: &SecretString) -> FsResult<Option<FileAttr>> {
if !self.node_exists(parent) {
return Err(FsError::InodeNotFound);
}
Expand All @@ -448,12 +469,16 @@ impl EncryptedFs {
if !self.is_dir(parent) {
return Err(FsError::InvalidInodeType);
}
if name == "." {
name = "$.";
} else if name == ".." {
name = "$..";
}
let name = crypto_util::normalize_end_encrypt_file_name(name, &self.cipher, &self.key);
let name = {
if name.expose_secret() == "." {
SecretString::from_str("$.").unwrap()
} else if name.expose_secret() == ".." {
SecretString::from_str("$..").unwrap()
} else {
SecretString::new(name.expose_secret().to_owned())
}
};
let name = crypto_util::normalize_end_encrypt_file_name(&name, &self.cipher, &self.key);
let file = File::open(self.data_dir.join(CONTENTS_DIR).join(parent.to_string()).join(name))?;
let (inode, _): (u64, FileType) = bincode::deserialize_from(crypto_util::create_decryptor(file, &self.cipher, &self.key))?;
Ok(Some(self.get_inode(inode)?))
Expand All @@ -465,7 +490,7 @@ impl EncryptedFs {
Ok(iter.into_iter().count())
}

pub fn remove_dir(&mut self, parent: u64, name: &str) -> FsResult<()> {
pub fn remove_dir(&mut self, parent: u64, name: &SecretString) -> FsResult<()> {
if !self.is_dir(parent) {
return Err(FsError::InvalidInodeType);
}
Expand Down Expand Up @@ -502,7 +527,7 @@ impl EncryptedFs {
Ok(())
}

pub fn remove_file(&mut self, parent: u64, name: &str) -> FsResult<()> {
pub fn remove_file(&mut self, parent: u64, name: &SecretString) -> FsResult<()> {
if !self.is_dir(parent) {
return Err(FsError::InvalidInodeType);
}
Expand All @@ -525,20 +550,24 @@ impl EncryptedFs {
fs::remove_file(self.data_dir.join(CONTENTS_DIR).join(parent.to_string()).join(name))?;

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();
self.write_inode(&parent_attr)?;

Ok(())
}

pub fn exists_by_name(&self, parent: u64, mut name: &str) -> bool {
if name == "." {
name = "$.";
} else if name == ".." {
name = "$..";
}
let name = crypto_util::normalize_end_encrypt_file_name(name, &self.cipher, &self.key);
pub fn exists_by_name(&self, parent: u64, name: &SecretString) -> bool {
let name = {
if name.expose_secret() == "." {
SecretString::from_str("$.").unwrap()
} else if name.expose_secret() == ".." {
SecretString::from_str("$..").unwrap()
} else {
SecretString::new(name.expose_secret().to_owned())
}
};
let name = crypto_util::normalize_end_encrypt_file_name(&name, &self.cipher, &self.key);
self.data_dir.join(CONTENTS_DIR).join(parent.to_string()).join(name).exists()
}

Expand Down Expand Up @@ -1072,7 +1101,7 @@ impl EncryptedFs {
map_guard.entry(ino).or_insert_with(|| Arc::new(RwLock::new(false)))
}

pub fn rename(&mut self, parent: u64, name: &str, new_parent: u64, new_name: &str) -> FsResult<()> {
pub fn rename(&mut self, parent: u64, name: &SecretString, new_parent: u64, new_name: &SecretString) -> FsResult<()> {
if !self.node_exists(parent) {
return Err(FsError::InodeNotFound);
}
Expand All @@ -1089,7 +1118,7 @@ impl EncryptedFs {
return Err(FsError::NotFound("name not found".to_string()));
}

if parent == new_parent && name == new_name {
if parent == new_parent && name.expose_secret() == new_name.expose_secret() {
// no-op
return Ok(());
}
Expand All @@ -1108,7 +1137,7 @@ impl EncryptedFs {
// add to new parent contents
self.insert_directory_entry(new_parent, DirectoryEntry {
ino: attr.ino,
name: new_name.to_string(),
name: SecretString::new(new_name.expose_secret().to_owned()),
kind: attr.kind,
})?;

Expand All @@ -1120,13 +1149,13 @@ impl EncryptedFs {
new_parent_attr.mtime = SystemTime::now();
new_parent_attr.ctime = SystemTime::now();

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

if attr.kind == FileType::Directory {
// add parent link to new directory
self.insert_directory_entry(attr.ino, DirectoryEntry {
ino: new_parent,
name: "$..".to_string(),
name: SecretString::from_str("$..").unwrap(),
kind: FileType::Directory,
})?;
}
Expand Down Expand Up @@ -1156,17 +1185,17 @@ impl EncryptedFs {
}

/// Encrypts a string using internal encryption info.
pub fn encrypt_string(&self, s: &str) -> String {
pub fn encrypt_string(&self, s: &SecretString) -> String {
crypto_util::encrypt_string(s, &self.cipher, &self.key)
}

/// Decrypts a string using internal encryption info.
pub fn decrypt_string(&self, s: &str) -> String {
pub fn decrypt_string(&self, s: &str) -> SecretString {
crypto_util::decrypt_string(s, &self.cipher, &self.key)
}

/// Normalize and encrypt a file name.
pub fn normalize_end_encrypt_file_name(&self, name: &str) -> String {
pub fn normalize_end_encrypt_file_name(&self, name: &SecretString) -> String {
crypto_util::normalize_end_encrypt_file_name(name, &self.cipher, &self.key)
}
/// Change the password of the filesystem used to access the encryption key.
Expand Down Expand Up @@ -1234,15 +1263,6 @@ impl EncryptedFs {
}
}

fn check_password(&mut self) -> FsResult<()> {
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<bool>>) -> FsResult<u64> {
let path = self.data_dir.join(CONTENTS_DIR).join(ino.to_string());
let file = OpenOptions::new().read(true).write(true).open(path)?;
Expand Down Expand Up @@ -1304,7 +1324,7 @@ impl EncryptedFs {
// add "." entry
self.insert_directory_entry(attr.ino, DirectoryEntry {
ino: attr.ino,
name: "$.".to_string(),
name: SecretString::from_str("$.").unwrap(),
kind: FileType::Directory,
})?;
}
Expand All @@ -1329,7 +1349,7 @@ impl EncryptedFs {
Ok(())
}

fn remove_directory_entry(&self, parent: u64, name: &str) -> FsResult<()> {
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_util::normalize_end_encrypt_file_name(name, &self.cipher, &self.key);
fs::remove_file(parent_path.join(name))?;
Expand Down
33 changes: 15 additions & 18 deletions src/encryptedfs/crypto_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,20 @@ pub fn create_decryptor(mut file: File, cipher: &Cipher, key: &SecretVec<u8>) ->
read::Decryptor::new(file, get_cipher(cipher), &key.expose_secret(), &iv).unwrap()
}

pub fn encrypt_string(s: &str, cipher: &Cipher, key: &SecretVec<u8>) -> String {
pub fn encrypt_string(s: &SecretString, cipher: &Cipher, key: &SecretVec<u8>) -> String {
// use the same IV so the same string will be encrypted to the same value
let iv: Vec<_> = decode("dB0Ej+7zWZWTS5JUCldWMg==").unwrap();

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.as_bytes()).unwrap();
encryptor.write_all(s.expose_secret().as_bytes()).unwrap();
cursor = encryptor.finish().unwrap();
base64::encode(&cursor.into_inner())
}

pub fn decrypt_string(s: &str, cipher: &Cipher, key: &SecretVec<u8>) -> String {
// use the same IV so the same string will be encrypted to the same value
pub fn decrypt_string(s: &str, cipher: &Cipher, key: &SecretVec<u8>) -> 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();
Expand All @@ -65,16 +65,12 @@ pub fn decrypt_string(s: &str, cipher: &Cipher, key: &SecretVec<u8>) -> String {
let mut decryptor = read::Decryptor::new(cursor, get_cipher(cipher), &key.expose_secret(), &iv).unwrap();
let mut decrypted = String::new();
decryptor.read_to_string(&mut decrypted).unwrap();
decrypted
SecretString::new(decrypted)
}

pub fn decrypt_and_unnormalize_end_file_name(name: &str, cipher: &Cipher, key: &SecretVec<u8>) -> String {
let mut name = String::from(name);
if name != "$." && name != "$.." {
name = name.replace("|", "/");
name = decrypt_string(&name, cipher, key);
}
name.to_string()
pub fn decrypt_and_unnormalize_end_file_name(name: &str, cipher: &Cipher, key: &SecretVec<u8>) -> SecretString {
let name = String::from(name).replace("|", "/");
decrypt_string(&name, cipher, key)
}

#[instrument(skip(password, salt))]
Expand All @@ -90,13 +86,14 @@ pub fn derive_key(password: &SecretString, cipher: &Cipher, salt: SecretVec<u8>)
Ok(SecretVec::new(dk))
}

pub fn normalize_end_encrypt_file_name(name: &str, cipher: &Cipher, key: &SecretVec<u8>) -> String {
let mut normalized_name = name.replace("/", " ").replace("\\", " ");
if normalized_name != "$." && normalized_name != "$.." {
normalized_name = encrypt_string(&normalized_name, cipher, key);
normalized_name = normalized_name.replace("/", "|");
pub fn normalize_end_encrypt_file_name(name: &SecretString, cipher: &Cipher, key: &SecretVec<u8>) -> String {
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
}
normalized_name
name.expose_secret().to_owned()
}

pub fn hash(data: &[u8]) -> [u8; 32] {
Expand Down
Loading

0 comments on commit 6a824fb

Please sign in to comment.