Add GocryptFs to simplify usage

This commit is contained in:
oupson 2022-08-11 15:41:42 +02:00
parent c4d1c479aa
commit b4ea1ed372
Signed by: oupson
GPG Key ID: 3BD88615552EFCB7
8 changed files with 125 additions and 157 deletions

3
.gitignore vendored
View File

@ -1,3 +1,4 @@
/target
*.py
enc
enc
mount

1
Cargo.lock generated
View File

@ -507,6 +507,7 @@ dependencies = [
"hkdf",
"scrypt",
"serde",
"serde_json",
"sha2",
"thiserror",
]

View File

@ -16,3 +16,4 @@ sha2 = "0.10"
cipher = "0.4"
eme-mode = "0.3"
thiserror = "1.0"
serde_json = "1.0"

View File

@ -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(),
&params,
&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)?;

View File

@ -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");
}
}

View File

@ -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 error;
pub mod filename;
pub mod error;
/// 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
}
}

View File

@ -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)]

View File

@ -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)?;