Add GocryptFs to simplify usage
This commit is contained in:
parent
c4d1c479aa
commit
b4ea1ed372
|
@ -1,3 +1,4 @@
|
|||
/target
|
||||
*.py
|
||||
enc
|
||||
mount
|
|
@ -507,6 +507,7 @@ dependencies = [
|
|||
"hkdf",
|
||||
"scrypt",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"thiserror",
|
||||
]
|
||||
|
|
|
@ -16,3 +16,4 @@ sha2 = "0.10"
|
|||
cipher = "0.4"
|
||||
eme-mode = "0.3"
|
||||
thiserror = "1.0"
|
||||
serde_json = "1.0"
|
|
@ -1,3 +1,5 @@
|
|||
//! Utilities to read gocryptfs config.
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use aes_gcm::{
|
||||
|
@ -7,8 +9,9 @@ use aes_gcm::{
|
|||
};
|
||||
use hkdf::Hkdf;
|
||||
|
||||
use crate::error::{Result, FilenameDecryptError, ScryptError};
|
||||
use crate::error::{FilenameDecryptError, Result, ScryptError};
|
||||
|
||||
/// An enum that contain all the feature flag a gocryptfs config can have.
|
||||
#[derive(serde::Deserialize, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum FeatureFlag {
|
||||
/// FlagPlaintextNames indicates that filenames are unencrypted.
|
||||
|
@ -53,7 +56,7 @@ pub struct CryptConf {
|
|||
#[serde(rename = "EncryptedKey")]
|
||||
encrypted_key: String,
|
||||
#[serde(rename = "ScryptObject")]
|
||||
pub scrypt_object: ScryptObject,
|
||||
scrypt_object: ScryptObject,
|
||||
#[serde(rename = "Version")]
|
||||
version: u8,
|
||||
#[serde(rename = "FeatureFlags")]
|
||||
|
@ -61,7 +64,12 @@ pub struct CryptConf {
|
|||
}
|
||||
|
||||
impl CryptConf {
|
||||
pub fn get_master_key(&self, password: &[u8]) -> Result<Vec<u8>> {
|
||||
/// Get the masterkey from configuration.
|
||||
///
|
||||
/// See gocryptfs documentation about [master key](https://nuetzlich.net/gocryptfs/forward_mode_crypto/#master-key-storage).
|
||||
///
|
||||
/// ![TODO NAME THIS IMAGE](https://nuetzlich.net/gocryptfs/img/master-key.svg)
|
||||
pub fn get_master_key(&self, password: &[u8]) -> Result<[u8; 32]> {
|
||||
let block = base64::decode(&self.encrypted_key)?;
|
||||
let key = self.scrypt_object.get_hkdf_key(password)?;
|
||||
|
||||
|
@ -69,7 +77,7 @@ impl CryptConf {
|
|||
let tag = &block[block.len() - 16..];
|
||||
let ciphertext = &block[16..block.len() - 16];
|
||||
|
||||
let mut buf = Vec::from(ciphertext);
|
||||
let mut buf: [u8; 32] = ciphertext.try_into().expect("TODO");
|
||||
|
||||
let aes = AesGcm::<Aes256, cipher::consts::U16>::new(Key::from_slice(&key));
|
||||
|
||||
|
@ -86,6 +94,28 @@ impl CryptConf {
|
|||
pub fn have_feature_flag(&self, flag: &FeatureFlag) -> bool {
|
||||
self.feature_flags.contains(flag)
|
||||
}
|
||||
|
||||
/// Get the gocryptfs encrypted directory creator.
|
||||
pub fn creator(&self) -> &str {
|
||||
self.creator.as_ref()
|
||||
}
|
||||
|
||||
/// Get the gocryptfs.conf encrypted key.
|
||||
pub fn encrypted_key(&self) -> &str {
|
||||
self.encrypted_key.as_ref()
|
||||
}
|
||||
|
||||
pub fn scrypt_object(&self) -> &ScryptObject {
|
||||
&self.scrypt_object
|
||||
}
|
||||
|
||||
pub fn version(&self) -> u8 {
|
||||
self.version
|
||||
}
|
||||
|
||||
pub fn feature_flags(&self) -> &HashSet<FeatureFlag> {
|
||||
&self.feature_flags
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
|
@ -103,17 +133,19 @@ pub struct ScryptObject {
|
|||
}
|
||||
|
||||
impl ScryptObject {
|
||||
pub fn get_hkdf_key(&self, password: &[u8]) -> std::result::Result<Vec<u8>, FilenameDecryptError> {
|
||||
fn get_hkdf_key(&self, password: &[u8]) -> std::result::Result<Vec<u8>, FilenameDecryptError> {
|
||||
let mut key = [0u8; 32];
|
||||
|
||||
let params = scrypt::Params::new((self.n as f64).log2() as u8, self.r, self.p).map_err(|e| ScryptError::from(e))?;
|
||||
let params = scrypt::Params::new((self.n as f64).log2() as u8, self.r, self.p)
|
||||
.map_err(|e| ScryptError::from(e))?;
|
||||
|
||||
scrypt::scrypt(
|
||||
password,
|
||||
&base64::decode(&self.salt).unwrap(),
|
||||
¶ms,
|
||||
&mut key,
|
||||
).map_err(|e| ScryptError::from(e))?;
|
||||
)
|
||||
.map_err(|e| ScryptError::from(e))?;
|
||||
|
||||
let hdkf = Hkdf::<sha2::Sha256>::new(None, &key);
|
||||
hdkf.expand(b"AES-GCM file content encryption", &mut key)?;
|
||||
|
|
|
@ -1,105 +0,0 @@
|
|||
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>;
|
||||
|
||||
// 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(&self, name: &str) -> Result<String, FilenameDecryptError> {
|
||||
let cipher = EmeCipher::new(self.filename_key, self.iv);
|
||||
|
||||
let mut filename = base64::decode_config(name, 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<String, 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
|
||||
|
||||
Ok(base64::encode_config(
|
||||
filename_encrypted,
|
||||
base64::URL_SAFE_NO_PAD,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
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, "vTBajRt-yCpxB7Sly0E7lQ");
|
||||
}
|
||||
|
||||
#[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");
|
||||
}
|
||||
}
|
|
@ -1,4 +1,55 @@
|
|||
//! A library to write gocryptfs compatible programs
|
||||
|
||||
use std::{fs::File, path::Path};
|
||||
|
||||
use content_enc::ContentEnc;
|
||||
use filename::FilenameDecoder;
|
||||
|
||||
pub mod config;
|
||||
pub mod content_enc;
|
||||
pub mod filename;
|
||||
pub mod error;
|
||||
pub mod filename;
|
||||
|
||||
/// A GocryptFs encrypted directory
|
||||
pub struct GocryptFs {
|
||||
filename_decoder: FilenameDecoder,
|
||||
content_decoder: ContentEnc,
|
||||
}
|
||||
|
||||
impl GocryptFs {
|
||||
/// Open an existing gocryptfs directory
|
||||
///
|
||||
/// The directory must contain a valid `gocryptfs.conf`
|
||||
pub fn open<P>(encrypted_dir_path: P, password: &str) -> error::Result<Self>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let base_path = encrypted_dir_path.as_ref();
|
||||
|
||||
let config = {
|
||||
let mut config_file =
|
||||
File::open(base_path.join("gocryptfs.conf")).expect("failed to get config");
|
||||
|
||||
serde_json::from_reader::<_, config::CryptConf>(&mut config_file)
|
||||
.expect("failed to parse config")
|
||||
};
|
||||
|
||||
let master_key = config.get_master_key(password.as_bytes())?;
|
||||
|
||||
let filename_decoder = FilenameDecoder::new(&master_key)?;
|
||||
let content_decoder = ContentEnc::new(&master_key, 16); // TODO IV LEN
|
||||
|
||||
Ok(Self {
|
||||
filename_decoder,
|
||||
content_decoder,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn filename_decoder(&self) -> &FilenameDecoder {
|
||||
&self.filename_decoder
|
||||
}
|
||||
|
||||
pub fn content_decoder(&self) -> &ContentEnc {
|
||||
&self.content_decoder
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,9 +21,9 @@ pub(crate) struct DecryptCommand {
|
|||
/// The file to decrypt
|
||||
pub(crate) file_path : String,
|
||||
|
||||
/// Path to the gocryptfs.conf
|
||||
#[clap(short('c'), long)]
|
||||
pub(crate) gocryptfs_conf_path : Option<String>,
|
||||
/// Path to the gocryptfs directory
|
||||
#[clap(short('g'), long)]
|
||||
pub(crate) gocryptfs_path : Option<String>,
|
||||
|
||||
/// The password
|
||||
#[clap(short, long)]
|
||||
|
@ -35,9 +35,9 @@ pub(crate) struct LsCommand {
|
|||
/// The directory
|
||||
pub(crate) folder_path : String,
|
||||
|
||||
/// Path to the gocryptfs.conf
|
||||
#[clap(short('c'), long)]
|
||||
pub(crate) gocryptfs_conf_path : Option<String>,
|
||||
/// Path to the gocryptfs directory
|
||||
#[clap(short('g'), long)]
|
||||
pub(crate) gocryptfs_path : Option<String>,
|
||||
|
||||
/// The password
|
||||
#[clap(short, long)]
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
use std::{
|
||||
fs::{self, File},
|
||||
fs::File,
|
||||
io::{BufWriter, Read, Write},
|
||||
path::{Path, PathBuf},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
use clap::Parser;
|
||||
|
||||
use args::{DecryptCommand, LsCommand};
|
||||
use rustcryptfs_lib::{config::{self, CryptConf}, filename::FilenameDecoder, content_enc::ContentEnc};
|
||||
use rustcryptfs_lib::GocryptFs;
|
||||
|
||||
mod args;
|
||||
|
||||
|
@ -25,20 +24,16 @@ fn main() -> anyhow::Result<()> {
|
|||
|
||||
fn ls(c: &LsCommand) -> anyhow::Result<()> {
|
||||
let folder_path = Path::new(&c.folder_path);
|
||||
let config_path = c
|
||||
.gocryptfs_conf_path
|
||||
.as_ref()
|
||||
.map(|p| PathBuf::from(p))
|
||||
.unwrap_or_else(|| folder_path.join("gocryptfs.conf"));
|
||||
|
||||
let content = fs::read_to_string(config_path)?;
|
||||
let fs = GocryptFs::open(
|
||||
c.gocryptfs_path
|
||||
.as_ref()
|
||||
.map(|p| Path::new(p))
|
||||
.unwrap_or(folder_path),
|
||||
c.password.as_ref().expect("Please input a password"),
|
||||
)?;
|
||||
|
||||
let conf: CryptConf =
|
||||
serde_json::from_str(&content).context("Failed to decode configuration")?;
|
||||
|
||||
let master_key = conf.get_master_key(c.password.as_ref().unwrap().as_bytes()).context("Failed to get master key")?;
|
||||
|
||||
let filename_decoder = FilenameDecoder::new(&master_key)?;
|
||||
let filename_decoder = fs.filename_decoder();
|
||||
|
||||
let iv = std::fs::read(folder_path.join("gocryptfs.diriv"))?;
|
||||
|
||||
|
@ -48,20 +43,17 @@ fn ls(c: &LsCommand) -> anyhow::Result<()> {
|
|||
let filename = dir.file_name();
|
||||
let filename = filename.to_str().unwrap();
|
||||
|
||||
if filename != "."
|
||||
&& filename != ".."
|
||||
&& filename != "gocryptfs.conf"
|
||||
&& filename != "gocryptfs.diriv"
|
||||
{
|
||||
if filename != "gocryptfs.conf" && filename != "gocryptfs.diriv" {
|
||||
if filename.starts_with("gocryptfs.longname.") {
|
||||
if !filename.ends_with(".name") {
|
||||
let filename = std::fs::read_to_string(folder_path.join(format!("{}.name", filename)))?;
|
||||
if let Ok(res) = dir_decoder.decode_filename(&filename) {
|
||||
let filename =
|
||||
std::fs::read_to_string(folder_path.join(format!("{}.name", filename)))?;
|
||||
if let Ok(res) = dir_decoder.decode_filename(filename) {
|
||||
println!("{}", res);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let Ok(res) = dir_decoder.decode_filename(&filename) {
|
||||
if let Ok(res) = dir_decoder.decode_filename(filename) {
|
||||
println!("{}", res);
|
||||
}
|
||||
};
|
||||
|
@ -73,22 +65,17 @@ fn ls(c: &LsCommand) -> anyhow::Result<()> {
|
|||
|
||||
fn decrypt_file(c: &DecryptCommand) -> anyhow::Result<()> {
|
||||
let file_path = Path::new(&c.file_path);
|
||||
let config_path = c
|
||||
.gocryptfs_conf_path
|
||||
.as_ref()
|
||||
.map(|p| PathBuf::from(p))
|
||||
.unwrap_or_else(|| file_path.parent().unwrap().join("gocryptfs.conf"));
|
||||
|
||||
let content = fs::read_to_string(config_path)?;
|
||||
|
||||
let conf: config::CryptConf =
|
||||
serde_json::from_str(&content).context("Failed to decode configuration")?;
|
||||
let fs = GocryptFs::open(
|
||||
c.gocryptfs_path
|
||||
.as_ref()
|
||||
.map(|p| Path::new(p))
|
||||
.unwrap_or_else(|| file_path.parent().unwrap()),
|
||||
c.password.as_ref().expect("Please input a password"),
|
||||
)?;
|
||||
|
||||
let mut file = File::open(file_path).unwrap();
|
||||
|
||||
let master_key = conf.get_master_key(c.password.as_ref().unwrap().as_bytes())?;
|
||||
|
||||
let enc = ContentEnc::new(&master_key, 16);
|
||||
let enc = fs.content_decoder();
|
||||
|
||||
let mut buf = [0u8; 18];
|
||||
let n = file.read(&mut buf)?;
|
||||
|
|
Loading…
Reference in New Issue