feat(cross-platform): add QUICKSTART.md, live crash test, and platform abstraction module

This commit is contained in:
2026-09-10 16:31:57 +02:00
parent 6cb48f55d9
commit 46f976b353
7 changed files with 367 additions and 10 deletions
+113
View File
@@ -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 <DATEI>` | Standard-Mount. Öffnet das Laufwerk automatisch im Explorer. |
| `sanctum mount --path <DATEI> --drive S` | Bindet den Tresor fest an den Buchstaben `S:` (statt automatischer Wahl). |
| `sanctum mount --path <DATEI> --idle-timeout 300` | Trennt das Laufwerk automatisch nach 5 Minuten (300 Sek.) Inaktivität. |
| `sanctum mount --path <DATEI> --no-open` | Verhindert das automatische Öffnen des Windows Explorers (Schutz vor ShellBag-Spuren). |
| `sanctum mount --path <DATEI> --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.
```
+2
View File
@@ -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
+1
View File
@@ -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")
+1
View File
@@ -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;
+7
View File
@@ -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::*;
+85 -4
View File
@@ -32,14 +32,68 @@ pub fn find_next_available_drive() -> Result<char> {
}
}
/// Ö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<()> {
#[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
+152
View File
@@ -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);
}