Add missing filename
This commit is contained in:
parent
b4ea1ed372
commit
cdfddc6eae
|
@ -0,0 +1,76 @@
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
/// EncodedFilename
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum EncodedFilename {
|
||||||
|
ShortFilename(String),
|
||||||
|
LongFilename(LongFilename),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EncodedFilename {
|
||||||
|
fn new<P>(file: P) -> crate::error::Result<Self>
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
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<String> 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Aes256>;
|
||||||
|
|
||||||
|
mod filename_encoded;
|
||||||
|
|
||||||
|
pub use filename_encoded::*;
|
||||||
|
|
||||||
|
// TODO RENAME
|
||||||
|
pub struct FilenameDecoder {
|
||||||
|
filename_key: Key<Aes256>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FilenameDecoder {
|
||||||
|
pub fn new(master_key: &[u8]) -> Result<Self, FilenameDecryptError> {
|
||||||
|
let mut key = [0u8; 32];
|
||||||
|
let hdkf = Hkdf::<sha2::Sha256>::new(None, &master_key);
|
||||||
|
hdkf.expand(b"EME filename encryption", &mut key)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
filename_key: Key::<EmeCipher>::from(key),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_decoder_for_dir<'a, 'b>(&'a self, iv: &'b [u8]) -> DirFilenameDecoder<'a, 'b> {
|
||||||
|
let iv = Iv::<EmeCipher>::from_slice(iv);
|
||||||
|
DirFilenameDecoder {
|
||||||
|
filename_key: &self.filename_key,
|
||||||
|
iv,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO RENAME
|
||||||
|
pub struct DirFilenameDecoder<'a, 'b> {
|
||||||
|
filename_key: &'a Key<EmeCipher>,
|
||||||
|
iv: &'b Iv<EmeCipher>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> DirFilenameDecoder<'a, 'b> {
|
||||||
|
pub fn decode_filename<S>(&self, name: S) -> Result<String, FilenameDecryptError>
|
||||||
|
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::<Pkcs7>(&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<EncodedFilename, FilenameDecryptError> {
|
||||||
|
let mut cipher = EmeCipher::new(self.filename_key, self.iv);
|
||||||
|
let mut res = [0u8; 2048];
|
||||||
|
|
||||||
|
let filename_encrypted = cipher
|
||||||
|
.encrypt_padded_inout_mut::<Pkcs7>(
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<DirEntry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DirCache {
|
||||||
|
pub fn load_from_path<P>(path: P) -> Self
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
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<P>(
|
||||||
|
&self,
|
||||||
|
filename_decoder: &FilenameDecoder,
|
||||||
|
decrypted_path: P,
|
||||||
|
) -> Option<PathBuf>
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
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<P>(
|
||||||
|
&self,
|
||||||
|
filename_decoder: &FilenameDecoder,
|
||||||
|
decrypted_path: P,
|
||||||
|
dir: &DirCache,
|
||||||
|
) -> Option<PathBuf>
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
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<dyn Error>; // TODO
|
||||||
|
|
||||||
|
fn try_from(path: &Path) -> Result<Self, Self::Error> {
|
||||||
|
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(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue