Skip to content

Commit

Permalink
keep pass in keyring while executing program, delete it on exit
Browse files Browse the repository at this point in the history
get pass from keyring when is needed to refresh the encryption key
  • Loading branch information
radumarias committed May 1, 2024
1 parent d5658e8 commit eda0c62
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 41 deletions.
15 changes: 11 additions & 4 deletions src/encryptedfs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -559,16 +559,21 @@ struct WriteHandleContext {

struct KeyProvider {
path: PathBuf,
password: SecretString,
password_provider: Box<dyn PasswordProvider>,
cipher: Cipher,
}

impl expire_value::Provider<SecretVec<u8>, FsError> for KeyProvider {
fn provide(&self) -> Result<SecretVec<u8>, FsError> {
EncryptedFs::read_or_create_key(&self.path, &self.password, &self.cipher)
let password = self.password_provider.get_password().ok_or(FsError::InvalidPassword)?;
EncryptedFs::read_or_create_key(&self.path, &password, &self.cipher)
}
}

pub trait PasswordProvider: Send + Sync + 'static {
fn get_password(&self) -> Option<SecretString>;
}

/// Encrypted FS that stores encrypted files in a dedicated directory with a specific structure based on `inode`.
pub struct EncryptedFs {
pub(crate) data_dir: PathBuf,
Expand All @@ -595,16 +600,18 @@ const BUF_SIZE: usize = 256 * 1024;
const BUF_SIZE: usize = 1024 * 1024; // 1 MB buffer

impl EncryptedFs {
pub async fn new(data_dir: &str, password: SecretString, cipher: Cipher) -> FsResult<Self> {
pub async fn new(data_dir: &str, password_provider: Box<dyn PasswordProvider>, cipher: Cipher) -> FsResult<Self> {
let path = PathBuf::from(&data_dir);

let password = password_provider.get_password().ok_or(FsError::InvalidPassword)?;

ensure_structure_created(&path.clone()).await?;
check_password(&path, &password, &cipher)?;

let key_provider = KeyProvider {
path: path.join(SECURITY_DIR).join(KEY_ENC_FILENAME),
// todo: read pass from pass provider field
password: SecretString::new(password.expose_secret().to_owned()),
password_provider,
cipher: cipher.clone(),
};

Expand Down
8 changes: 4 additions & 4 deletions src/encryptedfs_fuse3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use libc::{EACCES, EBADF, EEXIST, EIO, ENAMETOOLONG, ENOENT, ENOTDIR, ENOTEMPTY,
use secrecy::{ExposeSecret, SecretString};
use tracing::{debug, error, instrument, trace, warn};

use crate::encryptedfs::{EncryptedFs, Cipher, FileAttr, FileType, FsError, FsResult, SetFileAttr, CreateFileAttr};
use crate::encryptedfs::{EncryptedFs, Cipher, FileAttr, FileType, FsError, FsResult, SetFileAttr, CreateFileAttr, PasswordProvider};

const TTL: Duration = Duration::from_secs(1);
const STATFS: ReplyStatFs = ReplyStatFs {
Expand Down Expand Up @@ -120,18 +120,18 @@ pub struct EncryptedFsFuse3 {
}

impl EncryptedFsFuse3 {
pub async fn new(data_dir: &str, password: SecretString, cipher: Cipher,
pub async fn new(data_dir: &str, password_provider: Box<dyn PasswordProvider>, cipher: Cipher,
direct_io: bool, _suid_support: bool) -> FsResult<Self> {
#[cfg(feature = "abi-7-26")] {
Ok(Self {
fs: Arc::new(EncryptedFs::new(data_dir, password, cipher).await?),
fs: Arc::new(EncryptedFs::new(data_dir, password_provider, cipher).await?),
direct_io,
suid_support: _suid_support,
})
}
#[cfg(not(feature = "abi-7-26"))] {
Ok(Self {
fs: Arc::new(EncryptedFs::new(data_dir, password, cipher).await?),
fs: Arc::new(EncryptedFs::new(data_dir, password_provider, cipher).await?),
direct_io,
suid_support: false,
})
Expand Down
20 changes: 20 additions & 0 deletions src/keyring.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use keyring::Entry;
use secrecy::{ExposeSecret, SecretString};

const KEYRING_SERVICE: &'static str = "rencfs";
const KEYRING_USER: &'static str = "encrypted_fs";

pub(crate) fn save(password: SecretString, suffix: &str) -> Result<(), keyring::Error> {
let entry = Entry::new(KEYRING_SERVICE, &format!("{KEYRING_USER}.{suffix}"))?;
entry.set_password(password.expose_secret())
}

pub(crate) fn delete(suffix: &str) -> Result<(), keyring::Error> {
let entry = Entry::new(KEYRING_SERVICE, &format!("{KEYRING_USER}.{suffix}"))?;
entry.delete_password()
}

pub(crate) fn get(suffix: &str) -> Result<SecretString, keyring::Error> {
let entry = Entry::new(KEYRING_SERVICE, &format!("{KEYRING_USER}.{suffix}"))?;
Ok(SecretString::new(entry.get_password()?))
}
28 changes: 4 additions & 24 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,11 +180,9 @@ use tracing_appender::non_blocking::WorkerGuard;
use fuse3::MountOptions;
use std::ffi::OsStr;
use fuse3::raw::Session;
use keyring::Entry;
use secrecy::{ExposeSecret, SecretString};
use tracing::level_filters::LevelFilter;
use tracing_subscriber::EnvFilter;
use crate::encryptedfs::Cipher;
use crate::encryptedfs::{Cipher, PasswordProvider};
use crate::encryptedfs_fuse3::EncryptedFsFuse3;

pub mod encryptedfs;
Expand Down Expand Up @@ -223,8 +221,8 @@ pub fn log_init(level: Level) -> WorkerGuard {
guard
}

#[instrument(skip(password))]
pub async fn run_fuse(mountpoint: &str, data_dir: &str, password: SecretString, cipher: Cipher, allow_root: bool, allow_other: bool, direct_io: bool, suid_support: bool) -> anyhow::Result<()> {
#[instrument(skip(password_provider))]
pub async fn run_fuse(mountpoint: &str, data_dir: &str, password_provider: Box<dyn PasswordProvider>, cipher: Cipher, allow_root: bool, allow_other: bool, direct_io: bool, suid_support: bool) -> anyhow::Result<()> {
let mut mount_options = &mut MountOptions::default();
#[cfg(target_os = "linux")] {
unsafe {
Expand All @@ -242,27 +240,9 @@ pub async fn run_fuse(mountpoint: &str, data_dir: &str, password: SecretString,

info!("Checking password and mounting FUSE filesystem");
Session::new(mount_options)
.mount_with_unprivileged(EncryptedFsFuse3::new(data_dir, password, cipher, direct_io, suid_support).await?, mount_path)
.mount_with_unprivileged(EncryptedFsFuse3::new(data_dir, password_provider, cipher, direct_io, suid_support).await?, mount_path)
.await?
.await?;

Ok(())
}

const KEYRING_SERVICE: &'static str = "rencfs";
const KEYRING_USER: &'static str = "encrypted_fs";

pub(crate) fn save_to_keyring(password: SecretString, suffix: &str) -> Result<(), keyring::Error> {
let entry = Entry::new(KEYRING_SERVICE, &format!("{KEYRING_USER}.{suffix}"))?;
entry.set_password(password.expose_secret())
}

pub(crate) fn delete_from_keyring(suffix: &str) -> Result<(), keyring::Error> {
let entry = Entry::new(KEYRING_SERVICE, &format!("{KEYRING_USER}.{suffix}"))?;
entry.delete_password()
}

pub(crate) fn get_from_keyring(suffix: &str) -> Result<SecretString, keyring::Error> {
let entry = Entry::new(KEYRING_SERVICE, &format!("{KEYRING_USER}.{suffix}"))?;
Ok(SecretString::new(entry.get_password()?))
}
50 changes: 41 additions & 9 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ use anyhow::Result;
use secrecy::{ExposeSecret, SecretString};
use thiserror::Error;

use rencfs::encryptedfs::{Cipher, EncryptedFs, FsError};
use rencfs::encryptedfs::{Cipher, EncryptedFs, FsError, PasswordProvider};
use rencfs::{is_debug, log_init};

mod keyring;

#[derive(Debug, Error)]
enum ExitStatusError {
#[error("exit with status {0}")]
Expand Down Expand Up @@ -267,22 +269,52 @@ async fn run_normal(matches: ArgMatches, data_dir: &String, cipher: Cipher) -> R
}
}
}
// save password in keyring
keyring::save(password.clone(), "password").map_err(|err| {
error!(err = %err);
ExitStatusError::from(ExitStatusError::Failure(1))
})?;

if matches.get_flag("umount-on-start") {
umount(mountpoint.as_str(), false)?;
}

let auto_unmount = matches.get_flag("auto_unmount");
let mountpoint_kill = mountpoint.clone();
// unmount on process kill
if matches.get_flag("auto_unmount") {
let mountpoint_kill = mountpoint.clone();
set_handler(move || {
info!("Received signal to exit");
let _ = umount(mountpoint_kill.as_str(), true).map_err(|err| error!(err = %err));
process::exit(0);
}).unwrap();
set_handler(move || {
info!("Received signal to exit");
let mut status: Option<ExitStatusError> = None;

if auto_unmount {
info!("Unmounting {}", mountpoint_kill);
}
umount(mountpoint_kill.as_str(), true).map_err(|err| {
error!(err = %err);
status.replace(ExitStatusError::Failure(1));
}).ok();

info!("Delete key from keyring");
keyring::delete("password").map_err(|err| {
error!(err = %err);
status.replace(ExitStatusError::Failure(1));
}).ok();

process::exit(status.map_or(0, |x| match x { ExitStatusError::Failure(status) => status }));
}).unwrap();

struct PasswordProviderImpl {}

impl PasswordProvider for PasswordProviderImpl {
fn get_password(&self) -> Option<SecretString> {
keyring::get("password").map_err(|err| {
error!(err = %err, "cannot get password from keyring");
err
}).ok()
}
}

rencfs::run_fuse(&mountpoint, &data_dir, password, cipher,
rencfs::run_fuse(&mountpoint, &data_dir, Box::new(PasswordProviderImpl {}), cipher,
matches.get_flag("allow-root"), matches.get_flag("allow-other"),
matches.get_flag("direct-io"), matches.get_flag("suid")).await
}
Expand Down

0 comments on commit eda0c62

Please sign in to comment.