diff --git a/QUICKSTART.md b/QUICKSTART.md new file mode 100644 index 0000000..e029c23 --- /dev/null +++ b/QUICKSTART.md @@ -0,0 +1,113 @@ +# Sanctum 🛡️ — Schnellstartanleitung & Notfallhandbuch + +Willkommen bei **Sanctum**! Diese Anleitung führt dich in wenigen Minuten durch die grundlegenden Funktionen zur sicheren Verwaltung deiner verschlüsselten Container unter Windows 10/11 – **100% Userland, ohne Administratorrechte und ohne Treiber**. + +--- + +## ⚡ In 3 Minuten startklar + +```powershell +# 1. Neuen Tresor mit Notfallkarte anlegen: +sanctum.exe init --path "C:\Users\DeinName\Dokumente\mein_tresor.sanctum" + +# 2. Tresor einbinden (wählt automatisch den nächsten freien Buchstaben, z. B. Z:): +sanctum.exe mount --path "C:\Users\DeinName\Dokumente\mein_tresor.sanctum" + +# 3. Sicher trennen: +# Entweder Ctrl+C im Konsolenfenster ODER Rechtsklick auf das Schild-Icon im Infobereich (Systray) -> "Aushängen & Beenden". +``` + +--- + +## 📂 Die Kernfunktionen im Überblick + +### 1. Tresor initialisieren (`init`) + +Beim Erstellen eines Tresors wählst du zwischen zwei Sicherheitsstufen: + +* **Standard-Tresor (Single-Vault)**: + ```powershell + sanctum.exe init --path "D:\Tresor\daten.sanctum" + ``` +* **Plausible Deniability Tresor (Dual-Vault mit Alibi-Carrier)**: + ```powershell + sanctum.exe init --path "D:\Tresor\daten.sanctum" --with-hidden + ``` + * Hier legst du **zwei verschiedene Passwörter** fest: + 1. **Decoy-Passwort**: Öffnet den äußeren Safe (enthält eine scheinbare Backup-Datei `system_backup.dat`). + 2. **Hidden-Passwort**: Öffnet den geheimen, unnachweisbaren Safe. + * **Wichtig**: Notiere dir die ausgegebenen **24 Wörter des Notfallschlüssels (BIP-39)** auf der untenstehenden Notfallkarte! + +--- + +### 2. Tresor einbinden (`mount`) + +Sanctum erkennt automatisch anhand des eingegebenen Passworts, ob der Decoy- oder Hidden-Safe geöffnet werden soll. + +| Befehl | Zweck | +| :--- | :--- | +| `sanctum mount --path ` | Standard-Mount. Öffnet das Laufwerk automatisch im Explorer. | +| `sanctum mount --path --drive S` | Bindet den Tresor fest an den Buchstaben `S:` (statt automatischer Wahl). | +| `sanctum mount --path --idle-timeout 300` | Trennt das Laufwerk automatisch nach 5 Minuten (300 Sek.) Inaktivität. | +| `sanctum mount --path --no-open` | Verhindert das automatische Öffnen des Windows Explorers (Schutz vor ShellBag-Spuren). | +| `sanctum mount --path --stealth` | **Lautloser Stealth-Modus**: Keine Terminal-Ausgaben, keine URLs, kein Explorer-Start. | + +--- + +### 3. Notfallrettung & Wartung + +* **Integritätsprüfung (FSCK)**: + ```powershell + sanctum.exe verify --path "D:\Tresor\daten.sanctum" + ``` + Überprüft die B-Tree-Struktur der Datenbank und testet sämtliche Chunks gegen ihre kryptografischen AEAD-Authentifizierungs-Tags. + +* **Online-Backup im laufenden Betrieb**: + ```powershell + sanctum.exe backup --path "D:\Tresor\daten.sanctum" --output "E:\Backup\daten_backup.sanctum" + ``` + Erzeugt über die SQLite Online Backup API eine konsistente Kopie – selbst während Dateien geöffnet sind. + +* **Passwort vergessen? Wiederherstellung via BIP-39 Notfallschlüssel**: + ```powershell + sanctum.exe passwd --path "D:\Tresor\daten.sanctum" --recovery-key + ``` + *(Liest die 24 Wörter maskiert ein, ohne Spuren in der PowerShell-Historie zu hinterlassen, und vergibt ein neues Passwort).* + +--- + +## 🖨️ Druckvorlage: BIP-39 Notfallkarte + +Drucke diesen Abschnitt aus oder übertrage die Wörter handschriftlich auf ein Blatt Papier. Bewahre diese Karte physisch getrennt von deinem Computer an einem sicheren Ort (z. B. Tresor, Dokumentenmappe) auf. + +```text +┌──────────────────────────────────────────────────────────────────────────────┐ +│ SANCTUM — KRYPTOGRAFISCHE NOTFALL-WIEDERHERSTELLUNGSKARTE (BIP-39) │ +└──────────────────────────────────────────────────────────────────────────────┘ + + Container: __________________________________________________________________ + Erstelldatum: ____.___.202__ Safe: [ ] Standard [ ] Hidden Vault + Hinweis: Alle Wörter sind in Kleinbuchstaben aus der offiziellen BIP-39 Liste. + + ┌────┬────────────────────────────┬────┬────────────────────────────┐ + │ # │ WORT │ # │ WORT │ + ├────┼────────────────────────────┼────┼────────────────────────────┤ + │ 01 │ __________________________ │ 13 │ __________________________ │ + │ 02 │ __________________________ │ 14 │ __________________________ │ + │ 03 │ __________________________ │ 15 │ __________________________ │ + │ 04 │ __________________________ │ 16 │ __________________________ │ + │ 05 │ __________________________ │ 17 │ __________________________ │ + │ 06 │ __________________________ │ 18 │ __________________________ │ + │ 07 │ __________________________ │ 19 │ __________________________ │ + │ 08 │ __________________________ │ 20 │ __________________________ │ + │ 09 │ __________________________ │ 21 │ __________________________ │ + │ 10 │ __________________________ │ 22 │ __________________________ │ + │ 11 │ __________________________ │ 23 │ __________________________ │ + │ 12 │ __________________________ │ 24 │ __________________________ │ + └────┴────────────────────────────┴────┴────────────────────────────┘ + + ⚠️ SICHERHEITSHINWEISE: + 1. Wer im Besitz dieser 24 Wörter ist, kann den Tresor ohne Passwort entschlüsseln! + 2. Niemals abfotografieren, in Cloud-Notizen speichern oder unverschlüsselt versenden. + 3. Bei Verlust beider Passwörter und dieser Karte sind die Daten unwiederbringlich verloren. +``` diff --git a/README.md b/README.md index 4da32ad..332edab 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ Sanctum ist eine eigenständige, speichersichere und hochperformante CLI-Anwendu - **Disaster Recovery**: 24-Wort BIP-39 Mnemonic Seed Phrases, konsistente Online-Backups via SQLite Online Backup API und kryptografische Vollprüfung (`sanctum verify`). - **Statisches Single-Binary**: `sanctum.exe` (~5.3 MB) ohne externe DLL-Abhängigkeiten. +> 💡 **Neu bei Sanctum?** Eine kompakte Schritt-für-Schritt-Anleitung inklusive ausdruckbarer BIP-39 Notfallkarte findest du in der [Schnellstartanleitung (QUICKSTART.md)](QUICKSTART.md). + --- ## 🔐 Kryptografie & Sicherheitsarchitektur diff --git a/scripts/package-release.ps1 b/scripts/package-release.ps1 index 3dda7b0..82330ec 100644 --- a/scripts/package-release.ps1 +++ b/scripts/package-release.ps1 @@ -63,6 +63,7 @@ New-Item -ItemType Directory -Path $StagingDir -Force | Out-Null $ExeSource = Join-Path $ProjectRoot "target\release\sanctum.exe" Copy-Item $ExeSource (Join-Path $StagingDir "sanctum.exe") Copy-Item (Join-Path $ProjectRoot "README.md") (Join-Path $StagingDir "README.md") +Copy-Item (Join-Path $ProjectRoot "QUICKSTART.md") (Join-Path $StagingDir "QUICKSTART.md") Copy-Item (Join-Path $ProjectRoot "LICENSE") (Join-Path $StagingDir "LICENSE") Copy-Item (Join-Path $ProjectRoot "LEGAL.md") (Join-Path $StagingDir "LEGAL.md") Copy-Item (Join-Path $ProjectRoot "THIRD_PARTY_LICENSES.md") (Join-Path $StagingDir "THIRD_PARTY_LICENSES.md") diff --git a/src/lib.rs b/src/lib.rs index e485f85..ee5ea7b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ pub mod carrier; pub mod crypto; pub mod mount; +pub mod platform; pub mod recovery; pub mod storage; pub mod ui; diff --git a/src/platform/mod.rs b/src/platform/mod.rs new file mode 100644 index 0000000..fc8b2d5 --- /dev/null +++ b/src/platform/mod.rs @@ -0,0 +1,7 @@ +//! Plattform-Abstraktionsschicht für Sanctum. +//! +//! Dieses Modul bündelt alle betriebssystemspezifischen Funktionen +//! (Speichersperren, Dateimanager-Aufrufe, Signal-Monitoring, Shell-Integration) +//! für Windows, Linux und macOS unter einer einheitlichen, speichersicheren Schnittstelle. + +pub use crate::windows::*; diff --git a/src/windows.rs b/src/windows.rs index 3457861..5ef0e00 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -32,14 +32,68 @@ pub fn find_next_available_drive() -> Result { } } -/// Öffnet das eingebundene Netzlaufwerk direkt im Windows Explorer. +/// Öffnet das eingebundene Netzlaufwerk oder Verzeichnis direkt im systemeigenen Dateimanager +/// (Windows: Explorer, macOS: open, Linux: xdg-open). pub fn open_in_explorer(drive_char: char) -> Result<()> { - let drive_path = format!("{}:\\", drive_char.to_ascii_uppercase()); - Command::new("explorer.exe") - .arg(&drive_path) - .spawn() - .with_context(|| format!("Konnte Windows Explorer für '{}' nicht öffnen", drive_path))?; - Ok(()) + #[cfg(windows)] + { + let drive_path = format!("{}:\\", drive_char.to_ascii_uppercase()); + Command::new("explorer.exe") + .arg(&drive_path) + .spawn() + .with_context(|| format!("Konnte Windows Explorer für '{}' nicht öffnen", drive_path))?; + Ok(()) + } + #[cfg(target_os = "macos")] + { + let _ = drive_char; + let _ = Command::new("open").arg(".").spawn(); + Ok(()) + } + #[cfg(all(unix, not(target_os = "macos")))] + { + let _ = drive_char; + let _ = Command::new("xdg-open").arg(".").spawn(); + Ok(()) + } + #[cfg(not(any(windows, unix)))] + { + let _ = drive_char; + Ok(()) + } +} + +/// Öffnet einen beliebigen Pfad im nativen Dateimanager der Plattform. +pub fn open_in_file_manager(path: &std::path::Path) -> Result<()> { + #[cfg(windows)] + { + Command::new("explorer.exe") + .arg(path) + .spawn() + .with_context(|| format!("Konnte Windows Explorer für '{}' nicht öffnen", path.display()))?; + Ok(()) + } + #[cfg(target_os = "macos")] + { + Command::new("open") + .arg(path) + .spawn() + .with_context(|| format!("Konnte macOS Finder für '{}' nicht öffnen", path.display()))?; + Ok(()) + } + #[cfg(all(unix, not(target_os = "macos")))] + { + Command::new("xdg-open") + .arg(path) + .spawn() + .with_context(|| format!("Konnte Dateimanager via xdg-open für '{}' nicht öffnen", path.display()))?; + Ok(()) + } + #[cfg(not(any(windows, unix)))] + { + let _ = path; + Ok(()) + } } /// Benachrichtigt die Windows-Shell (Explorer) über geänderte Dateiverknüpfungen (SHCNE_ASSOCCHANGED). @@ -67,6 +121,13 @@ pub fn notify_shell_associations_changed() { ); } } + #[cfg(all(unix, not(target_os = "macos")))] + { + let _ = Command::new("update-desktop-database").spawn(); + } + #[cfg(not(any(windows, all(unix, not(target_os = "macos")))))] + { + } } /// Registriert `.sanctum`-Containerdateien im Windows Explorer für den aktuellen Benutzer (HKCU, 100% Userland, keine Adminrechte). @@ -527,7 +588,7 @@ pub fn start_console_ctrl_monitor( } } -/// Verriegelt einen Speicherbereich im physischen RAM (verhindert Paging in pagefile.sys / swapfile.sys). +/// Verriegelt einen Speicherbereich im physischen RAM (verhindert Paging in pagefile.sys / swapfile.sys unter Windows bzw. Swap unter Linux/macOS). pub fn lock_memory(ptr: *const u8, len: usize) -> bool { #[cfg(windows)] { @@ -539,7 +600,17 @@ pub fn lock_memory(ptr: *const u8, len: usize) -> bool { } unsafe { VirtualLock(ptr as *const std::ffi::c_void, len) != 0 } } - #[cfg(not(windows))] + #[cfg(unix)] + { + extern "C" { + fn mlock(addr: *const std::ffi::c_void, len: usize) -> std::ffi::c_int; + } + if ptr.is_null() || len == 0 { + return false; + } + unsafe { mlock(ptr as *const std::ffi::c_void, len) == 0 } + } + #[cfg(not(any(windows, unix)))] { let _ = (ptr, len); false @@ -558,7 +629,17 @@ pub fn unlock_memory(ptr: *const u8, len: usize) -> bool { } unsafe { VirtualUnlock(ptr as *const std::ffi::c_void, len) != 0 } } - #[cfg(not(windows))] + #[cfg(unix)] + { + extern "C" { + fn munlock(addr: *const std::ffi::c_void, len: usize) -> std::ffi::c_int; + } + if ptr.is_null() || len == 0 { + return false; + } + unsafe { munlock(ptr as *const std::ffi::c_void, len) == 0 } + } + #[cfg(not(any(windows, unix)))] { let _ = (ptr, len); false diff --git a/tests/live_crash_resilience_test.rs b/tests/live_crash_resilience_test.rs new file mode 100644 index 0000000..2e1903a --- /dev/null +++ b/tests/live_crash_resilience_test.rs @@ -0,0 +1,152 @@ +use std::path::PathBuf; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; + +use bytes::Bytes; +use dav_server::{ + davpath::DavPath, + fs::{DavFileSystem, OpenOptions}, +}; +use rand::RngCore; +use sanctum::{ + crypto::{derive_kek, generate_dek, generate_salt, wrap_dek, KdfParams, FORMAT_VERSION}, + storage::Database, + verify::verify_container, + vfs::SanctumFs, +}; + +/// Live-Crash- und Stresstest: Simuliert harten Verbindungsabbruch und Power-Cut +/// während intensiver paralleler Schreibvorgänge im VFS. +#[tokio::test] +async fn test_live_crash_and_recovery_stress() { + let temp_dir = std::env::temp_dir(); + let container_path: PathBuf = temp_dir.join(format!("sanctum_live_stress_{}.sanctum", std::process::id())); + if container_path.exists() { + let _ = std::fs::remove_file(&container_path); + } + + let password = "LiveStressPassword2026!"; + 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("KEK derivation"); + let dek = generate_dek(); + let (wrapped_dek, header_nonce, header_tag) = wrap_dek(&kek, &dek).expect("DEK wrapping"); + + // 1. Initialisierung des Containers + { + let db = Database::open(&container_path).expect("Open database"); + db.init_schema(&salt, &kdf_params, &wrapped_dek, &header_nonce, &header_tag) + .expect("Init schema"); + db.checkpoint().expect("Initial Checkpoint"); + } + + // 2. Parallele Schreiblast mit SanctumFs erzeugen + let stop_signal = Arc::new(AtomicBool::new(false)); + let db = Database::open(&container_path).expect("Open database for VFS"); + let fs = SanctumFs::new(db, dek.clone(), FORMAT_VERSION); + + let mut handles = Vec::new(); + + // Spawn 4 parallele Schreiber + for worker_id in 0..4 { + let fs_clone = fs.clone(); + let stop_clone = stop_signal.clone(); + + let handle = tokio::spawn(async move { + let mut file_idx = 0; + while !stop_clone.load(Ordering::Relaxed) && file_idx < 10 { + let file_path_str = format!("/worker_{}_file_{}.dat", worker_id, file_idx); + let dav_path = DavPath::new(&file_path_str).unwrap(); + + let mut opts = OpenOptions::default(); + opts.write = true; + opts.create = true; + opts.truncate = true; + + // Datei anlegen + let mut file = match fs_clone.open(&dav_path, opts).await { + Ok(f) => f, + Err(_) => break, + }; + + // Mehrere 256-KB Blöcke schreiben (über mehrere Chunks hinweg) + let mut payload = vec![0u8; 256 * 1024]; + rand::thread_rng().fill_bytes(&mut payload); + + for _ in 0..6 { + if stop_clone.load(Ordering::Relaxed) { + break; + } + let _ = file.write_bytes(Bytes::copy_from_slice(&payload)).await; + } + let _ = file.flush().await; + file_idx += 1; + } + }); + handles.push(handle); + } + + // Lass die Worker 500ms unter Volllast schreiben + tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; + + // 3. Simuliere abrupten Prozessabbruch (Hard Kill / Power Cut) + // Wir brechen die Worker hart ab (Cancel) und verwerfen das FS-Handle ohne sauberen Unmount + stop_signal.store(true, Ordering::SeqCst); + for h in handles { + h.abort(); // Simuliert Kill + } + + // FS ohne Checkpoint/Drop-Finalisierung freigeben + drop(fs); + + // 4. Recovery & Integritätsprüfung nach Crash + // Das System muss die SQLite WAL-Datei automatisch erkennen und verarbeiten + let verify_result = verify_container(&container_path, Some(&dek), false).expect("Verify post-crash"); + assert!( + verify_result.is_healthy(), + "Container muss nach Crash vollkommen konsistent sein! Fehler: {:?}", + verify_result.errors + ); + assert_eq!(verify_result.corrupted_chunks, 0, "Keine korrupten Chunks erlaubt"); + + // 5. Konsistentes Weiterarbeiten nach dem Absturz + let db_recovered = Database::open(&container_path).expect("Open database after crash"); + let fs_recovered = SanctumFs::new(db_recovered, dek.clone(), FORMAT_VERSION); + + // Neue Datei im wiederhergestellten Dateisystem anlegen und lesen + let recovery_test_path = DavPath::new("/post_crash_verification.txt").unwrap(); + { + let mut opts = OpenOptions::default(); + opts.write = true; + opts.create = true; + opts.truncate = true; + let mut file = fs_recovered + .open(&recovery_test_path, opts) + .await + .expect("Create post-crash file"); + file.write_bytes(Bytes::from_static(b"Sanctum Crash Consistency Verified!")) + .await + .expect("Write post crash file"); + file.flush().await.expect("Flush post crash file"); + } + + // Datei wieder einlesen + { + let mut opts = OpenOptions::default(); + opts.read = true; + let mut file = fs_recovered + .open(&recovery_test_path, opts) + .await + .expect("Read post-crash file"); + let bytes = file.read_bytes(1024).await.expect("Read bytes"); + assert_eq!(&bytes[..], b"Sanctum Crash Consistency Verified!"); + } + + // Sauberes Aufräumen der Testdatei + drop(fs_recovered); + let _ = std::fs::remove_file(&container_path); +}