feat(anti-forensics): implement incremental auto-vacuum, chunk shredding, and plausible deniability

This commit is contained in:
2026-09-08 10:46:24 +02:00
parent 2cb065c8c0
commit 1e6854e9f5
8 changed files with 1386 additions and 270 deletions
+64 -67
View File
@@ -388,12 +388,13 @@ pub struct SanctumFs {
dek: Arc<Zeroizing<[u8; 32]>>,
format_version: u32,
anti_leak: bool,
vault_id: u32,
last_activity: Arc<AtomicU64>,
}
impl SanctumFs {
pub fn new(db: Database, dek: Zeroizing<[u8; 32]>, format_version: u32) -> Self {
Self::with_options(db, dek, format_version, true)
Self::with_vault(db, dek, format_version, true, 0)
}
pub fn with_options(
@@ -401,6 +402,16 @@ impl SanctumFs {
dek: Zeroizing<[u8; 32]>,
format_version: u32,
anti_leak: bool,
) -> Self {
Self::with_vault(db, dek, format_version, anti_leak, 0)
}
pub fn with_vault(
db: Database,
dek: Zeroizing<[u8; 32]>,
format_version: u32,
anti_leak: bool,
vault_id: u32,
) -> Self {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
@@ -411,10 +422,15 @@ impl SanctumFs {
dek: Arc::new(dek),
format_version,
anti_leak,
vault_id,
last_activity: Arc::new(AtomicU64::new(now)),
}
}
pub fn vault_id(&self) -> u32 {
self.vault_id
}
pub fn last_activity(&self) -> Arc<AtomicU64> {
self.last_activity.clone()
}
@@ -442,6 +458,33 @@ impl SanctumFs {
None => ("", trimmed),
}
}
fn resolve_path(&self, path: &str) -> Result<Option<NodeRecord>, FsError> {
self.db
.resolve_path_in_vault(path, self.vault_id, &self.dek)
.map_err(|_| FsError::GeneralFailure)
}
fn list_children(&self, parent_id: i64) -> Result<Vec<NodeRecord>, FsError> {
self.db
.list_children_in_vault(parent_id, self.vault_id, &self.dek)
.map_err(|_| FsError::GeneralFailure)
}
fn create_node(&self, parent_id: i64, name: &str, is_dir: bool) -> Result<NodeRecord, FsError> {
self.db
.create_node_in_vault(self.vault_id, parent_id, name, is_dir, &self.dek)
.map_err(|e| {
error!("Fehler beim Erstellen des Knotens '{}': {e}", name);
FsError::GeneralFailure
})
}
fn rename_node(&self, id: i64, new_parent_id: i64, new_name: &str) -> Result<(), FsError> {
self.db
.rename_node_in_vault(id, new_parent_id, new_name, self.vault_id, &self.dek)
.map_err(|_| FsError::GeneralFailure)
}
}
impl DavFileSystem for SanctumFs {
@@ -473,10 +516,7 @@ impl DavFileSystem for SanctumFs {
self.touch();
debug!("VFS open aufgerufen: path='{}', options={:?}", path_str, options);
let existing_node = self
.db
.resolve_path(&path_str)
.map_err(|_| FsError::GeneralFailure)?;
let existing_node = self.resolve_path(&path_str)?;
let node = match existing_node {
Some(n) => {
@@ -511,21 +551,14 @@ impl DavFileSystem for SanctumFs {
None => {
if options.create || options.create_new {
let parent = self
.db
.resolve_path(parent_path)
.map_err(|_| FsError::GeneralFailure)?
.resolve_path(parent_path)?
.ok_or(FsError::NotFound)?;
if !parent.is_dir {
return Err(FsError::Forbidden);
}
self.db
.create_node(parent.id, file_name, false)
.map_err(|e| {
error!("Fehler beim Erstellen der Datei: {e}");
FsError::GeneralFailure
})?
self.create_node(parent.id, file_name, false)?
} else {
return Err(FsError::NotFound);
}
@@ -552,19 +585,14 @@ impl DavFileSystem for SanctumFs {
self.touch();
let path_str = Self::path_to_str(path);
let node = self
.db
.resolve_path(&path_str)
.map_err(|_| FsError::GeneralFailure)?
.resolve_path(&path_str)?
.ok_or(FsError::NotFound)?;
if !node.is_dir {
return Err(FsError::Forbidden);
}
let children = self
.db
.list_children(node.id)
.map_err(|_| FsError::GeneralFailure)?;
let children = self.list_children(node.id)?;
let entries: Vec<Result<Box<dyn DavDirEntry>, FsError>> = children
.into_iter()
@@ -593,9 +621,7 @@ impl DavFileSystem for SanctumFs {
self.touch();
}
let node = self
.db
.resolve_path(&path_str)
.map_err(|_| FsError::GeneralFailure)?
.resolve_path(&path_str)?
.ok_or(FsError::NotFound)?;
let meta = SanctumMetaData {
@@ -623,28 +649,19 @@ impl DavFileSystem for SanctumFs {
return Err(FsError::Forbidden);
}
if self
.db
.resolve_path(&path_str)
.map_err(|_| FsError::GeneralFailure)?
.is_some()
{
if self.resolve_path(&path_str)?.is_some() {
return Err(FsError::Exists);
}
let parent = self
.db
.resolve_path(parent_path)
.map_err(|_| FsError::GeneralFailure)?
.resolve_path(parent_path)?
.ok_or(FsError::NotFound)?;
if !parent.is_dir {
return Err(FsError::Forbidden);
}
self.db
.create_node(parent.id, dir_name, true)
.map_err(|_| FsError::GeneralFailure)?;
self.create_node(parent.id, dir_name, true)?;
Ok(())
})
@@ -655,15 +672,14 @@ impl DavFileSystem for SanctumFs {
self.touch();
let path_str = Self::path_to_str(path);
let node = self
.db
.resolve_path(&path_str)
.map_err(|_| FsError::GeneralFailure)?
.resolve_path(&path_str)?
.ok_or(FsError::NotFound)?;
if !node.is_dir {
return Err(FsError::Forbidden);
}
if node.id == 1 {
let root_id = Database::get_root_node_id_for_vault(self.vault_id);
if node.id == root_id {
// Root-Verzeichnis darf nicht gelöscht werden
return Err(FsError::Forbidden);
}
@@ -681,9 +697,7 @@ impl DavFileSystem for SanctumFs {
self.touch();
let path_str = Self::path_to_str(path);
let node = self
.db
.resolve_path(&path_str)
.map_err(|_| FsError::GeneralFailure)?
.resolve_path(&path_str)?
.ok_or(FsError::NotFound)?;
if node.is_dir {
@@ -709,9 +723,7 @@ impl DavFileSystem for SanctumFs {
let to_str = Self::path_to_str(to);
let node = self
.db
.resolve_path(&from_str)
.map_err(|_| FsError::GeneralFailure)?
.resolve_path(&from_str)?
.ok_or(FsError::NotFound)?;
let (to_parent_path, to_name) = self.split_parent_and_name(&to_str);
@@ -721,9 +733,7 @@ impl DavFileSystem for SanctumFs {
}
let to_parent = self
.db
.resolve_path(to_parent_path)
.map_err(|_| FsError::GeneralFailure)?
.resolve_path(to_parent_path)?
.ok_or(FsError::NotFound)?;
if !to_parent.is_dir {
@@ -731,11 +741,7 @@ impl DavFileSystem for SanctumFs {
}
// Falls Zieldatei bereits existiert und Datei ist: überschreiben / löschen
if let Some(dest) = self
.db
.resolve_path(&to_str)
.map_err(|_| FsError::GeneralFailure)?
{
if let Some(dest) = self.resolve_path(&to_str)? {
if dest.is_dir {
return Err(FsError::Forbidden);
}
@@ -744,9 +750,7 @@ impl DavFileSystem for SanctumFs {
.map_err(|_| FsError::GeneralFailure)?;
}
self.db
.rename_node(node.id, to_parent.id, to_name)
.map_err(|_| FsError::GeneralFailure)?;
self.rename_node(node.id, to_parent.id, to_name)?;
Ok(())
})
@@ -763,9 +767,7 @@ impl DavFileSystem for SanctumFs {
let to_str = Self::path_to_str(to);
let node = self
.db
.resolve_path(&from_str)
.map_err(|_| FsError::GeneralFailure)?
.resolve_path(&from_str)?
.ok_or(FsError::NotFound)?;
if node.is_dir {
@@ -779,15 +781,10 @@ impl DavFileSystem for SanctumFs {
}
let to_parent = self
.db
.resolve_path(to_parent_path)
.map_err(|_| FsError::GeneralFailure)?
.resolve_path(to_parent_path)?
.ok_or(FsError::NotFound)?;
let dest_node = self
.db
.create_node(to_parent.id, to_name, false)
.map_err(|_| FsError::GeneralFailure)?;
let dest_node = self.create_node(to_parent.id, to_name, false)?;
// Kopiere alle Chunks und re-verschlüssele mit neuer node_id (wegen AAD-Bindung!)
let total_chunks = if node.size == 0 {