use std::collections::HashSet; use std::path::PathBuf; use rand::rngs::OsRng; use rand::RngCore; use sanctum::crypto::{ derive_kek, generate_dek, generate_salt, wrap_slot0_payload, wrap_slot1_payload, KdfParams, MIN_MEMORY_COST_KIB, MIN_TIME_COST, }; use sanctum::storage::Database; use sanctum::sync::{delete_orphans_in_vault, SyncStats}; fn temp_db_path(prefix: &str) -> PathBuf { let mut path = std::env::temp_dir(); let id: u64 = OsRng.next_u64(); path.push(format!("sanctum_test_{}_{}.sanctum", prefix, id)); path } #[test] fn test_carrier_protection_in_storage_and_sync() { let path = temp_db_path("carrier_sec"); let carrier_name = "secret_carrier.iso"; let carrier_size_bytes = 2 * 1024 * 1024; // 2 MB let pass_decoy = "DecoyPassword2026!"; let pass_hidden = "SuperSecretHiddenPassword2026!"; let kdf_params = KdfParams { memory_cost: MIN_MEMORY_COST_KIB, time_cost: MIN_TIME_COST, parallelism: 1, }; let salt_0 = generate_salt(); let kek_0 = derive_kek(pass_decoy, &salt_0, &kdf_params).unwrap(); let dek_0 = generate_dek(); let salt_1 = generate_salt(); let kek_1 = derive_kek(pass_hidden, &salt_1, &kdf_params).unwrap(); let dek_1 = generate_dek(); let carrier_node_id = 3i64; let (wrapped_0, nonce_0, tag_0) = wrap_slot0_payload(&kek_0, &dek_0, carrier_node_id).unwrap(); let (wrapped_1, nonce_1, tag_1) = wrap_slot1_payload(&kek_1, &dek_1, &dek_0, carrier_node_id).unwrap(); let db = Database::open(&path).expect("Open database"); let created_cid = db .init_schema_with_carrier( &salt_0, &kdf_params, &wrapped_0, &nonce_0, &tag_0, Some(( carrier_name, carrier_size_bytes, &salt_1, &kdf_params, &wrapped_1, &nonce_1, &tag_1, &dek_0, &dek_1, )), ) .expect("Init carrier schema"); db.checkpoint().unwrap(); assert_eq!(created_cid, Some(carrier_node_id)); // 1. Direkter Löschversuch der Trägerdatei muss fail-closed abgewehrt werden let del_res = db.delete_node(carrier_node_id); assert!(del_res.is_err(), "Löschen der Trägerdatei muss fehlschlagen"); assert!(del_res.unwrap_err().to_string().contains("Carrier-Schutz")); // 2. Umbenennung der Trägerdatei muss fail-closed abgewehrt werden let rename_res = db.rename_node_in_vault(carrier_node_id, 1, "renamed.iso", 0, &dek_0); assert!(rename_res.is_err(), "Umbenennen der Trägerdatei muss fehlschlagen"); assert!(rename_res.unwrap_err().to_string().contains("Carrier-Schutz")); // 3. Truncate der Trägerdatei muss fail-closed abgewehrt werden let trunc_res = db.truncate_chunks_after(carrier_node_id, 0); assert!(trunc_res.is_err(), "Truncate der Trägerdatei muss fehlschlagen"); assert!(trunc_res.unwrap_err().to_string().contains("Carrier-Schutz")); // 4. delete_orphans_in_vault mit leerem lokalem Pfad-Set: // Der Carrier darf unter keinen Umständen gelöscht werden! let local_paths = HashSet::new(); let mut stats = SyncStats::default(); let orphan_res = delete_orphans_in_vault( &db, 0, // Decoy Vault ID &dek_0, 1, // Root-Knoten "", &local_paths, false, // kein dry_run true, // quiet &mut stats, ); assert!(orphan_res.is_ok(), "delete_orphans_in_vault muss ohne Fehler durchlaufen"); assert_eq!(stats.files_deleted, 0, "Carrier-Datei darf nicht gelöscht worden sein"); // Sicherstellen, dass Carrier nach wie vor in der DB existiert let carrier_rec = db.get_node_by_id(carrier_node_id).unwrap(); assert!(carrier_rec.is_some(), "Carrier-Knoten muss nach Sync unversehrt vorhanden sein"); let _ = std::fs::remove_file(&path); }