From cdfddc6eaebf3031c6c3953cb9b4b523d1d940c3 Mon Sep 17 00:00:00 2001 From: oupson Date: Thu, 11 Aug 2022 15:54:13 +0200 Subject: [PATCH] Add missing filename --- .../src/filename/filename_encoded.rs | 76 +++++++++ rustcryptfs-lib/src/filename/mod.rs | 119 ++++++++++++++ rustcryptfs-lib/src/io/mod.rs | 155 ++++++++++++++++++ 3 files changed, 350 insertions(+) create mode 100644 rustcryptfs-lib/src/filename/filename_encoded.rs create mode 100644 rustcryptfs-lib/src/filename/mod.rs create mode 100644 rustcryptfs-lib/src/io/mod.rs diff --git a/rustcryptfs-lib/src/filename/filename_encoded.rs b/rustcryptfs-lib/src/filename/filename_encoded.rs new file mode 100644 index 0000000..2edc65e --- /dev/null +++ b/rustcryptfs-lib/src/filename/filename_encoded.rs @@ -0,0 +1,76 @@ +use std::path::Path; + +/// EncodedFilename +#[derive(Debug, PartialEq)] +pub enum EncodedFilename { + ShortFilename(String), + LongFilename(LongFilename), +} + +impl EncodedFilename { + fn new

(file: P) -> crate::error::Result + where + P: AsRef, + { + let path = file.as_ref(); + + let filename = path.file_name().unwrap().to_str().expect("Failed to get filename"); + + if filename.starts_with("gocryptfs.longname.") { + if !filename.ends_with(".name") { + let long = std::fs::read_to_string( + path.parent().unwrap().join(format!("{}.name", filename)), + ).unwrap(); + Ok(EncodedFilename::LongFilename(LongFilename { + filename: filename.to_string(), + filename_content: long, + })) + } else { + panic!() + } + } else { + Ok(EncodedFilename::ShortFilename(filename.to_string())) + } + } +} + +#[derive(Debug, PartialEq)] +pub struct LongFilename { + pub filename: String, + pub filename_content: String, +} + +impl From for EncodedFilename { + fn from(filename: String) -> Self { + if filename.len() > 255 { + unimplemented!() + } else { + Self::ShortFilename(filename) + } + } +} + +pub trait IntoDecodable { + fn to_decodable<'s>(&'s self) -> &'s str; +} + +impl IntoDecodable for EncodedFilename { + fn to_decodable<'s>(&'s self) -> &'s str { + match self { + Self::ShortFilename(s) => s.as_str(), + Self::LongFilename(l) => l.filename_content.as_str(), + } + } +} + +impl IntoDecodable for String { + fn to_decodable<'s>(&'s self) -> &'s str { + self + } +} + +impl<'a> IntoDecodable for &'a str { + fn to_decodable<'s>(&'s self) -> &'s str { + self + } +} diff --git a/rustcryptfs-lib/src/filename/mod.rs b/rustcryptfs-lib/src/filename/mod.rs new file mode 100644 index 0000000..3492a13 --- /dev/null +++ b/rustcryptfs-lib/src/filename/mod.rs @@ -0,0 +1,119 @@ +use aes::Aes256; +use cipher::{block_padding::Pkcs7, inout::InOutBufReserved, Iv, Key, KeyIvInit}; +use eme_mode::DynamicEme; +use hkdf::Hkdf; + +use crate::error::FilenameDecryptError; + +pub(crate) type EmeCipher = DynamicEme; + +mod filename_encoded; + +pub use filename_encoded::*; + +// TODO RENAME +pub struct FilenameDecoder { + filename_key: Key, +} + +impl FilenameDecoder { + pub fn new(master_key: &[u8]) -> Result { + let mut key = [0u8; 32]; + let hdkf = Hkdf::::new(None, &master_key); + hdkf.expand(b"EME filename encryption", &mut key)?; + + Ok(Self { + filename_key: Key::::from(key), + }) + } + + pub fn get_decoder_for_dir<'a, 'b>(&'a self, iv: &'b [u8]) -> DirFilenameDecoder<'a, 'b> { + let iv = Iv::::from_slice(iv); + DirFilenameDecoder { + filename_key: &self.filename_key, + iv, + } + } +} + +// TODO RENAME +pub struct DirFilenameDecoder<'a, 'b> { + filename_key: &'a Key, + iv: &'b Iv, +} + +impl<'a, 'b> DirFilenameDecoder<'a, 'b> { + pub fn decode_filename(&self, name: S) -> Result + where + S: IntoDecodable, + { + let cipher = EmeCipher::new(self.filename_key, self.iv); + + let mut filename = base64::decode_config(name.to_decodable(), base64::URL_SAFE_NO_PAD)?; + let filename_decoded = cipher + .decrypt_padded_mut::(&mut filename) + .map_err(|_| FilenameDecryptError::DecryptError())?; + + Ok(String::from_utf8_lossy(filename_decoded).to_string()) + } + + pub fn encrypt_filename( + &self, + plain_text_name: &str, + ) -> Result { + let mut cipher = EmeCipher::new(self.filename_key, self.iv); + let mut res = [0u8; 2048]; + + let filename_encrypted = cipher + .encrypt_padded_inout_mut::( + InOutBufReserved::from_slices(plain_text_name.as_bytes(), &mut res).unwrap(), + ) + .map_err(|_| FilenameDecryptError::DecryptError())?; // TODO RENAME ERROR + + // TODO LONG FILENAME + + let filename = base64::encode_config(filename_encrypted, base64::URL_SAFE_NO_PAD); + + Ok(filename.into()) + } +} + +#[cfg(test)] +mod test { + use crate::filename::EncodedFilename; + + use super::FilenameDecoder; + + #[test] + fn test_encrypt() { + let master_key = base64::decode("9gtUW9XiiefEgEXEkbONI6rnUsd2yh5UZZLG0V8Bxgk=").unwrap(); + let dir_iv = base64::decode("6ysCeWOp2euF1x39gth8KQ==").unwrap(); + + let decoder = FilenameDecoder::new(&master_key).expect("Failed to get file decoder"); + let dir_decoder = decoder.get_decoder_for_dir(&dir_iv); + + let encoded = dir_decoder + .encrypt_filename("7.mp4") + .expect("Failed to encrypt filename"); + + assert_eq!( + encoded, + EncodedFilename::ShortFilename("vTBajRt-yCpxB7Sly0E7lQ".into()) + ); + } + + #[test] + fn test_decrypt() { + let master_key = base64::decode("9gtUW9XiiefEgEXEkbONI6rnUsd2yh5UZZLG0V8Bxgk=").unwrap(); + let dir_iv = base64::decode("6ysCeWOp2euF1x39gth8KQ==").unwrap(); + + let decoder = FilenameDecoder::new(&master_key).expect("Failed to get file decoder"); + let dir_decoder = decoder.get_decoder_for_dir(&dir_iv); + + let decrypted = dir_decoder + .decode_filename("vTBajRt-yCpxB7Sly0E7lQ") + .expect("Failed to decrypt filename"); + + assert_eq!(decrypted, "7.mp4"); + } +} diff --git a/rustcryptfs-lib/src/io/mod.rs b/rustcryptfs-lib/src/io/mod.rs new file mode 100644 index 0000000..5852394 --- /dev/null +++ b/rustcryptfs-lib/src/io/mod.rs @@ -0,0 +1,155 @@ +use std::{ + error::Error, + fs::File, + io::Read, + path::{Path, PathBuf}, +}; + +use crate::filename::{EncodedFilename, FilenameDecoder}; + +#[derive(Debug)] +pub struct DirCache { + filename: String, + dir_iv: [u8; 16], + dir_entries: Vec, +} + +impl DirCache { + pub fn load_from_path

(path: P) -> Self + where + P: AsRef, + { + let path = path.as_ref(); + + let mut dir_iv = [0u8; 16]; + { + let dir_iv_path = path.join("gocryptfs.diriv"); + + let mut file = File::open(dir_iv_path).unwrap(); + + file.read_exact(&mut dir_iv).unwrap(); + } + + let dir_entries = path + .read_dir() + .unwrap() + .filter_map(|f| { + if let Ok(entry) = f { + if entry.file_name() != "gocryptfs.conf" + && entry.file_name() != "gocryptfs.diriv" + { + Some(entry) + } else { + None + } + } else { + None + } + }) + .map(|f| DirEntry::try_from(f.path().as_path())) + .filter_map(|f| f.ok()) + .collect(); + + Self { + filename: path.to_string_lossy().to_string(), + dir_iv, + dir_entries, + } + } + + pub fn lookup

( + &self, + filename_decoder: &FilenameDecoder, + decrypted_path: P, + ) -> Option + where + P: AsRef, + { + let decrypted_path = decrypted_path.as_ref(); + + let mut components = decrypted_path.components(); + + let component = components.next().expect("lol"); + + let decoder = filename_decoder.get_decoder_for_dir(&self.dir_iv); + + let segment = decoder + .encrypt_filename(component.as_os_str().to_str().unwrap()) + .expect("lol"); + + let segment_path = match segment { + EncodedFilename::ShortFilename(filename) => PathBuf::from(filename), + EncodedFilename::LongFilename(long_filename) => PathBuf::from(long_filename.filename), + }; + + if segment_path.is_dir() { + let (size, _) = components.size_hint(); + + if size > 0 { + unimplemented!() + } else { + unimplemented!() + //None + } + } else { + // component.as_path() + unimplemented!() + }; + + } + + fn lookup_internal

( + &self, + filename_decoder: &FilenameDecoder, + decrypted_path: P, + dir: &DirCache, + ) -> Option + where + P: AsRef, + { + unimplemented!() + } +} + +#[derive(Debug)] +pub enum DirEntry { + Dir(DirCache), + File(String), +} + +impl DirEntry { + /// Returns `true` if the dir entry is [`Dir`]. + /// + /// [`Dir`]: DirEntry::Dir + #[must_use] + pub fn is_dir(&self) -> bool { + matches!(self, Self::Dir(..)) + } + + /// Returns `true` if the dir entry is [`File`]. + /// + /// [`File`]: DirEntry::File + #[must_use] + pub fn is_file(&self) -> bool { + matches!(self, Self::File(..)) + } +} + +impl TryFrom<&Path> for DirEntry { + type Error = Box; // TODO + + fn try_from(path: &Path) -> Result { + Ok(if path.is_dir() { + DirEntry::Dir(DirCache::load_from_path(path)) + } else { + DirEntry::File( + path.components() + .last() + .unwrap() + .as_os_str() + .to_string_lossy() + .to_string(), + ) + }) + } +}