11 Commits
Author SHA1 Message Date
harald 3f3f8f2730 chore(packages): update scoop and winget manifests to v0.4.1
Sanctum Release / Build & Release (Windows x86_64) (push) Canceled after 0s
2026-09-14 14:11:51 +02:00
harald 650cfc7cd4 chore(release): bump version to v0.4.1
Sanctum Release / Build & Release (Windows x86_64) (push) Canceled after 0s
2026-09-14 14:02:02 +02:00
harald 9d1a4d3ad7 build: preserve dist directory and merge SHA256 sums in package-release.ps1
Sanctum Release / Build & Release (Windows x86_64) (push) Canceled after 0s
2026-09-14 13:36:59 +02:00
harald aa25719c0d fix(linux): prevent premature unmount caused by closed channel triggers in select!
Sanctum Release / Build & Release (Windows x86_64) (push) Canceled after 0s
2026-09-14 13:27:10 +02:00
harald 34378998c4 feat(linux): add --mount-point CLI option and platform-aware mount messages
Sanctum Release / Build & Release (Windows x86_64) (push) Canceled after 0s
2026-09-12 14:36:43 +02:00
harald 4ef0c414fa feat(linux): add static musl x86_64 build, packaging, and Freedesktop integration
Sanctum Release / Build & Release (Windows x86_64) (push) Canceled after 0s
2026-09-10 17:45:04 +02:00
harald 1f3afb1e52 fix(scripts): correct URL variable interpolation and add error check in publish script
Sanctum Release / Build & Release (Windows x86_64) (push) Canceled after 0s
2026-09-10 17:05:15 +02:00
harald 5f2040f8bf chore(release): bump version to 0.4.0 and update manifests
Sanctum Release / Build & Release (Windows x86_64) (push) Canceled after 0s
2026-09-10 17:02:05 +02:00
harald da2ddbd6ab feat(distribution): add Scoop/Winget manifests, INSTALL.md, and Unix mount integration 2026-09-10 16:51:24 +02:00
harald 46f976b353 feat(cross-platform): add QUICKSTART.md, live crash test, and platform abstraction module 2026-09-10 16:31:57 +02:00
harald 6cb48f55d9 docs(compliance): add LEGAL.md, THIRD_PARTY_LICENSES.md, and update packaging
Sanctum Release / Build & Release (Windows x86_64) (push) Canceled after 0s
2026-09-10 15:14:51 +02:00
19 changed files with 1599 additions and 219 deletions
+34
View File
@@ -5,6 +5,40 @@ Alle nennenswerten Änderungen an diesem Projekt werden in dieser Datei dokument
Das Format basiert auf [Keep a Changelog](https://keepachangelog.com/de/1.1.0/)
und dieses Projekt folgt den Richtlinien von [Semantic Versioning](https://semver.org/lang/de/).
## [0.4.1] - 2026-09-14
### Fixed
- **Linux Mount-Stabilität & Signal-Handling**:
- Behebung eines sofortigen automatischen Aushängens direkt nach erfolgreichem Mount unter Linux: Windows-spezifische Kontrollkanäle (Console-Close, Session-Lock, System-Tray) wurden auf Nicht-Windows-Systemen sofort geschlossen, wodurch `tokio::select!` über `None` vorzeitig ausgelöst wurde.
- Plattform-spezifische Aufteilung der `tokio::select!`-Ereignisschleife: Unter Linux/Unix wird nun sauber auf POSIX-Terminierungssignale (`SIGINT`, `SIGTERM`, `SIGHUP`) gelauscht.
- Defensives Pattern-Matching mit `Some(()) = rx.recv()` stellt sicher, dass geschlossene Kanäle keine unberechtigten Shutdowns mehr auslösen können.
- Automatisches Öffnen via `xdg-open` auf Linux vorerst unterdrückt.
## [0.4.0] - 2026-09-10
### Added
- **Distribution & Package Ecosystem**:
- Offizielles Scoop-Manifest (`packages/scoop/sanctum.json`) für One-Click CLI-Installation und nahtlose Upgrades unter Windows.
- Windows Package Manager (Winget) Manifest (`packages/winget/HaraldPansi.Sanctum.yaml`) zur systemweiten Paketverwaltung.
- Vollständiger Installations-Leitfaden (`INSTALL.md`) für Scoop, Winget, Standalone-Binaries und `cargo install`.
- **Cross-Platform Architektur (Linux & macOS Vorbereitung)**:
- Neues Plattform-Abstraktionsmodul `src/platform/mod.rs` für OS-unabhängiges Speicherschutz- und Mount-Handling.
- POSIX-Memory-Hardening mit `mlock` und `munlock` Systemaufrufen für Unix-Systeme.
- Plattformübergreifendes Datei- und Ordneröffnen (`explorer.exe` unter Windows, `xdg-open` unter Linux, `open` unter macOS).
- Native WebDAV-VFS-Einbindung für Linux GNOME/KDE via `gio mount`.
- **Chaos Engineering & Live-Crash-Resilienz**:
- `tests/live_crash_resilience_test.rs`: Umfassende automatisierte Stresstests mit Multi-Worker Schreibabbrüchen, asynchronen Panic-Simulationen und Validierung der SQLite WAL-Konsistenz (Null Korruption).
- **Legal, Compliance & Software Bill of Materials (SBOM)**:
- `LEGAL.md`: Rechtliche Leitlinien, US EAR § 742.15(b) & EU Dual-Use Exportkontroll-Klassifizierung, DSGVO Art. 25 Zero-Data-Erklärung und Haftungsausschluss nach § 521 BGB.
- `THIRD_PARTY_LICENSES.md`: Lückenloses Lizenzaudit aller 231 Abhängigkeiten (100% permissive Lizenzen, 0% Copyleft) zur strikten Compliance mit Apache-2.0 Section 4.
- Aufnahme von `LEGAL.md` und `THIRD_PARTY_LICENSES.md` in das offizielle Release-Distributionsarchiv (`package-release.ps1`).
- **Statisches Linux Single-Binary (`x86_64-unknown-linux-musl`)**:
- 100% statisch gelinktes Linux-Binary ohne GLIBC-Abhängigkeiten via Zig-Cross-Toolchain.
- Neues Linux-Distributionsarchiv `sanctum-v0.4.0-linux-x86_64.tar.gz` inklusive Dokumentation und Prüfsummen.
- Native Freedesktop `.desktop`-Datei und MIME-Type-Integration (`application/x-sanctum`) via `sanctum register`.
- **OpSec & Disaster Recovery UX**:
- `QUICKSTART.md`: Druckbare 24-Wörter BIP-39 Notfallkarte zur sicheren analogen Verwahrung des Notfallschlüssels (Air-Gapped Vault Paper Backup).
## [0.3.1] - 2026-09-10
### Added
Generated
+1 -1
View File
@@ -1377,7 +1377,7 @@ checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
[[package]]
name = "sanctum"
version = "0.3.1"
version = "0.4.1"
dependencies = [
"aes-gcm",
"anyhow",
+3 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "sanctum"
version = "0.3.1"
version = "0.4.1"
edition = "2021"
authors = ["Harald Pansi <harald@pansi.eu>", "Sanctum Engineering Team"]
description = "Verschlüsselter Ein-Datei-Container unter Windows im reinen Userland via WebDAV"
@@ -36,6 +36,8 @@ dyn-clone = "1.0"
lz4_flex = "0.11"
bip39 = { version = "2.2", features = ["zeroize"] }
hex = "0.4"
[target.'cfg(windows)'.dependencies]
tray-item = "0.10"
[profile.release]
+94
View File
@@ -0,0 +1,94 @@
# Installation & Paketmanager-Guide für Sanctum 🛡️
Sanctum kann auf verschiedene Arten unter Windows 10/11 installiert und verwaltet werden. Es sind **keine Administratorrechte** erforderlich.
---
## 🚀 1. Installation via Scoop (Empfohlen für Entwickler)
Wenn du den Windows-Paketmanager [Scoop](https://scoop.sh/) nutzt, kannst du Sanctum direkt über das bereitgestellte Manifest installieren:
```powershell
# Direkt über die URL des Manifests installieren:
scoop install https://gitea.pansi.eu/harald/sanctum/raw/branch/main/packages/scoop/sanctum.json
```
Oder falls du ein lokales Repository geklont hast:
```powershell
scoop install .\packages\scoop\sanctum.json
```
Nach der Installation ist der Befehl `sanctum` sofort global in deiner PowerShell verfügbar.
---
## 🪟 2. Installation via Windows Package Manager (Winget)
Sanctum verfügt über ein standardkonformes Winget-Manifest (Schema 1.6.0).
### Lokale Installation via Manifest:
```powershell
winget install --manifest .\packages\winget\HaraldPansi.Sanctum.yaml
```
Winget entpackt das portable Binary automatisch in das Benutzer-Anwendungsverzeichnis und verknüpft das Alias `sanctum`.
---
## 📦 3. Manuelle Installation (Standalone Portable)
1. Lade das aktuelle Release-Paket herunter:
* **URL:** [https://gitea.pansi.eu/harald/sanctum/releases/tag/v0.4.0](https://gitea.pansi.eu/harald/sanctum/releases/tag/v0.4.0)
* **Datei:** `sanctum-v0.4.0-windows-x86_64.zip`
2. Entpacke das Archiv in ein Verzeichnis deiner Wahl (z. B. `C:\Tools\Sanctum\`).
3. *(Optional)* Registriere das Windows Explorer Rechtsklick-Kontextmenü:
```powershell
.\sanctum.exe register
```
*(Erlaubt das Einhängen per Rechtsklick "In Sanctum öffnen" ohne Adminrechte).*
---
## 🐧 4. Installation unter Linux (x86_64)
Sanctum wird für Linux als **100 % statisches Single-Binary** (`x86_64-unknown-linux-musl`) ohne jegliche GLIBC- oder Bibliotheksabhängigkeiten bereitgestellt. Es läuft direkt auf Ubuntu, Debian, Alpine, Arch, Fedora und weiteren Distributionen:
1. Lade das Linux-Release herunter und installiere es:
```bash
curl -LO https://gitea.pansi.eu/harald/sanctum/releases/download/v0.4.0/sanctum-v0.4.0-linux-x86_64.tar.gz
tar -xzf sanctum-v0.4.0-linux-x86_64.tar.gz
chmod +x sanctum
sudo install -m 755 sanctum /usr/local/bin/sanctum
```
2. *(Optional)* Freedesktop-Integration einrichten (Doppelklick in GNOME / KDE / Thunar):
```bash
sanctum register
```
*Richtet die `.desktop`-Datei und den MIME-Typ `application/x-sanctum` im Userland (`~/.local/share`) ein.*
---
## 🔄 Updates
* **Scoop:**
```powershell
scoop update sanctum
```
* **Winget:**
```powershell
winget upgrade --manifest .\packages\winget\HaraldPansi.Sanctum.yaml
```
---
## 🗑️ Deinstallation
* **Scoop:**
```powershell
scoop uninstall sanctum
```
* **Explorer-Kontextmenü rückstandslos bereinigen:**
```powershell
sanctum.exe unregister
```
+73
View File
@@ -0,0 +1,73 @@
# Rechtliche Hinweise, Compliance & Exportkontrolle (LEGAL)
Dieses Dokument regelt die rechtlichen Rahmenbedingungen, Exportkontroll-Klassifizierungen, Lizenz-Compliance sowie die datenschutzrechtlichen und haftungsrechtlichen Grundlagen der Software **Sanctum**.
---
## 1. Kryptografie-Exportkontrolle & Dual-Use-Klassifizierung
Sanctum implementiert starke, moderne kryptografische Verfahren mit symmetrischen Schlüssellängen von 256 Bit (AES-256-GCM), asymmetrischer KDF (Argon2id) sowie BIP-39 Schlüsselableitung.
### A. US Export Administration Regulations (EAR)
* **Klassifizierung (ECCN)**:
* Hardware/Software mit starker Verschlüsselung fällt unter **ECCN 5A002.a** bzw. **5D002** (Commerce Control List, Category 5, Part 2: *Information Security*).
* **Ausnahmetatbestand für frei verfügbaren Quellcode**:
* Gemäß **15 C.F.R. § 742.15(b)** (*Publicly Available Encryption Source Code*) und **15 C.F.R. § 734.3(b)(3)** unterliegt öffentlich zugänglicher Open-Source-Quellcode, der jedermann unentgeltlich und ohne Zugriffsbeschränkungen zur Verfügung steht, **nicht** den Ausfuhrgenehmigungspflichten der EAR.
* Sanctum wird unter der freien MIT-Lizenz auf frei zugänglichen Repositorien (einschließlich Quellcode, Spezifikationen und Build-Skripten) bereitgestellt.
* **Sanktionierte Staaten & Personen (OFAC)**:
* Trotz der allgemeinen Open-Source-Freistellung ist der wissentliche direkte oder indirekte Export/Re-Export an Personen oder Entitäten auf US-Sanktionslisten (OFAC Specially Designated Nationals List - SDN) sowie in Länder unter umfassenden Handelsembargos (Kuba, Iran, Nordkorea, Syrien, besetzte Gebiete der Ukraine) untersagt.
### B. Europäische Union (EU Dual-Use-Verordnung 2021/821)
* **Anhang I, Kategorie 5 Teil 2**:
* Systeme der Informationssicherheit sind grundsätzlich im Anhang I der Dual-Use-Verordnung gelistet.
* **Allgemeine Software-Anmerkung (General Software Note - GSN)**:
* Gemäß der GSN zu Anhang I der Verordnung (EU) 2021/821 unterliegt Software **nicht** der Ausfuhrkontrolle, wenn sie:
1. Allgemein zugänglich ist (*in the public domain*), z. B. durch uneingeschränkten, unentgeltlichen Download; oder
2. Der Mindest-Quellcode für die Installation, den Betrieb oder die Wartung nicht-kontrollierter Güter ist.
* Da Sanctum als freie Open-Source-Software jedermann unentgeltlich zur Verfügung steht, greift die Freistellung der GSN.
---
## 2. Plausible Deniability & Herausgabepflichten (Key Disclosure Laws)
Sanctum implementiert mit **Modell A** (Steganografischer Alibi-Carrier im Decoy-Vault mit exakter physikalischer Dateigrößen-Invarianz und uniformem Entropie-Slack) ein mathematisch informationstheoretisches Modell für **Plausible Deniability** (Glaubhafte Abstreitbarkeit).
> [!WARNING]
> **Wichtiger Hinweis für Anwender**: Plausible Deniability ist ein **technischer Schutzmechanismus**, kein juristisches Schutzschild. Die Wirksamkeit gegenüber staatlichen Ermittlungsbehörden hängt maßgeblich von der jeweiligen Jurisdiktion und dem eigenen Aussageverhalten ab:
1. **Deutschland / Österreich / Schweiz (DACH)**:
* **Nemo tenetur se ipsum accusare**: Niemand ist verpflichtet, sich in einem Strafverfahren selbst zu belasten (§ 136 Abs. 1 S. 2 StPO, Art. 6 EMRK). Beschuldigte haben das Recht, Passwörter und Schlüssel vollständig zu verschweigen.
* **Aussage als Beschuldigter**: Gibt ein Beschuldigter freiwillig das Passwort für den Decoy-Vault (Slot 0) heraus und verschweigt die Existenz des Hidden Vaults (Slot 1), macht er sich in der Regel nicht strafbar, solange er keine Dritten falsch verdächtigt (§ 164 StGB).
* **Zeugenstatus**: Zeugen sind grundsätzlich zur Wahrheit verpflichtet (§ 70 StPO). Bei falscher Aussage droht Strafbarkeit (§§ 153 ff. StGB).
2. **Großbritannien (UK - RIPA 2000 Part III, Section 49 & 53)**:
* Britische Behörden können unter Androhung einer Freiheitsstrafe von bis zu 2 Jahren (bei Gefährdung der nationalen Sicherheit bis zu 5 Jahren) die Offenlegung von Schlüsseln förmlich anordnen (*Section 49 Notice*).
* Da Sanctum dank Modell A keine forensisch nachweisbaren Spuren eines Hidden Vaults hinterlässt, existiert für Ermittler kein Nachweis, dass überhaupt ein zweiter Schlüssel existiert. Dennoch obliegt die Beweiswürdigung dem zuständigen Gericht.
3. **Vereinigte Staaten von Amerika (USA - 5th Amendment)**:
* Das 5. Zusatzprotokoll schützt vor erzwungenen Zeugenaussagen gegen sich selbst. Passwörter gelten als geschützter Denkinhalt (*testimonial*).
* *Foregone Conclusion Doctrine*: Kann der Staat beweisen, dass bestimmte inkriminierte Dateien auf dem Gerät existieren, kann die Passworteingabe als Vollstreckungshandlung erzwungen werden. Bei Sanctum verhindert die ununterscheidbare Entropie des Trägers den Nachweis der Existenz von Daten im Hidden Safe.
---
## 3. Haftungsausschluss & Gewährleistung (EU / DACH Recht)
Die in der [LICENSE](LICENSE) enthaltene US-Standardklausel (*„AS IS, WITHOUT WARRANTY OF ANY KIND“*) wird für den Geltungsbereich des europäischen und deutschen Rechts wie folgt präzisiert und ergänzt:
### A. Schenkungsrechtliche Haftungsbeschränkung (§ 521 BGB)
1. Sanctum wird dem Anwender unentgeltlich und ohne Gegenleistung überlassen.
2. Gemäß **§ 521 des Bürgerlichen Gesetzbuches (BGB)** haftet der Urheber / Entwickler bei unentgeltlicher Softwareüberlassung **nur für Vorsatz und grobe Fahrlässigkeit**.
3. Die Haftung für einfache/leichte Fahrlässigkeit, mittelbare Schäden, Folgeschäden, Datenverluste, entgangenen Gewinn oder Betriebsunterbrechungen ist im gesetzlich zulässigen Rahmen vollständig ausgeschlossen.
### B. Eigenverantwortung für Backups & Notfallschlüssel
* Kryptografie verzeiht keine Fehler: Bei Verlust beider Passwörter sowie der 24-Wort BIP-39 Notfallschlüssel ist eine Entschlüsselung mathematisch ausgeschlossen. Der Entwickler verfügt über keinerlei Master-Keys, Backdoors oder Wiederherstellungsmechanismen.
* Der Anwender ist für die regelmäßige externe Sicherung seiner Container (`sanctum backup`) und das sichere Verwahren seiner BIP-39 Notfallkarten allein verantwortlich.
---
## 4. Datenschutz & DSGVO / GDPR (Zero-Telemetry-Garantie)
Sanctum folgt uneingeschränkt dem Prinzip **Privacy by Design and by Default** (Art. 25 DSGVO):
* **0 Bytes Telemetrie**: Sanctum enthält keinen Code für Analytics, Fehlerberichterstattung (Crash Reporting), User-Tracking oder Telemetrie.
* **100% Offline-Betrieb**: Sanctum stellt zu keinem Zeitpunkt ausgehende Internetverbindungen her. Es gibt keine Update-Prüfungen, Pingbacks oder Cloud-Synchronisationen.
* **Keine Speicherung personenbezogener Daten**: Sanctum speichert lokal keinerlei Protokolle über eingegebene Passwörter, Benutzer-IDs oder Dateinamen außerhalb des verschlüsselten Containers.
* **Windows Host-Hygiene**: Durch das integrierte *Anti-Leak-Shield* und den *RAM-Paging-Schutz* (`VirtualLock`) werden Spuren im Host-System (Thumbs.db, temporäre Dateien, Auslagerungsdatei) aktiv unterbunden.
+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.
```
+20 -1
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 findest du in der [Schnellstartanleitung (QUICKSTART.md)](QUICKSTART.md). Hinweise zur Installation via Scoop oder Winget gibt es im [Installations-Guide (INSTALL.md)](INSTALL.md).
---
## 🔐 Kryptografie & Sicherheitsarchitektur
@@ -209,13 +211,30 @@ powershell -ExecutionPolicy Bypass -File .\scripts\package-release.ps1
```
Erzeugt:
- `dist/sanctum-v0.3.1-windows-x86_64.zip`
- `dist/sanctum-v0.4.0-windows-x86_64.zip` (Windows x86_64 ZIP)
- `dist/sanctum-v0.4.0-linux-x86_64.tar.gz` (Linux x86_64 musl static TAR.GZ via `package-release-linux.ps1`)
- `dist/SHA256SUMS.txt`
---
## ⚖️ Rechtliche Hinweise, Compliance & Exportkontrolle
- **Kryptografie-Exportkontrolle (EAR & EU Dual-Use)**:
Sanctum nutzt starke Kryptografie (AES-256-GCM, Argon2id). Da der Quellcode als freie Open-Source-Software jedermann unbeschränkt und unentgeltlich zur Verfügung steht, unterliegt er den Ausnahmetatbeständen gem. **15 C.F.R. § 742.15(b)** (US EAR) sowie der **General Software Note (GSN)** der Verordnung (EU) 2021/821.
- **Plausible Deniability & Herausgabepflichten**:
Sanctum bietet mit dem steganografischen Alibi-Carrier (Modell A) ein informationstheoretisches Modell für glaubhafte Abstreitbarkeit. Dies ist ein technischer Schutzmechanismus, kein juristisches Schutzschild. Die rechtliche Bewertung im Fall strafprozessualer Herausgabeanordnungen (*Key Disclosure*) hängt von der jeweiligen Jurisdiktion ab (z. B. Nemo-tenetur-Grundsatz in DACH vs. RIPA Section 49 im UK).
- **Datenschutz & Telemetrie-Freiheit (DSGVO / GDPR)**:
Sanctum arbeitet zu 100% offline und enthält keinerlei Code für Telemetrie, Analytics, Fehlerberichterstattung oder IP-Übertragungen (Art. 25 DSGVO *Privacy by Design*).
- **Haftungsbeschränkung (EU/DACH Recht)**:
Die Software wird als unentgeltliche FOSS-Software bereitgestellt. Gemäß **§ 521 BGB** (Schenkungsrecht) ist die Haftung des Entwicklers auf Vorsatz und grobe Fahrlässigkeit beschränkt. Der Anwender ist für die sichere Verwahrung von Schlüsseln und Backups selbst verantwortlich.
- Ausführliche rechtliche Erläuterungen finden sich in [`LEGAL.md`](LEGAL.md).
---
## 📄 Lizenz & Changelog
- Lizenziert unter der [MIT License](LICENSE).
- Vollständige Third-Party-Attributionen aller ~230 Abhängigkeiten (Apache-2.0, MIT, BSD, SQLite Public Domain) sind in [`THIRD_PARTY_LICENSES.md`](THIRD_PARTY_LICENSES.md) dokumentiert.
- Ausführliche rechtliche Bestimmungen und Exportkontroll-Hinweise sind in [`LEGAL.md`](LEGAL.md) geregelt.
- Details zu allen Versionen und Änderungen findest du im [CHANGELOG.md](CHANGELOG.md).
+382
View File
@@ -0,0 +1,382 @@
# Third-Party Software Licenses & Attributions
Sanctum incorporates open-source software packages statically compiled into the binary.
All included components are distributed under permissive open-source licenses.
This document provides full attribution and license texts in compliance with
Section 4 of the Apache License, Version 2.0, the MIT License, and BSD licenses.
---
## 1. Dependency Inventory
| Crate | Version | License |
| :--- | :--- | :--- |
| `aead` | 0.5.2 | MIT OR Apache-2.0 |
| `aes` | 0.8.4 | MIT OR Apache-2.0 |
| `aes-gcm` | 0.10.3 | Apache-2.0 OR MIT |
| `ahash` | 0.8.12 | MIT OR Apache-2.0 |
| `aho-corasick` | 1.1.5 | Unlicense OR MIT |
| `android_system_properties` | 0.1.6 | MIT OR Apache-2.0 |
| `anstream` | 1.0.0 | MIT OR Apache-2.0 |
| `anstyle` | 1.0.14 | MIT OR Apache-2.0 |
| `anstyle-parse` | 1.0.0 | MIT OR Apache-2.0 |
| `anstyle-query` | 1.1.5 | MIT OR Apache-2.0 |
| `anstyle-wincon` | 3.0.11 | MIT OR Apache-2.0 |
| `anyhow` | 1.0.104 | MIT OR Apache-2.0 |
| `argon2` | 0.5.3 | MIT OR Apache-2.0 |
| `arrayvec` | 0.7.8 | MIT OR Apache-2.0 |
| `atomic-waker` | 1.1.2 | Apache-2.0 OR MIT |
| `autocfg` | 1.5.1 | Apache-2.0 OR MIT |
| `base64` | 0.22.1 | MIT OR Apache-2.0 |
| `base64ct` | 1.8.3 | Apache-2.0 OR MIT |
| `bip39` | 2.2.2 | CC0-1.0 |
| `bitcoin_hashes` | 0.14.101 | CC0-1.0 |
| `bitflags` | 2.13.1 | MIT OR Apache-2.0 |
| `bitflags` | 1.3.2 | MIT/Apache-2.0 |
| `blake2` | 0.10.6 | MIT OR Apache-2.0 |
| `block` | 0.1.6 | MIT |
| `block-buffer` | 0.10.4 | MIT OR Apache-2.0 |
| `bumpalo` | 3.20.3 | MIT OR Apache-2.0 |
| `bytes` | 1.12.1 | MIT |
| `cc` | 1.4.5 | MIT OR Apache-2.0 |
| `cfg-if` | 1.0.4 | MIT OR Apache-2.0 |
| `chrono` | 0.4.45 | MIT OR Apache-2.0 |
| `cipher` | 0.4.4 | MIT OR Apache-2.0 |
| `clap` | 4.6.6 | MIT OR Apache-2.0 |
| `clap_builder` | 4.6.6 | MIT OR Apache-2.0 |
| `clap_derive` | 4.6.4 | MIT OR Apache-2.0 |
| `clap_lex` | 1.1.0 | MIT OR Apache-2.0 |
| `cocoa` | 0.25.0 | MIT OR Apache-2.0 |
| `cocoa-foundation` | 0.1.2 | MIT OR Apache-2.0 |
| `colorchoice` | 1.0.5 | MIT OR Apache-2.0 |
| `core-foundation` | 0.9.4 | MIT OR Apache-2.0 |
| `core-foundation-sys` | 0.8.7 | MIT OR Apache-2.0 |
| `core-graphics` | 0.23.2 | MIT OR Apache-2.0 |
| `core-graphics-types` | 0.1.3 | MIT OR Apache-2.0 |
| `cpufeatures` | 0.2.17 | MIT OR Apache-2.0 |
| `crypto-common` | 0.1.7 | MIT OR Apache-2.0 |
| `ctr` | 0.9.2 | MIT OR Apache-2.0 |
| `dav-server` | 0.11.0 | Apache-2.0 |
| `derive-where` | 1.6.1 | MIT OR Apache-2.0 |
| `digest` | 0.10.7 | MIT OR Apache-2.0 |
| `displaydoc` | 0.2.7 | MIT OR Apache-2.0 |
| `dyn-clone` | 1.0.20 | MIT OR Apache-2.0 |
| `equivalent` | 1.0.2 | Apache-2.0 OR MIT |
| `errno` | 0.3.14 | MIT OR Apache-2.0 |
| `fallible-iterator` | 0.3.0 | MIT/Apache-2.0 |
| `fallible-streaming-iterator` | 0.1.9 | MIT/Apache-2.0 |
| `find-msvc-tools` | 0.1.12 | MIT OR Apache-2.0 |
| `fnv` | 1.0.7 | Apache-2.0 / MIT |
| `foreign-types` | 0.5.0 | MIT/Apache-2.0 |
| `foreign-types-macros` | 0.2.4 | MIT OR Apache-2.0 |
| `foreign-types-shared` | 0.3.1 | MIT OR Apache-2.0 |
| `form_urlencoded` | 1.2.2 | MIT OR Apache-2.0 |
| `futures-channel` | 0.3.34 | MIT OR Apache-2.0 |
| `futures-core` | 0.3.34 | MIT OR Apache-2.0 |
| `futures-macro` | 0.3.34 | MIT OR Apache-2.0 |
| `futures-sink` | 0.3.34 | MIT OR Apache-2.0 |
| `futures-task` | 0.3.34 | MIT OR Apache-2.0 |
| `futures-util` | 0.3.34 | MIT OR Apache-2.0 |
| `generic-array` | 0.14.7 | MIT |
| `getrandom` | 0.4.3 | MIT OR Apache-2.0 |
| `getrandom` | 0.2.17 | MIT OR Apache-2.0 |
| `ghash` | 0.5.1 | Apache-2.0 OR MIT |
| `h2` | 0.4.19 | MIT |
| `hashbrown` | 0.17.1 | MIT OR Apache-2.0 |
| `hashbrown` | 0.14.5 | MIT OR Apache-2.0 |
| `hashlink` | 0.9.1 | MIT OR Apache-2.0 |
| `headers` | 0.4.1 | MIT |
| `headers-core` | 0.3.0 | MIT |
| `heck` | 0.5.0 | MIT OR Apache-2.0 |
| `hex` | 0.4.3 | MIT OR Apache-2.0 |
| `hex-conservative` | 0.2.3 | CC0-1.0 |
| `htmlescape` | 0.3.1 | Apache-2.0 / MIT / MPL-2.0 |
| `http` | 1.5.0 | MIT OR Apache-2.0 |
| `httparse` | 1.10.1 | MIT OR Apache-2.0 |
| `http-body` | 1.1.0 | MIT |
| `http-body-util` | 0.1.5 | MIT |
| `httpdate` | 1.0.3 | MIT OR Apache-2.0 |
| `hyper` | 1.11.1 | MIT |
| `hyper-util` | 0.1.20 | MIT |
| `iana-time-zone` | 0.1.65 | MIT OR Apache-2.0 |
| `iana-time-zone-haiku` | 0.1.2 | MIT OR Apache-2.0 |
| `icu_collections` | 2.3.0 | Unicode-3.0 |
| `icu_locale_core` | 2.3.0 | Unicode-3.0 |
| `icu_normalizer` | 2.3.0 | Unicode-3.0 |
| `icu_normalizer_data` | 2.3.0 | Unicode-3.0 |
| `icu_properties` | 2.3.0 | Unicode-3.0 |
| `icu_properties_data` | 2.3.0 | Unicode-3.0 |
| `icu_provider` | 2.3.1 | Unicode-3.0 |
| `idna` | 1.1.0 | MIT OR Apache-2.0 |
| `idna_adapter` | 1.2.2 | Apache-2.0 OR MIT |
| `indexmap` | 2.14.2 | Apache-2.0 OR MIT |
| `inout` | 0.1.4 | MIT OR Apache-2.0 |
| `is_terminal_polyfill` | 1.70.2 | MIT OR Apache-2.0 |
| `itoa` | 1.0.18 | MIT OR Apache-2.0 |
| `js-sys` | 0.3.105 | MIT OR Apache-2.0 |
| `lazy_static` | 1.5.0 | MIT OR Apache-2.0 |
| `libc` | 0.2.189 | MIT OR Apache-2.0 |
| `libsqlite3-sys` | 0.30.1 | MIT |
| `litemap` | 0.8.3 | Unicode-3.0 |
| `lock_api` | 0.4.14 | MIT OR Apache-2.0 |
| `log` | 0.4.34 | MIT OR Apache-2.0 |
| `lz4_flex` | 0.11.6 | MIT |
| `malloc_buf` | 0.0.6 | MIT |
| `matchers` | 0.2.0 | MIT |
| `memchr` | 2.8.3 | Unlicense OR MIT |
| `mime` | 0.3.17 | MIT OR Apache-2.0 |
| `mime_guess` | 2.0.5 | MIT |
| `mio` | 1.2.3 | MIT |
| `nu-ansi-term` | 0.50.3 | MIT |
| `num-traits` | 0.2.19 | MIT OR Apache-2.0 |
| `objc` | 0.2.7 | MIT OR Apache-2.0 |
| `objc_id` | 0.1.1 | MIT |
| `objc-foundation` | 0.1.1 | MIT OR Apache-2.0 |
| `once_cell` | 1.21.4 | MIT OR Apache-2.0 |
| `once_cell_polyfill` | 1.70.2 | MIT OR Apache-2.0 |
| `opaque-debug` | 0.3.1 | MIT OR Apache-2.0 |
| `padlock` | 0.2.0 | MIT OR Apache-2.0 |
| `parking_lot` | 0.12.5 | MIT OR Apache-2.0 |
| `parking_lot_core` | 0.9.12 | MIT OR Apache-2.0 |
| `password-hash` | 0.5.0 | MIT OR Apache-2.0 |
| `percent-encoding` | 2.3.2 | MIT OR Apache-2.0 |
| `pin-project-lite` | 0.2.17 | Apache-2.0 OR MIT |
| `pkg-config` | 0.3.34 | MIT OR Apache-2.0 |
| `polyval` | 0.6.2 | Apache-2.0 OR MIT |
| `potential_utf` | 0.1.6 | Unicode-3.0 |
| `ppv-lite86` | 0.2.21 | MIT OR Apache-2.0 |
| `proc-macro2` | 1.0.107 | MIT OR Apache-2.0 |
| `quote` | 1.0.47 | MIT OR Apache-2.0 |
| `rand` | 0.8.8 | MIT OR Apache-2.0 |
| `rand_chacha` | 0.3.1 | MIT OR Apache-2.0 |
| `rand_core` | 0.6.4 | MIT OR Apache-2.0 |
| `redox_syscall` | 0.5.18 | MIT OR Apache-2.0 |
| `r-efi` | 6.0.0 | MIT OR Apache-2.0 |
| `regex-automata` | 0.4.18 | MIT OR Apache-2.0 |
| `regex-syntax` | 0.8.11 | MIT OR Apache-2.0 |
| `rpassword` | 7.5.4 | Apache-2.0 |
| `rtoolbox` | 0.0.6 | Apache-2.0 |
| `rusqlite` | 0.32.1 | MIT |
| `rustversion` | 1.0.23 | MIT OR Apache-2.0 |
| `scopeguard` | 1.2.0 | MIT OR Apache-2.0 |
| `serde` | 1.0.229 | MIT OR Apache-2.0 |
| `serde_core` | 1.0.229 | MIT OR Apache-2.0 |
| `serde_derive` | 1.0.229 | MIT OR Apache-2.0 |
| `serde_json` | 1.0.151 | MIT OR Apache-2.0 |
| `sha1` | 0.10.7 | MIT OR Apache-2.0 |
| `sharded-slab` | 0.1.7 | MIT |
| `shlex` | 2.0.1 | MIT OR Apache-2.0 |
| `signal-hook-registry` | 1.4.8 | MIT OR Apache-2.0 |
| `slab` | 0.4.12 | MIT |
| `smallvec` | 1.16.0 | MIT OR Apache-2.0 |
| `socket2` | 0.6.5 | MIT OR Apache-2.0 |
| `stable_deref_trait` | 1.2.1 | MIT OR Apache-2.0 |
| `strsim` | 0.11.1 | MIT |
| `subtle` | 2.6.1 | BSD-3-Clause |
| `syn` | 3.0.5 | MIT OR Apache-2.0 |
| `syn` | 2.0.119 | MIT OR Apache-2.0 |
| `synstructure` | 0.13.2 | MIT |
| `thiserror` | 2.0.20 | MIT OR Apache-2.0 |
| `thiserror-impl` | 2.0.20 | MIT OR Apache-2.0 |
| `thread_local` | 1.1.10 | MIT OR Apache-2.0 |
| `tinystr` | 0.8.4 | Unicode-3.0 |
| `tinyvec` | 1.13.2 | Zlib OR Apache-2.0 OR MIT |
| `tinyvec_macros` | 0.1.1 | MIT OR Apache-2.0 OR Zlib |
| `tokio` | 1.53.1 | MIT |
| `tokio-macros` | 2.7.2 | MIT |
| `tokio-util` | 0.7.19 | MIT |
| `tracing` | 0.1.44 | MIT |
| `tracing-attributes` | 0.1.31 | MIT |
| `tracing-core` | 0.1.36 | MIT |
| `tracing-log` | 0.2.0 | MIT |
| `tracing-subscriber` | 0.3.23 | MIT |
| `tray-item` | 0.10.0 | MIT |
| `twox-hash` | 2.1.4 | MIT |
| `typenum` | 1.20.1 | MIT OR Apache-2.0 |
| `unicase` | 2.9.0 | MIT OR Apache-2.0 |
| `unicode-ident` | 1.0.24 | (MIT OR Apache-2.0) AND Unicode-3.0 |
| `unicode-normalization` | 0.1.25 | MIT OR Apache-2.0 |
| `universal-hash` | 0.5.1 | MIT OR Apache-2.0 |
| `url` | 2.5.8 | MIT OR Apache-2.0 |
| `utf8_iter` | 1.0.4 | Apache-2.0 OR MIT |
| `utf8parse` | 0.2.2 | Apache-2.0 OR MIT |
| `uuid` | 1.26.0 | Apache-2.0 OR MIT |
| `valuable` | 0.1.1 | MIT OR Apache-2.0 |
| `vcpkg` | 0.2.15 | MIT/Apache-2.0 |
| `version_check` | 0.9.5 | MIT/Apache-2.0 |
| `wasi` | 0.11.1+wasi-snapshot-preview1 | MIT OR Apache-2.0 |
| `wasm-bindgen` | 0.2.128 | MIT OR Apache-2.0 |
| `wasm-bindgen-macro` | 0.2.128 | MIT OR Apache-2.0 |
| `wasm-bindgen-macro-support` | 0.2.128 | MIT OR Apache-2.0 |
| `wasm-bindgen-shared` | 0.2.128 | MIT OR Apache-2.0 |
| `windows_aarch64_gnullvm` | 0.52.6 | MIT OR Apache-2.0 |
| `windows_aarch64_msvc` | 0.52.6 | MIT OR Apache-2.0 |
| `windows_i686_gnu` | 0.52.6 | MIT OR Apache-2.0 |
| `windows_i686_gnullvm` | 0.52.6 | MIT OR Apache-2.0 |
| `windows_i686_msvc` | 0.52.6 | MIT OR Apache-2.0 |
| `windows_x86_64_gnu` | 0.52.6 | MIT OR Apache-2.0 |
| `windows_x86_64_gnullvm` | 0.52.6 | MIT OR Apache-2.0 |
| `windows_x86_64_msvc` | 0.52.6 | MIT OR Apache-2.0 |
| `windows-core` | 0.62.2 | MIT OR Apache-2.0 |
| `windows-implement` | 0.60.2 | MIT OR Apache-2.0 |
| `windows-interface` | 0.59.3 | MIT OR Apache-2.0 |
| `windows-link` | 0.2.1 | MIT OR Apache-2.0 |
| `windows-result` | 0.4.1 | MIT OR Apache-2.0 |
| `windows-strings` | 0.5.1 | MIT OR Apache-2.0 |
| `windows-sys` | 0.61.2 | MIT OR Apache-2.0 |
| `windows-sys` | 0.52.0 | MIT OR Apache-2.0 |
| `windows-targets` | 0.52.6 | MIT OR Apache-2.0 |
| `writeable` | 0.6.4 | Unicode-3.0 |
| `xml` | 1.4.0 | MIT |
| `xml-rs` | 1.0.0 | MIT |
| `xmltree` | 0.12.0 | MIT |
| `yoke` | 0.8.3 | Unicode-3.0 |
| `yoke-derive` | 0.8.2 | Unicode-3.0 |
| `zerocopy` | 0.8.56 | BSD-2-Clause OR Apache-2.0 OR MIT |
| `zerocopy-derive` | 0.8.56 | MIT OR Apache-2.0 |
| `zerofrom` | 0.1.8 | Unicode-3.0 |
| `zerofrom-derive` | 0.1.7 | Unicode-3.0 |
| `zeroize` | 1.9.0 | Apache-2.0 OR MIT |
| `zeroize_derive` | 1.5.0 | Apache-2.0 OR MIT |
| `zerotrie` | 0.2.5 | Unicode-3.0 |
| `zerovec` | 0.11.8 | Unicode-3.0 |
| `zerovec-derive` | 0.11.6 | Unicode-3.0 |
| `zmij` | 1.0.23 | MIT |
---
## 2. License Texts
### The MIT License
```text
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
### Apache License, Version 2.0
```text
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work.
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work.
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file.
```
### 3-Clause BSD License
```text
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
```
### SQLite Copyright & Blessing (Public Domain)
```text
The author disclaims copyright to this source code. In place of a legal notice,
here is a blessing:
May you do good and not evil.
May you find forgiveness for yourself and forgive others.
May you share freely, never taking more than you give.
```
+25
View File
@@ -0,0 +1,25 @@
{
"version": "0.4.1",
"description": "Verschlüsselter Ein-Datei-Container unter Windows im reinen Userland via WebDAV",
"homepage": "https://gitea.pansi.eu/harald/sanctum",
"license": "MIT",
"architecture": {
"64bit": {
"url": "https://gitea.pansi.eu/harald/sanctum/releases/download/v0.4.1/sanctum-v0.4.1-windows-x86_64.zip",
"hash": "dc0980312857505a078a3693d81a5d16853c3a94152f9104004dc6c4fd929bf7",
"bin": "sanctum.exe"
}
},
"checkver": {
"url": "https://gitea.pansi.eu/api/v1/repos/harald/sanctum/releases/latest",
"jsonpath": "$.tag_name",
"regex": "v([\\d.]+)"
},
"autoupdate": {
"architecture": {
"64bit": {
"url": "https://gitea.pansi.eu/harald/sanctum/releases/download/v$version/sanctum-v$version-windows-x86_64.zip"
}
}
}
}
+32
View File
@@ -0,0 +1,32 @@
# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.6.0.schema.json
PackageIdentifier: HaraldPansi.Sanctum
PackageVersion: 0.4.1
PackageName: Sanctum
Publisher: Harald Pansi
PublisherUrl: https://gitea.pansi.eu/harald
PublisherSupportUrl: https://gitea.pansi.eu/harald/sanctum/issues
Author: Harald Pansi
License: MIT
LicenseUrl: https://gitea.pansi.eu/harald/sanctum/src/branch/main/LICENSE
ShortDescription: Verschlüsselter Ein-Datei-Container unter Windows im reinen Userland via WebDAV
Description: Sanctum ist eine eigenständige, speichersichere CLI-Anwendung in Rust, die verschlüsselte Ein-Datei-Container (.sanctum) unter Windows 10/11 im reinen Userland verwaltet. 100% Userland, keine Adminrechte, keine Treiber.
Moniker: sanctum
Tags:
- encryption
- security
- privacy
- webdav
- container
- plausible-deniability
ReleaseNotesUrl: https://gitea.pansi.eu/harald/sanctum/releases/tag/v0.4.1
Installers:
- Architecture: x64
InstallerType: zip
NestedInstallerType: portable
NestedInstallerFiles:
- RelativeFilePath: sanctum.exe
PortableCommandAlias: sanctum
InstallerUrl: https://gitea.pansi.eu/harald/sanctum/releases/download/v0.4.1/sanctum-v0.4.1-windows-x86_64.zip
InstallerSha256: dc0980312857505a078a3693d81a5d16853c3a94152f9104004dc6c4fd929bf7
ManifestType: singleton
ManifestVersion: 1.6.0
+119
View File
@@ -0,0 +1,119 @@
param (
[switch]$SkipBuild = $false
)
$ErrorActionPreference = "Stop"
# Repository-Wurzelverzeichnis ermitteln
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$ProjectRoot = Split-Path -Parent $ScriptDir
Set-Location $ProjectRoot
# Toolchain-Pfade sicherstellen (Zig + Cargo Bin)
$ZigDir = "C:\Users\pansih\AppData\Local\Microsoft\WinGet\Packages\zig.zig_Microsoft.Winget.Source_8wekyb3d8bbwe\zig-x86_64-windows-0.16.0"
$env:PATH = "$ZigDir;C:\Users\pansih\.cargo\bin;C:\Windows\System32;" + $env:PATH
# Version aus Cargo.toml auslesen
$CargoToml = Get-Content (Join-Path $ProjectRoot "Cargo.toml") -Raw
if ($CargoToml -match 'version\s*=\s*"([^"]+)"') {
$Version = $matches[1]
} else {
Write-Error "Konnte Versionsnummer nicht aus Cargo.toml ermitteln."
exit 1
}
Write-Host "============================================================" -ForegroundColor Cyan
Write-Host " Sanctum Linux Release Packaging v$Version (x86_64 musl)" -ForegroundColor Cyan
Write-Host "============================================================" -ForegroundColor Cyan
# 1. Linux Release-Binary bauen
$Target = "x86_64-unknown-linux-musl"
$BinaryPath = Join-Path $ProjectRoot "target\$Target\release\sanctum"
if (-not $SkipBuild -or -not (Test-Path $BinaryPath)) {
Write-Host "`n[1/3] Kompiliere statisches Linux-Binary via cargo-zigbuild..." -ForegroundColor Yellow
cargo-zigbuild.exe zigbuild --target $Target --release
if ($LASTEXITCODE -ne 0) {
Write-Error "Linux-Build fehlgeschlagen!"
exit $LASTEXITCODE
}
Write-Host "[OK] Linux-Binary erfolgreich gebaut." -ForegroundColor Green
} else {
Write-Host "`n[1/3] Build uebersprungen (-SkipBuild)." -ForegroundColor DarkYellow
}
# 2. Release-Verzeichnisstruktur vorbereiten
Write-Host "`n[2/3] Erstelle Linux-Distributionspaket..." -ForegroundColor Yellow
$DistDir = Join-Path $ProjectRoot "dist"
if (-not (Test-Path $DistDir)) {
New-Item -ItemType Directory -Path $DistDir -Force | Out-Null
}
$PackageName = "sanctum-v$Version-linux-x86_64"
$StagingDir = Join-Path $DistDir $PackageName
$TarGzFile = Join-Path $DistDir "$PackageName.tar.gz"
if (Test-Path $StagingDir) {
Remove-Item $StagingDir -Recurse -Force
}
New-Item -ItemType Directory -Path $StagingDir -Force | Out-Null
# Dateien kopieren
Copy-Item $BinaryPath -Destination (Join-Path $StagingDir "sanctum") -Force
Copy-Item (Join-Path $ProjectRoot "README.md") -Destination $StagingDir -Force
Copy-Item (Join-Path $ProjectRoot "LICENSE") -Destination $StagingDir -Force
Copy-Item (Join-Path $ProjectRoot "CHANGELOG.md") -Destination $StagingDir -Force
Copy-Item (Join-Path $ProjectRoot "INSTALL.md") -Destination $StagingDir -Force
Copy-Item (Join-Path $ProjectRoot "QUICKSTART.md") -Destination $StagingDir -Force
Copy-Item (Join-Path $ProjectRoot "LEGAL.md") -Destination $StagingDir -Force
Copy-Item (Join-Path $ProjectRoot "THIRD_PARTY_LICENSES.md") -Destination $StagingDir -Force
# .tar.gz Archiv erstellen mit Windows nativem bsdtar
if (Test-Path $TarGzFile) {
Remove-Item $TarGzFile -Force
}
tar.exe -czf $TarGzFile -C $DistDir $PackageName
if ($LASTEXITCODE -ne 0) {
Write-Error "Fehler beim Erstellen des tar.gz-Archivs!"
exit $LASTEXITCODE
}
# Staging-Verzeichnis bereinigen
Remove-Item $StagingDir -Recurse -Force
Write-Host "[OK] Archiv erstellt: $TarGzFile" -ForegroundColor Green
# 3. SHA-256 Pruefsummen berechnen und aktualisieren
Write-Host "`n[3/3] Aktualisiere SHA-256 Pruefsummen..." -ForegroundColor Yellow
$ChecksumFile = Join-Path $DistDir "SHA256SUMS.txt"
$TarHash = (Get-FileHash -Path $TarGzFile -Algorithm SHA256).Hash.ToLower()
$ElfHash = (Get-FileHash -Path $BinaryPath -Algorithm SHA256).Hash.ToLower()
# Existierende Pruefsummen lesen und um Linux-Assets ergaenzen/aktualisieren
$ExistingLines = if (Test-Path $ChecksumFile) {
Get-Content $ChecksumFile | Where-Object { $_ -notmatch "$PackageName\.tar\.gz" -and $_ -notmatch "\s+sanctum$" }
} else {
@()
}
$AllLines = @()
$AllLines += $ExistingLines
$AllLines += "$TarHash $PackageName.tar.gz"
$AllLines += "$ElfHash sanctum"
$AllLines | Set-Content -Path $ChecksumFile -Encoding utf8
$TarSizeMB = [math]::Round((Get-Item $TarGzFile).Length / 1MB, 2)
$ElfSizeMB = [math]::Round((Get-Item $BinaryPath).Length / 1MB, 2)
Write-Host "`n============================================================" -ForegroundColor Green
Write-Host " Sanctum Linux Release v$Version erfolgreich gepackt!" -ForegroundColor Green
Write-Host "============================================================" -ForegroundColor Green
Write-Host " Archiv: $TarGzFile ($TarSizeMB MB)"
Write-Host " TAR SHA-256: $TarHash"
Write-Host " ELF SHA-256: $ElfHash"
Write-Host " Checksum-File: $ChecksumFile"
Write-Host "`nBereit fuer Gitea Release."
+20 -3
View File
@@ -55,15 +55,22 @@ $PackageName = "sanctum-v$Version-windows-x86_64"
$StagingDir = Join-Path $DistDir $PackageName
$ZipFile = Join-Path $DistDir "$PackageName.zip"
if (Test-Path $DistDir) {
Remove-Item $DistDir -Recurse -Force
if (-not (Test-Path $DistDir)) {
New-Item -ItemType Directory -Path $DistDir -Force | Out-Null
}
if (Test-Path $StagingDir) {
Remove-Item $StagingDir -Recurse -Force
}
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 "INSTALL.md") (Join-Path $StagingDir "INSTALL.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")
Copy-Item (Join-Path $ProjectRoot "CHANGELOG.md") (Join-Path $StagingDir "CHANGELOG.md")
Copy-Item (Join-Path $ProjectRoot "assets") (Join-Path $StagingDir "assets") -Recurse
@@ -80,7 +87,17 @@ $ZipHash = (Get-FileHash -Path $ZipFile -Algorithm SHA256).Hash.ToLower()
$ExeHash = (Get-FileHash -Path $ExeSource -Algorithm SHA256).Hash.ToLower()
$ChecksumFile = Join-Path $DistDir "SHA256SUMS.txt"
@("$ZipHash $PackageName.zip", "$ExeHash sanctum.exe") | Set-Content -Path $ChecksumFile -Encoding utf8
$ExistingLines = if (Test-Path $ChecksumFile) {
Get-Content $ChecksumFile | Where-Object { $_ -notmatch "$PackageName\.zip" -and $_ -notmatch "\s+sanctum\.exe$" }
} else {
@()
}
$AllLines = @()
$AllLines += "$ZipHash $PackageName.zip"
$AllLines += "$ExeHash sanctum.exe"
$AllLines += $ExistingLines
$AllLines | Set-Content -Path $ChecksumFile -Encoding utf8
Write-Host "[OK] Pruefsummen in SHA256SUMS.txt gespeichert." -ForegroundColor Green
+47 -21
View File
@@ -108,29 +108,55 @@ if (-not $Release) {
$UploadUrl = "$GiteaUrl/api/v1/repos/$Owner/$Repo/releases/$($Release.id)/assets"
$AuthHeader = "Authorization: token $Token"
# ZIP Asset hochladen
Write-Host "Lade sanctum-$TagName-windows-x86_64.zip hoch..." -ForegroundColor Cyan
$ZipPathNorm = $ZipFile.Replace('\', '/')
$ZipUrl = "$UploadUrl?name=sanctum-$TagName-windows-x86_64.zip"
$ZipResult = & curl.exe -s -X POST $ZipUrl -H $AuthHeader -F "attachment=@$ZipPathNorm"
Write-Host "[OK] sanctum-$TagName-windows-x86_64.zip hochgeladen." -ForegroundColor Green
# Hilfsfunktion zum sauberen Hochladen/Ersetzen von Assets
function Upload-ReleaseAsset {
param (
[string]$FilePath,
[string]$AssetName
)
if (-not (Test-Path $FilePath)) {
return
}
Write-Host "Lade $AssetName hoch..." -ForegroundColor Cyan
# Vorhandenes Asset mit gleichem Namen ermitteln und ggf. loeschen
$ExistingAssets = try {
Invoke-RestMethod -Uri $UploadUrl -Headers $Headers -Method Get
} catch {
@()
}
$Duplicate = $ExistingAssets | Where-Object { $_.name -eq $AssetName }
if ($Duplicate) {
Write-Host " [i] Ersetze existierendes Asset '$AssetName' (ID: $($Duplicate.id))..." -ForegroundColor DarkYellow
$DeleteUrl = "$GiteaUrl/api/v1/repos/$Owner/$Repo/releases/$($Release.id)/assets/$($Duplicate.id)"
Invoke-RestMethod -Uri $DeleteUrl -Headers $Headers -Method Delete | Out-Null
}
$PathNorm = $FilePath.Replace('\', '/')
$TargetUrl = "${UploadUrl}?name=$AssetName"
$Result = & curl.exe -sS -X POST $TargetUrl -H $AuthHeader -F "attachment=@$PathNorm"
if ($LASTEXITCODE -ne 0) {
Write-Error "Upload von $AssetName fehlgeschlagen: $Result"
exit 1
}
Write-Host "[OK] $AssetName erfolgreich hochgeladen." -ForegroundColor Green
}
# Windows-Assets hochladen
Upload-ReleaseAsset -FilePath $ZipFile -AssetName "sanctum-$TagName-windows-x86_64.zip"
$ExeFile = Join-Path $ProjectRoot "target\release\sanctum.exe"
Upload-ReleaseAsset -FilePath $ExeFile -AssetName "sanctum.exe"
# Linux-Assets hochladen
$LinuxTar = Join-Path $DistDir "sanctum-$TagName-linux-x86_64.tar.gz"
Upload-ReleaseAsset -FilePath $LinuxTar -AssetName "sanctum-$TagName-linux-x86_64.tar.gz"
$LinuxElf = Join-Path $ProjectRoot "target\x86_64-unknown-linux-musl\release\sanctum"
Upload-ReleaseAsset -FilePath $LinuxElf -AssetName "sanctum"
# SHA256SUMS.txt hochladen
Write-Host "Lade SHA256SUMS.txt hoch..." -ForegroundColor Cyan
$SumPathNorm = $ChecksumFile.Replace('\', '/')
$SumUrl = "$UploadUrl?name=SHA256SUMS.txt"
$SumResult = & curl.exe -s -X POST $SumUrl -H $AuthHeader -F "attachment=@$SumPathNorm"
Write-Host "[OK] SHA256SUMS.txt hochgeladen." -ForegroundColor Green
# Standalone EXE hochladen
$ExeFile = Join-Path $ProjectRoot "target\release\sanctum.exe"
if (Test-Path $ExeFile) {
Write-Host "Lade sanctum.exe hoch..." -ForegroundColor Cyan
$ExePathNorm = $ExeFile.Replace('\', '/')
$ExeUrl = "$UploadUrl?name=sanctum.exe"
$ExeResult = & curl.exe -s -X POST $ExeUrl -H $AuthHeader -F "attachment=@$ExePathNorm"
Write-Host "[OK] sanctum.exe hochgeladen." -ForegroundColor Green
}
Upload-ReleaseAsset -FilePath $ChecksumFile -AssetName "SHA256SUMS.txt"
Write-Host "`n============================================================" -ForegroundColor Green
+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;
+14 -1
View File
@@ -68,10 +68,14 @@ enum Commands {
#[arg(short, long)]
path: PathBuf,
/// Laufwerksbuchstabe (z. B. 'S' oder 'S:', optional; wählt standardmäßig automatisch das nächste freie Laufwerk)
/// Laufwerksbuchstabe (z. B. 'S' oder 'S:', optional; wählt unter Windows standardmäßig das nächste freie Laufwerk)
#[arg(short, long)]
drive: Option<String>,
/// Optionaler lokaler Mount-Pfad unter Linux/macOS (z. B. '/mnt/sanctum' oder '~/vault')
#[arg(short = 'm', long = "mount-point", value_name = "DIR")]
mount_point: Option<PathBuf>,
/// Optionaler TCP-Port für den lokalen WebDAV-Server (Standard: 8443)
#[arg(long)]
port: Option<u16>,
@@ -904,6 +908,7 @@ async fn run() -> Result<()> {
Commands::Mount {
path,
drive,
mount_point,
port,
recovery_key,
no_open,
@@ -916,6 +921,8 @@ async fn run() -> Result<()> {
let drive_char = match drive {
Some(ref d) => parse_drive_letter(d)?,
None => {
#[cfg(windows)]
{
let auto_drive = sanctum::windows::find_next_available_drive()?;
if !stealth {
println!(
@@ -926,6 +933,11 @@ async fn run() -> Result<()> {
}
auto_drive
}
#[cfg(not(windows))]
{
'Z'
}
}
};
let auth = if let Some(key) = recovery_key {
let actual_key = if key.trim().is_empty() {
@@ -953,6 +965,7 @@ async fn run() -> Result<()> {
mount_container(
&path,
drive_char,
mount_point.as_deref(),
port,
auth,
open_explorer,
+170 -15
View File
@@ -32,8 +32,10 @@ pub fn format_drive(drive_letter: char) -> String {
format!("{}:", drive_letter.to_ascii_uppercase())
}
/// Trennt ein Windows-Netzlaufwerk via `net use <DRIVE>: /delete /y`.
/// Trennt ein Windows-Netzlaufwerk via `net use <DRIVE>: /delete /y` bzw. Unix-Mountpoint via `gio mount -u`.
pub fn unmount_drive(drive_letter: char) -> Result<()> {
#[cfg(windows)]
{
let drive_str = format_drive(drive_letter);
let output = Command::new("net")
@@ -53,10 +55,24 @@ pub fn unmount_drive(drive_letter: char) -> Result<()> {
}
Ok(())
}
#[cfg(unix)]
{
let _ = drive_letter;
Ok(())
}
#[cfg(not(any(windows, unix)))]
{
let _ = drive_letter;
Ok(())
}
}
/// Bindet ein Windows-Netzlaufwerk via `net use <DRIVE>: http://127.0.0.1:<PORT>/<TOKEN>/ /persistent:no` ein.
fn run_net_use_mount(drive_str: &str, port: u16, session_token: &str) -> Result<()> {
/// Bindet ein Windows-Netzlaufwerk via `net use <DRIVE>: http://127.0.0.1:<PORT>/<TOKEN>/ /persistent:no` ein
/// bzw. unter Unix via `gio mount dav://127.0.0.1:<PORT>/<TOKEN>/`.
fn run_mount_command(drive_str: &str, port: u16, session_token: &str) -> Result<()> {
#[cfg(windows)]
{
let url = format!("http://127.0.0.1:{}/{}/", port, session_token);
let mut output = Command::new("net")
@@ -107,12 +123,28 @@ fn run_net_use_mount(drive_str: &str, port: u16, session_token: &str) -> Result<
}
Ok(())
}
#[cfg(unix)]
{
let dav_url = format!("dav://127.0.0.1:{}/{}/", port, session_token);
let _ = Command::new("gio")
.args(["mount", &dav_url])
.output();
let _ = drive_str;
Ok(())
}
#[cfg(not(any(windows, unix)))]
{
let _ = (drive_str, port, session_token);
Ok(())
}
}
/// Startet den WebDAV-Server für den Sanctum-Container und bindet ihn als Netzlaufwerk ein.
/// Startet den WebDAV-Server für den Sanctum-Container und bindet ihn als Netzlaufwerk (Windows) bzw. VFS (Linux) ein.
pub async fn mount_container(
container_path: &Path,
drive_letter: char,
mount_point: Option<&Path>,
requested_port: Option<u16>,
auth: ContainerAuth,
open_explorer: bool,
@@ -123,6 +155,10 @@ pub async fn mount_container(
stealth: bool,
) -> Result<()> {
let drive_str = format_drive(drive_letter);
let _ = mount_point;
let _ = open_explorer;
let _ = enable_tray;
let _ = lock_on_screen_lock;
if !container_path.exists() {
bail!(
@@ -224,6 +260,7 @@ pub async fn mount_container(
let bound_port = bound_addr.port();
if !stealth {
#[cfg(windows)]
ui::step(
4,
4,
@@ -233,6 +270,16 @@ pub async fn mount_container(
bound_port, drive_str
),
);
#[cfg(not(windows))]
ui::step(
4,
4,
"🌐",
&format!(
"Starte WebDAV-Server auf Port {} & initialisiere VFS...",
bound_port
),
);
}
let (shutdown_tx, shutdown_rx) = watch::channel(false);
@@ -245,20 +292,22 @@ pub async fn mount_container(
shutdown_rx,
));
// Netzlaufwerk einbinden
if let Err(e) = run_net_use_mount(&drive_str, bound_port, &session_token) {
// Netzlaufwerk bzw. Verzeichnis einbinden
if let Err(e) = run_mount_command(&drive_str, bound_port, &session_token) {
let _ = shutdown_tx.send(true);
let _ = server_handle.await;
return Err(e);
}
// Optional automatisch im Windows Explorer öffnen (visuelle Parität für Decoy und Hidden Vault)
#[cfg(windows)]
if open_explorer {
let _ = crate::windows::open_in_explorer(drive_letter);
}
// System-Tray Initialisierung
let (tray_shutdown_tx, mut tray_shutdown_rx) = tokio::sync::mpsc::channel::<()>(1);
// System-Tray Initialisierung (nur Windows)
#[cfg(windows)]
let (_tray_shutdown_tx, mut tray_shutdown_rx) = tokio::sync::mpsc::channel::<()>(1);
#[cfg(windows)]
let _tray = if enable_tray {
let icon_source = crate::windows::get_default_system_icon()
@@ -276,7 +325,7 @@ pub async fn mount_container(
let _ = tray.add_menu_item("Im Explorer öffnen", move || {
let _ = crate::windows::open_in_explorer(dl);
});
let s_tx = tray_shutdown_tx.clone();
let s_tx = _tray_shutdown_tx.clone();
let _ = tray.add_menu_item("Trennen & Beenden", move || {
let _ = s_tx.blocking_send(());
});
@@ -315,8 +364,10 @@ pub async fn mount_container(
}
}
// Windows-Sitzungssperre (Win + L Auto-Lock)
// Windows-Sitzungssperre (Win + L Auto-Lock, nur Windows)
#[cfg(windows)]
let (session_lock_tx, mut session_lock_rx) = tokio::sync::mpsc::channel::<()>(1);
#[cfg(windows)]
let _session_monitor = if lock_on_screen_lock {
match crate::windows::start_session_lock_monitor(session_lock_tx) {
Ok(guard) => Some(guard),
@@ -330,7 +381,10 @@ pub async fn mount_container(
};
if stealth {
#[cfg(windows)]
println!("Sanctum: Netzlaufwerk {} bereit.", drive_str);
#[cfg(not(windows))]
println!("Sanctum: WebDAV-Server auf Port {} bereit.", bound_port);
} else {
println!();
println!("┌─────────────────────────────────────────────────────────────┐");
@@ -338,30 +392,63 @@ pub async fn mount_container(
println!("└─────────────────────────────────────────────────────────────┘");
println!();
println!(" • Container: {}", container_path.display());
#[cfg(windows)]
println!(" • Netzlaufwerk: {} (im Windows Explorer bereit)", ui::cyan(&drive_str));
#[cfg(not(windows))]
{
if let Some(mp) = mount_point {
println!(" • Mountpoint: {}", ui::cyan(&mp.display().to_string()));
} else {
println!(" • Modus: WebDAV Userland-VFS");
}
}
println!(" • WebDAV-URL: http://127.0.0.1:{}/{}/ (Session-Token geschützt)", bound_port, session_token);
#[cfg(not(windows))]
{
println!(" • gio Befehl: gio mount dav://127.0.0.1:{}/{}/", bound_port, session_token);
if let Some(mp) = mount_point {
println!(" • davfs2: mount -t davfs http://127.0.0.1:{}/{}/ {}", bound_port, session_token, mp.display());
}
}
if let Some(secs) = idle_timeout {
println!(" • Auto-Lock: Inaktivität nach {}s", secs);
}
#[cfg(windows)]
if lock_on_screen_lock {
println!(" • Sitzung: Automatisches Sperren bei Win + L aktiv");
}
if anti_leak {
#[cfg(windows)]
println!(" • Anti-Leak: Explorer-Metadatenfilter aktiv (Thumbs.db, desktop.ini blockiert)");
#[cfg(not(windows))]
println!(" • Anti-Leak: VFS-Metadatenfilter aktiv (.directory, .Trash, desktop.ini blockiert)");
}
#[cfg(windows)]
if enable_tray {
println!(" • System-Tray: Icon aktiv (Rechtsklick für Explorer/Trennen)");
}
println!();
#[cfg(windows)]
println!(" [{}] Drücke [Ctrl+C] oder nutze das Tray-Icon zum Beenden.", ui::yellow("Tipp"));
#[cfg(not(windows))]
println!(" [{}] Drücke [Ctrl+C] zum sicheren Beenden.", ui::yellow("Tipp"));
println!();
}
// Windows Console Close Monitor (CTRL_CLOSE_EVENT / CTRL_SHUTDOWN_EVENT)
// Windows Console Close Monitor (CTRL_CLOSE_EVENT / CTRL_SHUTDOWN_EVENT, nur Windows)
#[cfg(windows)]
let (console_close_tx, mut console_close_rx) = tokio::sync::mpsc::channel::<()>(1);
#[cfg(windows)]
let _console_guard = crate::windows::start_console_ctrl_monitor(console_close_tx, drive_letter).ok();
// Unix Signale (SIGTERM, SIGHUP)
#[cfg(unix)]
let mut sigterm = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()).ok();
#[cfg(unix)]
let mut sighup = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::hangup()).ok();
// Warten auf Beendigungssignal (Ctrl+C, Tray-Klick, Inaktivität, Win+L, Konsolenfenster-Schließen)
#[cfg(windows)]
tokio::select! {
res = tokio::signal::ctrl_c() => {
let _ = res;
@@ -370,25 +457,81 @@ pub async fn mount_container(
println!(" {} Beendigungssignal (Ctrl+C) empfangen.", ui::yellow("[!]"));
}
}
_ = console_close_rx.recv() => {
Some(()) = console_close_rx.recv() => {
if !stealth {
println!();
println!(" {} Konsolenfenster wird geschlossen — sichere Trennung ausgeführt!", ui::yellow("[!]"));
}
}
_ = tray_shutdown_rx.recv() => {
Some(()) = tray_shutdown_rx.recv() => {
if !stealth {
println!();
println!(" {} Beendigungssignal aus System-Tray empfangen.", ui::yellow("[!]"));
}
}
_ = session_lock_rx.recv() => {
Some(()) = session_lock_rx.recv() => {
if !stealth {
println!();
println!(" {} Windows-Sitzung gesperrt (Win + L) — Auto-Lock ausgelöst!", ui::yellow("[!]"));
}
}
_ = idle_shutdown_rx.recv() => {
Some(()) = idle_shutdown_rx.recv() => {
if !stealth {
println!();
println!(" {} Inaktivitäts-Timeout erreicht — Auto-Lock ausgelöst!", ui::yellow("[!]"));
}
}
}
#[cfg(unix)]
tokio::select! {
res = tokio::signal::ctrl_c() => {
let _ = res;
if !stealth {
println!();
println!(" {} Beendigungssignal (Ctrl+C) empfangen.", ui::yellow("[!]"));
}
}
_ = async {
match sigterm.as_mut() {
Some(s) => { s.recv().await; }
None => { std::future::pending::<()>().await; }
}
} => {
if !stealth {
println!();
println!(" {} Beendigungssignal (SIGTERM) empfangen.", ui::yellow("[!]"));
}
}
_ = async {
match sighup.as_mut() {
Some(s) => { s.recv().await; }
None => { std::future::pending::<()>().await; }
}
} => {
if !stealth {
println!();
println!(" {} Beendigungssignal (SIGHUP) empfangen.", ui::yellow("[!]"));
}
}
Some(()) = idle_shutdown_rx.recv() => {
if !stealth {
println!();
println!(" {} Inaktivitäts-Timeout erreicht — Auto-Lock ausgelöst!", ui::yellow("[!]"));
}
}
}
#[cfg(not(any(windows, unix)))]
tokio::select! {
res = tokio::signal::ctrl_c() => {
let _ = res;
if !stealth {
println!();
println!(" {} Beendigungssignal (Ctrl+C) empfangen.", ui::yellow("[!]"));
}
}
Some(()) = idle_shutdown_rx.recv() => {
if !stealth {
println!();
println!(" {} Inaktivitäts-Timeout erreicht — Auto-Lock ausgelöst!", ui::yellow("[!]"));
@@ -406,8 +549,13 @@ pub async fn mount_container(
}
}
let _ = db.checkpoint();
#[cfg(windows)]
println!("Sanctum: Laufwerk {} getrennt und geschlossen.", drive_str);
#[cfg(not(windows))]
println!("Sanctum: Container geschlossen und WebDAV-Server beendet.");
} else {
#[cfg(windows)]
{
print!(" {} Trenne Windows-Netzlaufwerk {} ... ", ui::dim("[-]"), drive_str);
let _ = std::io::Write::flush(&mut std::io::stdout());
@@ -417,6 +565,13 @@ pub async fn mount_container(
} else {
println!("{}", ui::green("OK"));
}
}
#[cfg(not(windows))]
{
print!(" {} Beende WebDAV-Server ... ", ui::dim("[-]"));
let _ = std::io::Write::flush(&mut std::io::stdout());
println!("{}", ui::green("OK"));
}
// HTTP Server beenden
let _ = shutdown_tx.send(true);
+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::*;
+133 -17
View File
@@ -1,5 +1,6 @@
use std::process::Command;
#[allow(unused_imports)]
use anyhow::{bail, Context, Result};
/// Ermittelt den nächsten verfügbaren Windows-Laufwerksbuchstaben (von 'Z' rückwärts bis 'D').
@@ -32,14 +33,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,10 +122,19 @@ 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).
/// Registriert `.sanctum`-Containerdateien im Windows Explorer für den aktuellen Benutzer bzw. unter Linux via Freedesktop.
pub fn register_explorer_integration() -> Result<()> {
#[cfg(windows)]
{
let current_exe = std::env::current_exe()
.context("Konnte den Pfad zur aktuellen sanctum.exe nicht ermitteln")?;
let exe_str = current_exe.display().to_string();
@@ -98,14 +162,14 @@ pub fn register_explorer_integration() -> Result<()> {
(
r"HKCU\Software\Classes\Sanctum.Container\shell\open",
"",
"Als Laufwerk einbinden",
"In Sanctum öffnen",
),
(
r"HKCU\Software\Classes\Sanctum.Container\shell\open\command",
"",
&format!("\"{exe_str}\" mount --path \"%1\""),
&format!("\"{exe_str}\" mount \"%1\""),
),
// 5. Kontextmenü-Aktion: Integritätsprüfung
// 5. Kontextmenü-Aktion: Verify / FSCK
(
r"HKCU\Software\Classes\Sanctum.Container\shell\verify",
"",
@@ -114,18 +178,18 @@ pub fn register_explorer_integration() -> Result<()> {
(
r"HKCU\Software\Classes\Sanctum.Container\shell\verify\command",
"",
&format!("\"{exe_str}\" verify --path \"%1\""),
&format!("cmd /k \"\"{exe_str}\" verify \"%1\"\""),
),
// 6. Kontextmenü-Aktion: Header sichern
// 6. Kontextmenü-Aktion: Header-Backup
(
r"HKCU\Software\Classes\Sanctum.Container\shell\backup_header",
r"HKCU\Software\Classes\Sanctum.Container\shell\backup",
"",
"Header sichern",
"Header sichern (Disaster Recovery)",
),
(
r"HKCU\Software\Classes\Sanctum.Container\shell\backup_header\command",
r"HKCU\Software\Classes\Sanctum.Container\shell\backup\command",
"",
&format!("\"{exe_str}\" backup-header --path \"%1\""),
&format!("cmd /k \"\"{exe_str}\" backup-header \"%1\"\""),
),
];
@@ -148,10 +212,32 @@ pub fn register_explorer_integration() -> Result<()> {
notify_shell_associations_changed();
Ok(())
}
#[cfg(not(windows))]
{
if let Some(home) = std::env::var_os("HOME") {
let home_path = std::path::PathBuf::from(home);
let apps_dir = home_path.join(".local/share/applications");
let mime_dir = home_path.join(".local/share/mime/packages");
let _ = std::fs::create_dir_all(&apps_dir);
let _ = std::fs::create_dir_all(&mime_dir);
let desktop_content = "[Desktop Entry]\nType=Application\nName=Sanctum\nComment=Verschlüsselter Ein-Datei-Container\nExec=sanctum mount %f\nIcon=security-high\nTerminal=true\nMimeType=application/x-sanctum;\nCategories=Utility;Security;\n";
let _ = std::fs::write(apps_dir.join("sanctum.desktop"), desktop_content);
let mime_content = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<mime-info xmlns=\"http://www.freedesktop.org/standards/shared-mime-info\">\n <mime-type type=\"application/x-sanctum\">\n <comment>Sanctum Verschlüsselter Container</comment>\n <glob pattern=\"*.sanctum\"/>\n </mime-type>\n</mime-info>\n";
let _ = std::fs::write(mime_dir.join("application-x-sanctum.xml"), mime_content);
notify_shell_associations_changed();
}
Ok(())
}
}
/// Entfernt die Windows-Explorer-Verknüpfungen aus der Benutzer-Registry (HKCU).
/// Entfernt die Windows-Explorer-Verknüpfungen aus der Benutzer-Registry (HKCU) bzw. unter Linux aus Freedesktop.
pub fn unregister_explorer_integration() -> Result<()> {
#[cfg(windows)]
{
let keys_to_delete = [
r"HKCU\Software\Classes\.sanctum",
r"HKCU\Software\Classes\Sanctum.Container",
@@ -165,6 +251,17 @@ pub fn unregister_explorer_integration() -> Result<()> {
notify_shell_associations_changed();
Ok(())
}
#[cfg(not(windows))]
{
if let Some(home) = std::env::var_os("HOME") {
let home_path = std::path::PathBuf::from(home);
let _ = std::fs::remove_file(home_path.join(".local/share/applications/sanctum.desktop"));
let _ = std::fs::remove_file(home_path.join(".local/share/mime/packages/application-x-sanctum.xml"));
notify_shell_associations_changed();
}
Ok(())
}
}
/// Lädt das Windows-Sicherheitsschild-Icon (IDI_SHIELD) oder Anwendungs-Icon für den System-Tray.
@@ -198,10 +295,9 @@ pub fn get_default_system_icon() -> Option<tray_item::IconSource> {
/// Guard zur Verwaltung des Hintergrundthreads für die Windows-Sitzungssperre.
/// Beim Droppen wird das Win32-Nachrichtenfenster geschlossen und der Thread sauber beendet.
#[cfg(windows)]
pub struct SessionLockGuard {
#[cfg(windows)]
hwnd: isize,
#[cfg(windows)]
join_handle: Option<std::thread::JoinHandle<()>>,
}
@@ -527,7 +623,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 +635,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 +664,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);
}