feat(opsec): implement inactivity auto-lock, session lock detection, and anti-leak shield
This commit is contained in:
@@ -498,4 +498,116 @@ async fn test_sanctum_disaster_recovery_workflow() {
|
||||
let _ = std::fs::remove_file(&backup_path);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_anti_leak_and_inactivity_shield() {
|
||||
let temp_dir = std::env::temp_dir();
|
||||
let container_path: PathBuf =
|
||||
temp_dir.join(format!("test_leak_shield_{}.sanctum", std::process::id()));
|
||||
|
||||
if container_path.exists() {
|
||||
let _ = std::fs::remove_file(&container_path);
|
||||
}
|
||||
|
||||
let password = "ShieldedContainerPass2026!";
|
||||
let salt = generate_salt();
|
||||
let kdf_params = KdfParams {
|
||||
memory_cost: 1024,
|
||||
time_cost: 1,
|
||||
parallelism: 1,
|
||||
};
|
||||
let kek = derive_kek(password, &salt, &kdf_params).expect("derive kek");
|
||||
let dek = generate_dek();
|
||||
let (wrapped_dek, header_nonce, header_tag) = wrap_dek(&kek, &dek).expect("wrap dek");
|
||||
|
||||
let db = Database::open(&container_path).expect("open db");
|
||||
db.init_schema(&salt, &kdf_params, &wrapped_dek, &header_nonce, &header_tag)
|
||||
.expect("init schema");
|
||||
db.checkpoint().expect("checkpoint");
|
||||
|
||||
// 1. Erstelle Filesystem mit aktivem Anti-Leak Shield (Standard)
|
||||
let fs = SanctumFs::with_options(db.clone(), dek.clone(), FORMAT_VERSION, true);
|
||||
|
||||
// 2. Windows Explorer Leck-Dateien müssen abgewehrt werden
|
||||
let leak_files = [
|
||||
"/Thumbs.db",
|
||||
"/thumbs.db",
|
||||
"/desktop.ini",
|
||||
"/Desktop.ini",
|
||||
"/Folder.jpg",
|
||||
"/AlbumArt_{A1B2C3D4-E5F6}_Large.jpg",
|
||||
"/albumartsmall.jpg",
|
||||
"/ehthumbs.db",
|
||||
"/autorun.inf",
|
||||
];
|
||||
|
||||
for leak in leak_files {
|
||||
let path = DavPath::new(leak).unwrap();
|
||||
let mut opts = OpenOptions::default();
|
||||
opts.write = true;
|
||||
opts.create_new = true;
|
||||
let res = fs.open(&path, opts).await;
|
||||
assert!(
|
||||
matches!(res, Err(dav_server::fs::FsError::Forbidden)),
|
||||
"Leak-Datei '{}' muss mit FsError::Forbidden blockiert werden",
|
||||
leak
|
||||
);
|
||||
}
|
||||
|
||||
// Ordnererstellung mit Leck-Namen muss ebenfalls blockiert werden
|
||||
let leak_dir = DavPath::new("/Thumbs.db").unwrap();
|
||||
let dir_res = fs.create_dir(&leak_dir).await;
|
||||
assert!(matches!(dir_res, Err(dav_server::fs::FsError::Forbidden)));
|
||||
|
||||
// 3. Legitime Dateien müssen reibungslos funktionieren
|
||||
let legit_path = DavPath::new("/top_secret.docx").unwrap();
|
||||
let mut opts_legit = OpenOptions::default();
|
||||
opts_legit.write = true;
|
||||
opts_legit.create_new = true;
|
||||
let mut file = fs.open(&legit_path, opts_legit).await.expect("create legit file");
|
||||
file.write_bytes(Bytes::from_static(b"Sanctum OpSec Shield Test"))
|
||||
.await
|
||||
.expect("write data");
|
||||
file.flush().await.expect("flush data");
|
||||
drop(file);
|
||||
|
||||
// 4. Inaktivitäts-Tracking überprüfen
|
||||
let activity_tracker = fs.last_activity();
|
||||
let recorded_now = activity_tracker.load(std::sync::atomic::Ordering::Relaxed);
|
||||
assert!(recorded_now > 0);
|
||||
|
||||
// Zurückdatieren um 300 Sekunden
|
||||
activity_tracker.store(recorded_now - 300, std::sync::atomic::Ordering::Relaxed);
|
||||
let past = activity_tracker.load(std::sync::atomic::Ordering::Relaxed);
|
||||
|
||||
// Datei lesen
|
||||
let mut opts_read = OpenOptions::default();
|
||||
opts_read.read = true;
|
||||
let mut rf = fs.open(&legit_path, opts_read).await.expect("open legit");
|
||||
let content = rf.read_bytes(25).await.expect("read bytes");
|
||||
assert_eq!(&content[..], b"Sanctum OpSec Shield Test");
|
||||
drop(rf);
|
||||
|
||||
let updated_time = activity_tracker.load(std::sync::atomic::Ordering::Relaxed);
|
||||
assert!(
|
||||
updated_time > past,
|
||||
"VFS-Aktivität muss den Inaktivitäts-Timer aktualisieren"
|
||||
);
|
||||
|
||||
// 5. Deaktivierter Shield (--no-anti-leak) erlaubt Explorer-Metadaten
|
||||
let unshielded_fs = SanctumFs::with_options(db.clone(), dek.clone(), FORMAT_VERSION, false);
|
||||
let mut unshielded_opts = OpenOptions::default();
|
||||
unshielded_opts.write = true;
|
||||
unshielded_opts.create_new = true;
|
||||
let desktop_ini_path = DavPath::new("/desktop.ini").unwrap();
|
||||
let desktop_res = unshielded_fs.open(&desktop_ini_path, unshielded_opts).await;
|
||||
assert!(
|
||||
desktop_res.is_ok(),
|
||||
"Ohne Shield muss Erstellung von desktop.ini gestattet sein"
|
||||
);
|
||||
|
||||
// Aufräumen
|
||||
let _ = std::fs::remove_file(&container_path);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user