Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2286fad112 | ||
|
|
f0e5d6d26e | ||
|
|
73453a7eb1 | ||
|
|
badbe3bd18 |
@@ -8,7 +8,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
# SA-02 & SA-04: Entkoppelte Build- & Test-Umgebung ohne Zugriff auf Signatur-Secrets
|
# SA-02 & SA-04: Entkoppelte Build- & Test-Umgebung ohne Zugriff auf Signatur-Secrets
|
||||||
build:
|
build:
|
||||||
name: Build & Test (Windows x86_64)
|
name: Build & Test (Windows x86_64 & Linux musl)
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
@@ -18,39 +18,101 @@ jobs:
|
|||||||
uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de # master commit pinned
|
uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de # master commit pinned
|
||||||
with:
|
with:
|
||||||
toolchain: "1.85.0"
|
toolchain: "1.85.0"
|
||||||
targets: x86_64-pc-windows-msvc
|
targets: x86_64-pc-windows-msvc,x86_64-unknown-linux-musl
|
||||||
|
|
||||||
|
- name: Install cargo-zigbuild (pinned)
|
||||||
|
run: cargo install cargo-zigbuild --locked --version 0.23.4
|
||||||
|
|
||||||
|
- name: Install Zig (pinned, hash-verified)
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$ZigVersion = "0.16.0"
|
||||||
|
$ZigUrl = "https://ziglang.org/download/$ZigVersion/zig-windows-x86_64-$ZigVersion.zip"
|
||||||
|
$ExpectedZigHash = "68659eb5f1e4eb1437a722f1dd889c5a322c9954607f5edcf337bc3684a75a7e"
|
||||||
|
Invoke-WebRequest -Uri $ZigUrl -OutFile "zig.zip"
|
||||||
|
$ActualHash = (Get-FileHash -Path "zig.zip" -Algorithm SHA256).Hash.ToLower()
|
||||||
|
if ($ActualHash -ne $ExpectedZigHash) {
|
||||||
|
Write-Error "KRITISCHER SICHERHEITSFEHLER: Zig SHA-256 Pruefsumme ungueltig! Erwartet: $ExpectedZigHash, Erhalten: $ActualHash"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
Expand-Archive -Path "zig.zip" -DestinationPath "zig-bin"
|
||||||
|
echo "$PWD/zig-bin/zig-windows-x86_64-$ZigVersion" >> $env:GITHUB_PATH
|
||||||
|
|
||||||
- name: Run Tests
|
- name: Run Tests
|
||||||
run: cargo test --all --verbose
|
run: cargo test --all --verbose
|
||||||
|
|
||||||
- name: Build Release Binary
|
- name: Build Windows Release Binary
|
||||||
run: cargo build --release
|
run: cargo build --release
|
||||||
|
|
||||||
|
- name: Cross-build Linux musl binary
|
||||||
|
run: cargo-zigbuild zigbuild --target x86_64-unknown-linux-musl --release
|
||||||
|
|
||||||
- name: Package Artifacts
|
- name: Package Artifacts
|
||||||
id: package
|
id: package
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
run: |
|
run: |
|
||||||
$Tag = "${{ gitea.ref_name }}"
|
$Tag = "${{ gitea.ref_name }}"
|
||||||
$DistDir = "dist"
|
$DistDir = "dist"
|
||||||
$PackageName = "sanctum-${Tag}-windows-x86_64"
|
if (-not (Test-Path $DistDir)) { New-Item -ItemType Directory -Path $DistDir -Force | Out-Null }
|
||||||
$StagingDir = "${DistDir}/${PackageName}"
|
|
||||||
$ZipFile = "${DistDir}/${PackageName}.zip"
|
|
||||||
|
|
||||||
New-Item -ItemType Directory -Path $StagingDir -Force | Out-Null
|
# 1. Windows Package
|
||||||
Copy-Item "target/release/sanctum.exe" "$StagingDir/"
|
$PackageNameWin = "sanctum-${Tag}-windows-x86_64"
|
||||||
Copy-Item "README.md" "$StagingDir/"
|
$StagingDirWin = "${DistDir}/${PackageNameWin}"
|
||||||
Copy-Item "LICENSE" "$StagingDir/"
|
$ZipFile = "${DistDir}/${PackageNameWin}.zip"
|
||||||
Copy-Item "CHANGELOG.md" "$StagingDir/"
|
|
||||||
|
|
||||||
Compress-Archive -Path "$StagingDir/*" -DestinationPath $ZipFile -Force
|
New-Item -ItemType Directory -Path $StagingDirWin -Force | Out-Null
|
||||||
|
Copy-Item "target/release/sanctum.exe" "$StagingDirWin/"
|
||||||
|
Copy-Item "README.md" "$StagingDirWin/"
|
||||||
|
Copy-Item "LICENSE" "$StagingDirWin/"
|
||||||
|
Copy-Item "CHANGELOG.md" "$StagingDirWin/"
|
||||||
|
Copy-Item "QUICKSTART.md" "$StagingDirWin/"
|
||||||
|
Copy-Item "INSTALL.md" "$StagingDirWin/"
|
||||||
|
Copy-Item "LEGAL.md" "$StagingDirWin/"
|
||||||
|
Copy-Item "THIRD_PARTY_LICENSES.md" "$StagingDirWin/"
|
||||||
|
if (Test-Path "assets") { Copy-Item "assets" "$StagingDirWin/" -Recurse }
|
||||||
|
|
||||||
|
Compress-Archive -Path "$StagingDirWin/*" -DestinationPath $ZipFile -Force
|
||||||
|
Remove-Item $StagingDirWin -Recurse -Force
|
||||||
|
|
||||||
|
# Standalone Windows EXE nach dist/ kopieren
|
||||||
|
Copy-Item "target/release/sanctum.exe" "${DistDir}/sanctum.exe" -Force
|
||||||
|
|
||||||
|
# 2. Linux Package
|
||||||
|
$PackageNameLinux = "sanctum-${Tag}-linux-x86_64"
|
||||||
|
$StagingDirLinux = "${DistDir}/${PackageNameLinux}"
|
||||||
|
$TarGzFile = "${DistDir}/${PackageNameLinux}.tar.gz"
|
||||||
|
|
||||||
|
New-Item -ItemType Directory -Path $StagingDirLinux -Force | Out-Null
|
||||||
|
Copy-Item "target/x86_64-unknown-linux-musl/release/sanctum" "$StagingDirLinux/sanctum"
|
||||||
|
Copy-Item "README.md" "$StagingDirLinux/"
|
||||||
|
Copy-Item "LICENSE" "$StagingDirLinux/"
|
||||||
|
Copy-Item "CHANGELOG.md" "$StagingDirLinux/"
|
||||||
|
Copy-Item "QUICKSTART.md" "$StagingDirLinux/"
|
||||||
|
Copy-Item "INSTALL.md" "$StagingDirLinux/"
|
||||||
|
Copy-Item "LEGAL.md" "$StagingDirLinux/"
|
||||||
|
Copy-Item "THIRD_PARTY_LICENSES.md" "$StagingDirLinux/"
|
||||||
|
|
||||||
|
tar.exe -czf $TarGzFile -C $DistDir $PackageNameLinux
|
||||||
|
Remove-Item $StagingDirLinux -Recurse -Force
|
||||||
|
|
||||||
|
# Standalone Linux ELF nach dist/ kopieren
|
||||||
|
Copy-Item "target/x86_64-unknown-linux-musl/release/sanctum" "${DistDir}/sanctum" -Force
|
||||||
|
|
||||||
|
# 3. SHA-256 Checksums für alle 4 Assets
|
||||||
$ZipHash = (Get-FileHash -Path $ZipFile -Algorithm SHA256).Hash.ToLower()
|
$ZipHash = (Get-FileHash -Path $ZipFile -Algorithm SHA256).Hash.ToLower()
|
||||||
$ExeHash = (Get-FileHash -Path "target/release/sanctum.exe" -Algorithm SHA256).Hash.ToLower()
|
$ExeHash = (Get-FileHash -Path "${DistDir}/sanctum.exe" -Algorithm SHA256).Hash.ToLower()
|
||||||
|
$TarHash = (Get-FileHash -Path $TarGzFile -Algorithm SHA256).Hash.ToLower()
|
||||||
@("$ZipHash ${PackageName}.zip", "$ExeHash sanctum.exe") | Set-Content -Path "${DistDir}/SHA256SUMS.txt" -Encoding utf8
|
$ElfHash = (Get-FileHash -Path "${DistDir}/sanctum" -Algorithm SHA256).Hash.ToLower()
|
||||||
|
|
||||||
|
@(
|
||||||
|
"$ZipHash ${PackageNameWin}.zip",
|
||||||
|
"$ExeHash sanctum.exe",
|
||||||
|
"$TarHash ${PackageNameLinux}.tar.gz",
|
||||||
|
"$ElfHash sanctum"
|
||||||
|
) | Set-Content -Path "${DistDir}/SHA256SUMS.txt" -Encoding utf8
|
||||||
|
|
||||||
echo "ZIP_FILE=$ZipFile" >> $env:GITHUB_OUTPUT
|
echo "ZIP_FILE=$ZipFile" >> $env:GITHUB_OUTPUT
|
||||||
echo "PACKAGE_NAME=$PackageName" >> $env:GITHUB_OUTPUT
|
echo "PACKAGE_NAME=$PackageNameWin" >> $env:GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Upload Build Artifacts
|
- name: Upload Build Artifacts
|
||||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||||
@@ -103,10 +165,11 @@ jobs:
|
|||||||
$MinisignExe = "minisign"
|
$MinisignExe = "minisign"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$Tag = "${{ gitea.ref_name }}"
|
||||||
$KeyFile = "sanctum-ci-release.key"
|
$KeyFile = "sanctum-ci-release.key"
|
||||||
[System.IO.File]::WriteAllText($KeyFile, $env:MINISIGN_SECRET_KEY)
|
[System.IO.File]::WriteAllText($KeyFile, $env:MINISIGN_SECRET_KEY)
|
||||||
try {
|
try {
|
||||||
& $MinisignExe -S -s $KeyFile -m "dist/SHA256SUMS.txt" -W -x "dist/SHA256SUMS.txt.minisig"
|
& $MinisignExe -S -s $KeyFile -m "dist/SHA256SUMS.txt" -W -x "dist/SHA256SUMS.txt.minisig" -t "version:$Tag"
|
||||||
if ($LASTEXITCODE -ne 0 -or -not (Test-Path "dist/SHA256SUMS.txt.minisig")) {
|
if ($LASTEXITCODE -ne 0 -or -not (Test-Path "dist/SHA256SUMS.txt.minisig")) {
|
||||||
Write-Error "Minisign-Signierung fehlgeschlagen!"
|
Write-Error "Minisign-Signierung fehlgeschlagen!"
|
||||||
exit 1
|
exit 1
|
||||||
@@ -120,6 +183,9 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
dist/*.zip
|
dist/*.zip
|
||||||
|
dist/*.tar.gz
|
||||||
|
dist/sanctum
|
||||||
|
dist/sanctum.exe
|
||||||
dist/SHA256SUMS.txt
|
dist/SHA256SUMS.txt
|
||||||
dist/SHA256SUMS.txt.minisig
|
dist/SHA256SUMS.txt.minisig
|
||||||
body_path: CHANGELOG.md
|
body_path: CHANGELOG.md
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Gitleaks ignore file
|
||||||
|
# Test mock constants in unit tests (confirmed non-sensitive test tokens)
|
||||||
|
2af14eef362e718a86bb079c4897f61719398c42:src/mount.rs:generic-api-key:976
|
||||||
|
1cdb30147b3c4ecb7f46db39f530a85113ad590d:tests/mount_security_test.rs:generic-api-key:90
|
||||||
|
436790abf0e904c1aaeef51fb10cb318ce9b8bdc:tests/upgrade_security_test.rs:generic-api-key:42
|
||||||
@@ -5,6 +5,57 @@ 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/)
|
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/).
|
und dieses Projekt folgt den Richtlinien von [Semantic Versioning](https://semver.org/lang/de/).
|
||||||
|
|
||||||
|
## [0.9.3] - 2026-09-21
|
||||||
|
|
||||||
|
### Sicherheits-Governance, Supply-Chain-Audit & Dokumentationsabgleich
|
||||||
|
Dieses Release etabliert formale Sicherheitsrichtlinien und automatisierte Supply-Chain-Prüfungen vor der breiten Veröffentlichung des Projekts und korrigiert historische Dokumentationsstände der Argon2id-Parameter.
|
||||||
|
|
||||||
|
#### Sicherheitsrichtlinien & Responsible Disclosure
|
||||||
|
- **Einführung von `SECURITY.md`**:
|
||||||
|
- Definition des Responsible-Disclosure-Prozesses mit verbindlichen SLAs (48h Erstkontakt, 5 Werktage Risikobewertung, 90 Tage Embargo).
|
||||||
|
- Bereitstellung dedizierter Meldekanäle via E-Mail (`security@pansi.eu`) und vertraulicher Gitea Security Advisories.
|
||||||
|
- Klare Abgrenzung des Sicherheits-Geltungsbereichs (In/Out of Scope) und Safe-Harbor-Zusicherung für ethische Sicherheitsforscher.
|
||||||
|
- Verlinkung in `README.md`.
|
||||||
|
|
||||||
|
#### Automatisierte Audits & Supply-Chain-Governance
|
||||||
|
- **Dependency & License Governance (`cargo-deny`)**:
|
||||||
|
- Bereitstellung einer strikten `deny.toml` für Lizenzen, Sicherheitswarnungen, Crates-Bans und Repository-Quellen.
|
||||||
|
- Verifikation aller 248 Abhängigkeiten: 0 Advisories, 0 unzulässige Lizenzen.
|
||||||
|
- **Vulnerability-Audit (`cargo-audit`)**:
|
||||||
|
- Vollständiger Abgleich aller Abhängigkeiten gegen die RustSec Advisory Database: 0 Sicherheitslücken.
|
||||||
|
- **Git-Historien-Audit auf Secrets (`gitleaks`)**:
|
||||||
|
- Audit aller 101+ Git-Commits: Keine echten Secrets, privaten Schlüssel oder API-Tokens im Repository.
|
||||||
|
- Konfiguration von `.gitleaksignore` für harmlose Test-Mock-Token in Unittests.
|
||||||
|
- **Erweiterung von `SECURITY_AUDIT.md`**:
|
||||||
|
- Dokumentation aller Tool-Audits in neuem Abschnitt 8.
|
||||||
|
|
||||||
|
#### Dokumentation & CLI-Korrekturen
|
||||||
|
- **Argon2id-Standardparameter (M-01 Synchronisation)**:
|
||||||
|
- Anpassung veralteter Angaben in `README.md` und den CLI-Ausgabetexten von `sanctum init` auf die tatsächlichen M-01 Standardwerte ($M=256\,\text{MiB}, T=4, P=4$).
|
||||||
|
|
||||||
|
## [0.9.2] - 2026-09-21
|
||||||
|
|
||||||
|
### Recovery-MAC-Fix & Vollständiger Release-Workflow
|
||||||
|
Dieses Release behebt einen kritischen Fehler in den Disaster-Recovery-Pfaden (R-NEW-1), bei dem der K-01 Metadaten-MAC nach Header-Wiederherstellungen nicht aktualisiert wurde und nachfolgende Mount-Vorgänge fälschlich abwies. Zudem wird die CI/CD-Pipeline um den Linux-Cross-Build vervollständigt (CI-01) und die Quota-Ermittlung unter Linux implementiert (W-1).
|
||||||
|
|
||||||
|
#### Sicherheits- & Funktions-Fixes (R-NEW-1)
|
||||||
|
- **Metadaten-MAC nach Header-Wiederherstellung (R-NEW-1, HOCH)**:
|
||||||
|
- Bei Notfallwiederherstellung via 24-Wort BIP-39 Notfallschlüssel (`restore_slot_from_recovery_key`) wird der Metadaten-MAC sofort neu berechnet und persistent gespeichert.
|
||||||
|
- Bei Header-Wiederherstellung via `.sanctum.hdr`-Datei (`restore_header_backup`) wird der Zustand als `PendingRebuild` markiert und beim nächsten `sanctum mount` mit dem Benutzerpasswort transparent und sicher neu aufgebaut.
|
||||||
|
- Verhindert fälschliche Manipulations-Fehlermeldungen nach legitimen Notfall-Wiederherstellungen.
|
||||||
|
|
||||||
|
#### CI/CD & Build-Infrastruktur (CI-01)
|
||||||
|
- **Automatisierter Linux-Cross-Build in `.gitea/workflows/release.yaml`**:
|
||||||
|
- Vollständige Integration des Linux musl Cross-Builds (`x86_64-unknown-linux-musl`) auf dem Windows-Runner via `cargo-zigbuild` und SHA-256-gepinntem Zig-Compiler (`0.16.0`).
|
||||||
|
- Automatische Paketierung beider Plattformen (`.zip`, `.tar.gz`, Standalone-Binaries `sanctum.exe` und `sanctum`) und gemeinsame Minisign-Signatur in CI.
|
||||||
|
- Dokumentation des Release-Workflows in `RELEASE_PROCESS.md`.
|
||||||
|
|
||||||
|
#### Plattform-Optimierungen (W-1)
|
||||||
|
- **Freier Speicherplatz unter Linux (W-1)**:
|
||||||
|
- Ermittlung des freien Festplattenspeichers unter Linux/Unix via `libc::statvfs` (`f_bavail * f_frsize`) anstelle des statischen 1-TB-Platzhalters.
|
||||||
|
- **Plattform-Modul**:
|
||||||
|
- Präzisierung des Modul-Docstrings in `src/platform/mod.rs`.
|
||||||
|
|
||||||
## [0.9.1] - 2026-09-20
|
## [0.9.1] - 2026-09-20
|
||||||
|
|
||||||
### Sicherheits-Härtung, Pfadvalidierung & Migration-Dokumentation
|
### Sicherheits-Härtung, Pfadvalidierung & Migration-Dokumentation
|
||||||
|
|||||||
Generated
+2
-1
@@ -1457,7 +1457,7 @@ checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sanctum"
|
name = "sanctum"
|
||||||
version = "0.9.1"
|
version = "0.9.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes-gcm",
|
"aes-gcm",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
@@ -1473,6 +1473,7 @@ dependencies = [
|
|||||||
"http-body-util",
|
"http-body-util",
|
||||||
"hyper",
|
"hyper",
|
||||||
"hyper-util",
|
"hyper-util",
|
||||||
|
"libc",
|
||||||
"lz4_flex",
|
"lz4_flex",
|
||||||
"minisign-verify",
|
"minisign-verify",
|
||||||
"rand",
|
"rand",
|
||||||
|
|||||||
+4
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "sanctum"
|
name = "sanctum"
|
||||||
version = "0.9.1"
|
version = "0.9.3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["Harald Pansi <harald@pansi.eu>", "Sanctum Engineering Team"]
|
authors = ["Harald Pansi <harald@pansi.eu>", "Sanctum Engineering Team"]
|
||||||
description = "Verschlüsselter Ein-Datei-Container unter Windows im reinen Userland via WebDAV"
|
description = "Verschlüsselter Ein-Datei-Container unter Windows im reinen Userland via WebDAV"
|
||||||
@@ -48,6 +48,9 @@ tempfile = "3"
|
|||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
tray-item = "0.10"
|
tray-item = "0.10"
|
||||||
|
|
||||||
|
[target.'cfg(unix)'.dependencies]
|
||||||
|
libc = "0.2"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
lto = true
|
lto = true
|
||||||
|
|||||||
@@ -19,14 +19,14 @@ 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`).
|
- **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**: Standalone-Executables ohne externe DLL-Abhängigkeiten (`sanctum.exe` für Windows, statisches musl-ELF für Linux).
|
- **Statisches Single-Binary**: Standalone-Executables ohne externe DLL-Abhängigkeiten (`sanctum.exe` für Windows, statisches musl-ELF für Linux).
|
||||||
|
|
||||||
> 💡 **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).
|
> 💡 **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). Details zur Sicherheitsrichtlinie und Responsible Disclosure siehe [SECURITY.md](SECURITY.md).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔐 Kryptografie & Sicherheitsarchitektur
|
## 🔐 Kryptografie & Sicherheitsarchitektur
|
||||||
|
|
||||||
- **Schlüsselableitung (Argon2id)**:
|
- **Schlüsselableitung (Argon2id)**:
|
||||||
Aus dem Master-Passwort wird mittels `Argon2id` ($M=64\,\text{MB}, T=3, P=4$) ein 256-Bit Key Encryption Key (KEK) abgeleitet.
|
Aus dem Master-Passwort wird mittels `Argon2id` ($M=256\,\text{MiB}, T=4, P=4$) ein 256-Bit Key Encryption Key (KEK) abgeleitet.
|
||||||
- **Data Encryption Key (DEK)**:
|
- **Data Encryption Key (DEK)**:
|
||||||
Zufälliger 256-Bit Schlüssel via CSPRNG (`OsRng`). Der DEK wird mit dem KEK via AES-256-GCM verschlüsselt und im Header abgelegt.
|
Zufälliger 256-Bit Schlüssel via CSPRNG (`OsRng`). Der DEK wird mit dem KEK via AES-256-GCM verschlüsselt und im Header abgelegt.
|
||||||
- **Speichersicherheit (Zeroize & VirtualLock)**:
|
- **Speichersicherheit (Zeroize & VirtualLock)**:
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
# Sanctum Release-Prozess & CI/CD Dokumentation
|
||||||
|
|
||||||
|
Dieses Dokument beschreibt den Release-Workflow von **Sanctum**, die Absicherung der Build-Pipeline (SA-02, SA-03, SA-04) und die unterstützten Veröffentlichungswege.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Automatisierter CI/CD-Workflow (Primärer Release-Pfad)
|
||||||
|
|
||||||
|
Ab Version **0.9.2** bildet der CI-Workflow unter [`.gitea/workflows/release.yaml`](.gitea/workflows/release.yaml) den gesamten Release-Prozess für alle Zielplattformen vollautomatisch auf dem `windows-latest`-Runner ab:
|
||||||
|
|
||||||
|
### Pipeline-Architektur:
|
||||||
|
1. **Trigger**:
|
||||||
|
- Push eines Git-Release-Tags nach dem Schema `v*` (z. B. `v0.9.2`).
|
||||||
|
2. **Build & Test Job** (`build`):
|
||||||
|
- **Plattform**: `windows-latest`.
|
||||||
|
- **Toolchains**:
|
||||||
|
- Rust Toolchain gepinnt (`1.85.0`).
|
||||||
|
- Rust Targets: `x86_64-pc-windows-msvc` und `x86_64-unknown-linux-musl`.
|
||||||
|
- `cargo-zigbuild` gepinnt auf `0.23.4`.
|
||||||
|
- `Zig` Compiler gepinnt auf `0.16.0` mit zwingender SHA-256-Integritätsprüfung (`68659eb5f1e4eb1437a722f1dd889c5a322c9954607f5edcf337bc3684a75a7e`).
|
||||||
|
- **Verifikation**:
|
||||||
|
- Vollständige Ausführung der Testsuite (`cargo test --all --verbose`).
|
||||||
|
- **Kompilierung**:
|
||||||
|
- Nativer Windows x86_64 Release-Build mit LTO (`cargo build --release`).
|
||||||
|
- Statischer Linux musl Cross-Build (`cargo-zigbuild zigbuild --target x86_64-unknown-linux-musl --release`).
|
||||||
|
- **Paketierung**:
|
||||||
|
- Erstellung von `sanctum-v<Version>-windows-x86_64.zip` (inkl. `sanctum.exe`, Dokumentation und Assets).
|
||||||
|
- Erstellung von `sanctum-v<Version>-linux-x86_64.tar.gz` (inkl. `sanctum` und Dokumentation).
|
||||||
|
- Bereitstellung der eigenständigen Binärdateien `sanctum.exe` und `sanctum`.
|
||||||
|
- Berechnung und Erstellung der vierzeiligen Prüfsummendatei `SHA256SUMS.txt`.
|
||||||
|
3. **Sign & Release Job** (`sign-and-release`):
|
||||||
|
- **Geheimnis-Isolation (SA-02)**: Das Signiergeheimnis `MINISIGN_SECRET_KEY` ist ausschließlich in diesem isolierten Job verfügbar, nicht in der Build- oder Testumgebung.
|
||||||
|
- **Signierung**:
|
||||||
|
- Gepinntes `minisign.exe` (SHA-256 geprüft).
|
||||||
|
- Signiert `SHA256SUMS.txt` mit `trusted comment: version:<Version>`.
|
||||||
|
- **Gitea Release**:
|
||||||
|
- Veröffentlicht das Release unter `https://gitea.pansi.eu/harald/sanctum/releases/tag/v<Version>` mit allen 6 Assets:
|
||||||
|
- `sanctum-v<Version>-windows-x86_64.zip`
|
||||||
|
- `sanctum.exe`
|
||||||
|
- `sanctum-v<Version>-linux-x86_64.tar.gz`
|
||||||
|
- `sanctum`
|
||||||
|
- `SHA256SUMS.txt`
|
||||||
|
- `SHA256SUMS.txt.minisig`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Lokaler Fallback-Prozess (Manuell)
|
||||||
|
|
||||||
|
Sollte die CI-Infrastruktur nicht verfügbar sein, kann der Release-Prozess lokal über PowerShell repliziert werden:
|
||||||
|
|
||||||
|
1. **Windows-Paket erstellen**:
|
||||||
|
```powershell
|
||||||
|
powershell -ExecutionPolicy Bypass -File scripts/package-release.ps1
|
||||||
|
```
|
||||||
|
2. **Linux-Paket erstellen (Cross-Build)**:
|
||||||
|
```powershell
|
||||||
|
powershell -ExecutionPolicy Bypass -File scripts/package-release-linux.ps1
|
||||||
|
```
|
||||||
|
3. **Auf Gitea veröffentlichen**:
|
||||||
|
```powershell
|
||||||
|
powershell -ExecutionPolicy Bypass -File scripts/publish-release.ps1
|
||||||
|
```
|
||||||
+94
@@ -0,0 +1,94 @@
|
|||||||
|
# Sicherheitsrichtlinie & Responsible Disclosure (SECURITY.md)
|
||||||
|
|
||||||
|
Die Sicherheit von **Sanctum** und der Schutz der Daten unserer Anwender haben höchste Priorität. Dieses Dokument definiert die Sicherheitsrichtlinie, den Geltungsbereich und den formalen Prozess zur vertraulichen Meldung von Sicherheitslücken (**Responsible Disclosure**).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Unterstützte Versionen
|
||||||
|
|
||||||
|
Sicherheitsupdates und Patches werden jeweils für die neueste Version von Sanctum bereitgestellt. Ältere Versionen werden nicht separat gepflegt; Anwendern wird dringend empfohlen, stets die aktuellste Version einzusetzen (automatisch prüfbar via `sanctum upgrade --check`).
|
||||||
|
|
||||||
|
| Version | Status | Sicherheits-Support |
|
||||||
|
|:---:|:---:|:---:|
|
||||||
|
| **v0.9.x** | **Aktiv (Aktuell: v0.9.2)** | **Vollständig unterstützt** |
|
||||||
|
| <= v0.8.x | Veraltet | Nicht mehr unterstützt (Upgrade empfohlen) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Geltungsbereich (Scope)
|
||||||
|
|
||||||
|
### 2.1 Im Geltungsbereich (In Scope)
|
||||||
|
Schwachstellen in folgenden Kernbereichen fallen unter diese Richtlinie:
|
||||||
|
- **Kryptografische Kernfunktionen**:
|
||||||
|
- Schlüsselableitung (`Argon2id`, KDF-Parametervalidierung, Salt-Erzeugung).
|
||||||
|
- Verschlüsselung, Integrität und Replay-Schutz (`AES-256-GCM`, 24-Byte Generation-AAD K-02, Inode-Metadaten-MAC K-01).
|
||||||
|
- Steganografisches Carrier-Format V2 (Paged Manifest, Superblock-Konsistenz C-02, Dirty-Tracking C-03).
|
||||||
|
- **Speicher- und Prozesssicherheit**:
|
||||||
|
- Schlüsselisolation im Arbeitsspeicher (`Zeroize`, `VirtualLock` / `mlock`).
|
||||||
|
- Schutz vor Auslagerung in Swap/Pagefile oder temporäre Absturz-Dumps.
|
||||||
|
- **Netzwerk- und Dienst-Sicherheit**:
|
||||||
|
- Lokaler WebDAV-Endpunkt (zwingende `127.0.0.1` Loopback-Beschränkung, Host-Header-Validierung).
|
||||||
|
- Session-Token-Schutz (dynamische Zufallserzeugung im RAM, keine Leaks über Prozessliste `argv`, Pfade oder URLs).
|
||||||
|
- **Anti-Forensik & Anti-Leak Shield**:
|
||||||
|
- Unterdrückung von Windows-Explorer-Artefakten (`Thumbs.db`, `desktop.ini`, ADS-Streams).
|
||||||
|
- Transaktionales Schreddern von Datenblöcken mit CSPRNG-Rauschen vor dem Löschen.
|
||||||
|
- **Software-Integrität & Upgrade-Prozess**:
|
||||||
|
- Signaturprüfung von Updates via **Minisign** (Ed25519) und Domain-Beschränkung.
|
||||||
|
- **Disaster Recovery**:
|
||||||
|
- BIP-39 Notfallschlüssel-Ableitung und Header-Wiederherstellung (R-NEW-1 MAC-Rebuild).
|
||||||
|
|
||||||
|
### 2.2 Außerhalb des Geltungsbereichs (Out of Scope)
|
||||||
|
Folgende Szenarien stellen keine Sicherheitslücke in Sanctum dar:
|
||||||
|
- **Kompromittierter Host**: Angriffe, die bereits uneingeschränkte Administrator-/Root-Rechte oder physischen Zugriff auf einen laufenden, entsperrten Rechner voraussetzen (z. B. Kernel-Treiber-Injection, Direct-Memory-Access via Hardware, Keylogger auf OS-Ebene).
|
||||||
|
- **Physikalisches Flash-Wear-Leveling**: Restfragmente gelöschter Blöcke auf Flash-Speichern (SSD, NVMe) infolge des Flash Translation Layers (FTL) der Hardware, sofern Sanctum die logischen Datenblöcke nachweislich kryptografisch geschreddert hat (siehe Hinweis in `README.md` und `THREAT_MODEL.md`).
|
||||||
|
- **Social Engineering & Phishing**: Angriffe, die darauf abzielen, das Master-Passwort oder den Notfallschlüssel direkt vom Anwender zu erpressen oder zu erschleichen.
|
||||||
|
- **Lokales DoS durch Dateilöschung**: Das manuelle Löschen der `.sanctum`-Containerdatei durch einen Nutzer mit Schreibrechten auf dem Hostdateisystem.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Vertrauliche Meldung (Responsible Disclosure)
|
||||||
|
|
||||||
|
Wenn Sie eine potenzielle Sicherheitslücke in Sanctum identifiziert haben, bitten wir Sie eindringlich, diese **nicht öffentlich** (z. B. über GitHub/Gitea Public Issues, X/Twitter, Mastodon, Diskussionsforen oder Blogs) bekanntzugeben, bevor wir die Möglichkeit hatten, das Problem zu analysieren, zu beheben und ein Update bereitzustellen.
|
||||||
|
|
||||||
|
### 3.1 Kontaktwege
|
||||||
|
|
||||||
|
Bitte übermitteln Sie Ihren Bericht über einen der folgenden vertraulichen Kanäle:
|
||||||
|
|
||||||
|
1. **Per E-Mail (Primär)**:
|
||||||
|
- **Adresse**: `security@pansi.eu` *(Fallback: `harald@pansi.eu`)*
|
||||||
|
- **Betreff**: `[SECURITY] Schwachstelle in Sanctum: <Kurzbeschreibung>`
|
||||||
|
- Wenn Sie sensible Details (z. B. PoC-Exploits) verschlüsseln möchten, fordern Sie bitte vorab per Mail einen PGP-Schlüssel an oder nutzen Sie den Gitea Security Advisory Kanal.
|
||||||
|
|
||||||
|
2. **Gitea Private Vulnerability Reporting (Web)**:
|
||||||
|
- Wenn Sie ein Gitea-Konto besitzen, können Sie unter folgender URL direkt einen vertraulichen Security Advisory erstellen:
|
||||||
|
- **URL**: [https://gitea.pansi.eu/harald/sanctum/security/advisories](https://gitea.pansi.eu/harald/sanctum/security/advisories)
|
||||||
|
|
||||||
|
### 3.2 Erforderliche Informationen im Bericht
|
||||||
|
Um eine schnelle Untersuchung zu ermöglichen, sollte Ihre Meldung folgende Informationen enthalten:
|
||||||
|
1. **Zusammenfassung**: Eine prägnante Beschreibung der Schwachstelle und der betroffenen Komponente.
|
||||||
|
2. **Version & Umgebung**: Getestete Sanctum-Version (z. B. v0.9.2), Betriebssystem (Windows 11, Linux etc.) und Toolchain.
|
||||||
|
3. **Schritt-für-Schritt-Anleitung**: Genaue Reproduktionsschritte oder ein minimales Proof of Concept (PoC).
|
||||||
|
4. **Schweregrad / Impact**: Ihre Einschätzung der Auswirkung (z. B. Datenverlust, Umgehung der Verschlüsselung, Information Leak).
|
||||||
|
5. **Lösungsvorschlag** *(optional)*: Falls Sie bereits eine Idee oder einen Patch haben, freuen wir uns über Ihren Vorschlag.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Reaktionszeiten & Zeitplan (SLA)
|
||||||
|
|
||||||
|
Wir verpflichten uns zu einem transparenten und zügigen Ablauf:
|
||||||
|
|
||||||
|
| Phase | Maximale Reaktionszeit | Beschreibung |
|
||||||
|
|---|---|---|
|
||||||
|
| **Eingangsbestätigung** | **Innerhalb von 48 Stunden** | Wir bestätigen den Empfang Ihrer Meldung und benennen einen Ansprechpartner. |
|
||||||
|
| **Erste Bewertung** | **Innerhalb von 5 Werktagen** | Wir prüfen die Reproduzierbarkeit und bewerten den Schweregrad. |
|
||||||
|
| **Status-Updates** | **Mindestens alle 7 Tage** | Sie werden regelmäßig über den Fortschritt der Behebung informiert. |
|
||||||
|
| **Embargo & Veröffentlichung** | **Standardmäßig 90 Tage** | Gemeinsam stimmen wir den Veröffentlichungstermin ab (spätestens nach 90 Tagen oder unmittelbar nach Bereitstellung des Patches). |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Safe Harbor & Anerkennung
|
||||||
|
|
||||||
|
- **Rechtlicher Schutz (Safe Harbor)**: Wenn Sie nach bestem Wissen und Gewissen im Rahmen dieser Richtlinie handeln (keine Daten Dritter kompromittieren, keine Systeme sabotieren und die Vertraulichkeitsfrist einhalten), werden wir keinerlei rechtliche Schritte gegen Sie einleiten.
|
||||||
|
- **Anerkennung**: Sofern von Ihnen gewünscht, nennen wir Sie namentlich (oder mit Alias/Handle) in den offiziellen Release Notes, im `SECURITY_AUDIT.md` sowie im Changelog als Entdecker der Schwachstelle.
|
||||||
|
|
||||||
|
Vielen Dank, dass Sie dazu beitragen, Sanctum für alle Anwender sicher zu halten!
|
||||||
+34
-7
@@ -1,17 +1,18 @@
|
|||||||
# Sanctum Security Audit & Remediation Log (v0.9.1)
|
# Sanctum Security Audit & Remediation Log (v0.9.3)
|
||||||
|
|
||||||
Dieses Dokument fasst alle 39 Findings aus drei umfassenden externen Sicherheitsaudits zusammen, dokumentiert die angewandten Härtungsmaßnahmen, referenziert die jeweiligen Git-Commits und benennt die zugehörigen automatisierten Regressionstests. Abschnitt 5 dokumentiert die Formaterweiterung Carrier V2 (Paged Manifest) aus v0.9.0, und Abschnitt 6 die Nachbesserungen der Minor Issues aus v0.9.1.
|
Dieses Dokument fasst alle 39 Findings aus drei umfassenden externen Sicherheitsaudits sowie nachfolgende Härtungen zusammen, dokumentiert die angewandten Härtungsmaßnahmen, referenziert die jeweiligen Git-Commits und benennt die zugehörigen automatisierten Regressionstests. Abschnitt 5 dokumentiert die Formaterweiterung Carrier V2 (Paged Manifest) aus v0.9.0, Abschnitt 6 die Minor Issues aus v0.9.1, Abschnitt 7 den Recovery-MAC-Fix (R-NEW-1) aus v0.9.2 und Abschnitt 8 die Supply-Chain- und Secret-Audits aus v0.9.3.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Audit-Zusammenfassung
|
## Audit-Zusammenfassung
|
||||||
|
|
||||||
- **Zielversion:** Sanctum v0.9.1 (Basis v0.9.0)
|
- **Zielversion:** Sanctum v0.9.3 (Basis v0.9.2)
|
||||||
- **Behobene Findings:** 39 / 39 (100%)
|
- **Behobene Findings:** 39 / 39 (100%) Audit-Findings + R-NEW-1
|
||||||
- **Test-Ergebnis:** 128 / 128 Tests erfolgreich (100% Pass Rate)
|
- **Quality Gates:** `cargo fmt --check` (100% sauber), `cargo clippy --all-targets -- -D warnings` (0 Warnungen), `cargo-deny` (100% ok), `gitleaks` (0 Leaks)
|
||||||
- **Quality Gates:** `cargo fmt --check` (100% sauber), `cargo clippy --all-targets -- -D warnings` (0 Warnungen)
|
|
||||||
- **Container-Format:** Container-Format V3 mit kanonischer Metadaten-Authentifizierung (HMAC-SHA256) beim Mount und Chunk-Replay-Schutz (Generation-gebundenes AAD).
|
- **Container-Format:** Container-Format V3 mit kanonischer Metadaten-Authentifizierung (HMAC-SHA256) beim Mount und Chunk-Replay-Schutz (Generation-gebundenes AAD).
|
||||||
- **Hidden Vault & Storage-Resilienz:** Carrier-Format V2 (Paged Manifest), Dual-Block rollierender Superblock (C-02), Manifest-Entkopplung mit Dirty-Tracking (C-03), Fail-Soft Inode-Isolation (D-01), Sekundärindex (D-02), lückenlose Pfadvalidierung (`validate_node_name`), dynamische Kapazitätsskalierung und Blockwarnung, Fail-Closed Node-Name-Decryption (ST-01), rekursive Fehleraggregation (ST-02) und atomare SQLite-Transaktionen für Baum-Löschungen (ST-03).
|
- **Hidden Vault & Storage-Resilienz:** Carrier-Format V2 (Paged Manifest), Dual-Block rollierender Superblock (C-02), Manifest-Entkopplung mit Dirty-Tracking (C-03), Fail-Soft Inode-Isolation (D-01), Sekundärindex (D-02), lückenlose Pfadvalidierung (`validate_node_name`), dynamische Kapazitätsskalierung und Blockwarnung, Fail-Closed Node-Name-Decryption (ST-01), rekursive Fehleraggregation (ST-02) und atomare SQLite-Transaktionen für Baum-Löschungen (ST-03).
|
||||||
|
- **Disaster Recovery Resilienz (R-NEW-1):** Vollständige Aktualisierung und Rebuild-Fähigkeit des Metadaten-MAC nach Wiederherstellung via Notfallschlüssel oder Header-Backup.
|
||||||
|
- **Security Governance & Supply Chain:** `SECURITY.md` Responsible Disclosure Richtlinie, `cargo audit` (0 CVEs), `cargo deny` (strikte Lizenz- & Crate-Governance), `gitleaks` historischer Secret-Scan (sauber).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -19,6 +20,7 @@ Dieses Dokument fasst alle 39 Findings aus drei umfassenden externen Sicherheits
|
|||||||
|
|
||||||
| Finding | Priorität | Modul | Kurzbeschreibung | Commit | Status & Regressionstest |
|
| Finding | Priorität | Modul | Kurzbeschreibung | Commit | Status & Regressionstest |
|
||||||
|---|---|---|---|---|---|
|
|---|---|---|---|---|---|
|
||||||
|
| **R-NEW-1** | HOCH | `recovery`, `storage`, `mount` | Metadaten-MAC nach Header-Wiederherstellung: direkte Neuberechnung bei Recovery-Key und PendingRebuild-Status bei Backup-Restore | `v0.9.2` | ✅ Bestanden (`test_mount_succeeds_and_rebuilds_mac_after_header_file_restore`, `test_mount_succeeds_after_recovery_key_restore_slot0`, `test_mount_succeeds_after_recovery_key_restore_slot1`) |
|
||||||
| **C-02** | HOCH | `carrier` | Single Point of Failure beseitigt: Rollierendes Dual-Block-Manifest (Block 0 & 1, `manifest_generation`) mit transparenter Failover-Wiederherstellung | `8455dc0` | ✅ Bestanden (`test_c02_dual_block_rolling_manifest_redundancy_and_recovery`) |
|
| **C-02** | HOCH | `carrier` | Single Point of Failure beseitigt: Rollierendes Dual-Block-Manifest (Block 0 & 1, `manifest_generation`) mit transparenter Failover-Wiederherstellung | `8455dc0` | ✅ Bestanden (`test_c02_dual_block_rolling_manifest_redundancy_and_recovery`) |
|
||||||
| **C-03** | MITTEL | `carrier`, `vfs`, `mount` | Entkopplung der Manifest-Neuverschlüsselung via Dirty-Tracking; synchrone Sicherung bei File-Flush und Unmount | `c606fa8` | ✅ Bestanden (`test_c03_manifest_dirty_decoupling_and_unmount_flush`) |
|
| **C-03** | MITTEL | `carrier`, `vfs`, `mount` | Entkopplung der Manifest-Neuverschlüsselung via Dirty-Tracking; synchrone Sicherung bei File-Flush und Unmount | `c606fa8` | ✅ Bestanden (`test_c03_manifest_dirty_decoupling_and_unmount_flush`) |
|
||||||
| **C-04** | NIEDRIG | `carrier`, `mount`, `doc` | Dokumentation der Hidden-Vault-Kapazität (~7.000 Inodes) & automatische 80%-Füllstandswarnung beim Mounten | `c21d63a` | ✅ Bestanden (`test_c04_manifest_capacity_limit_and_warning`) |
|
| **C-04** | NIEDRIG | `carrier`, `mount`, `doc` | Dokumentation der Hidden-Vault-Kapazität (~7.000 Inodes) & automatische 80%-Füllstandswarnung beim Mounten | `c21d63a` | ✅ Bestanden (`test_c04_manifest_capacity_limit_and_warning`) |
|
||||||
@@ -136,8 +138,33 @@ Dieses Dokument fasst alle 39 Findings aus drei umfassenden externen Sicherheits
|
|||||||
- **Migration-Dokumentation (V1 → V2):**
|
- **Migration-Dokumentation (V1 → V2):**
|
||||||
- Detaillierte 5-stufige Dokumentation für Anwender zur Konvertierung auf das Paged-Manifest-Format V2.
|
- Detaillierte 5-stufige Dokumentation für Anwender zur Konvertierung auf das Paged-Manifest-Format V2.
|
||||||
|
|
||||||
|
### 7. Sanctum v0.9.2 — Recovery-MAC-Fix (R-NEW-1) & CI/CD-Vervollständigung (CI-01)
|
||||||
|
- **Metadaten-MAC nach Header-Wiederherstellung (R-NEW-1):**
|
||||||
|
- Behebt die Interaktionslücke zwischen dem K-01 Metadaten-MAC und den Disaster-Recovery-Pfaden.
|
||||||
|
- `restore_slot_from_recovery_key` aktualisiert den MAC sofort (`db.set_active_slot_and_dek` + `db.update_metadata_mac()`).
|
||||||
|
- `restore_header_backup` setzt `metadata_gen = 0` und `metadata_mac = NULL`. `verify_metadata_mac_status_for_slot` liefert `MetadataMacStatus::PendingRebuild`, wodurch `mount_container` den MAC beim ersten Einbinden transparent und sicher neu aufbaut.
|
||||||
|
- **Vollständiger Linux-Cross-Build in CI (CI-01):**
|
||||||
|
- `.gitea/workflows/release.yaml` kompiliert und paketiert Windows x86_64 und Linux musl auf demselben Runner.
|
||||||
|
- **Freier Speicherplatz unter Linux (W-1):**
|
||||||
|
- `get_available_disk_space` nutzt unter Unix `libc::statvfs` zur exakten Quota-Ermittlung (`f_bavail * f_frsize`).
|
||||||
|
|
||||||
|
### 8. Automatisierte Sicherheitsprüfungen & Supply-Chain-Audit (cargo audit, cargo deny, gitleaks)
|
||||||
|
- **RustSec Advisory Database (`cargo audit`):**
|
||||||
|
- Vollständige Prüfung aller 248 Abhängigkeiten in `Cargo.lock` gegen die offizielle RustSec Advisory-Datenbank.
|
||||||
|
- **Ergebnis:** 0 bekannte Schwachstellen (0 vulnerabilities, 0 security warnings).
|
||||||
|
- **Supply-Chain- & Dependency-Governance (`cargo deny` via `deny.toml`):**
|
||||||
|
- `advisories ok`: Keine sicherheitskritischen oder zurückgezogenen (yanked) Crates.
|
||||||
|
- `bans ok`: Keine gesperrten Crates, unbedenkliche Duplikate (z. B. `windows-sys` Major-Versionen) innerhalb der Vorgaben.
|
||||||
|
- `licenses ok`: 100 % aller Lizenzen entsprechen den freigegebenen Permissive/OSI-Lizenzen (MIT, Apache-2.0, BSD, CC0, ISC, Unicode-3.0, Zlib, MPL-2.0, LGPL-2.1-or-later).
|
||||||
|
- `sources ok`: Sämtliche Crates stammen verifiziert aus dem offiziellen `crates.io`-Index.
|
||||||
|
- **Git-Historien-Scan auf Secrets (`gitleaks`):**
|
||||||
|
- Vollständiger Audit aller 101 Git-Commits und 1,28 MB Änderungshistorie mit `gitleaks detect`.
|
||||||
|
- **Ergebnis:** Keine privaten Schlüssel (Minisign Secret Keys, SSH-Schlüssel, Passwörter, API-Tokens) in der Git-Historie vorhanden. Drei harmlose Test-Mock-Strings in Unittests (`src/mount.rs`, `tests/mount_security_test.rs`, `tests/upgrade_security_test.rs`) wurden in `.gitleaksignore` gebaselined.
|
||||||
|
- **Responsible Disclosure Policy (`SECURITY.md`):**
|
||||||
|
- Einführung von `SECURITY.md` mit SLA (48h Reaktionszeit, 5 Tage Bewertung), Kontaktadresse (`security@pansi.eu`), Gitea Security Advisories Kanal und 90-Tage Embargo.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Verifikationsnachweis
|
## Verifikationsnachweis
|
||||||
|
|
||||||
Alle Unit- und Integrationstests wurden auf einem Windows x86_64 Host mit 100% Erfolgsquote ausgeführt (130 / 130 Tests). Die Release-Paketierung für Windows und Linux musl ist voll automatisiert und wird mit Minisign kryptografisch abgesichert.
|
Alle Unit- und Integrationstests wurden auf einem Windows x86_64 Host mit 100% Erfolgsquote ausgeführt. Die Release-Paketierung für Windows und Linux musl ist voll automatisiert und wird mit Minisign kryptografisch abgesichert. Alle automatisierten Supply-Chain- und Secret-Scans (`cargo audit`, `cargo deny`, `gitleaks`) wurden erfolgreich ohne Befunde absolviert.
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
# Threat Model & Sicherheitsarchitektur von Sanctum
|
# Threat Model & Sicherheitsarchitektur von Sanctum
|
||||||
|
|
||||||
Dieses Dokument beschreibt das Bedrohungsmodell, die Sicherheitsannahmen und die Schutzmechanismen von **Sanctum v0.9.1** im reinen Userland-Betrieb.
|
Dieses Dokument beschreibt das Bedrohungsmodell, die Sicherheitsannahmen und die Schutzmechanismen von **Sanctum v0.9.3** im reinen Userland-Betrieb.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,249 @@
|
|||||||
|
# This template contains all of the possible sections and their default values
|
||||||
|
|
||||||
|
# Note that all fields that take a lint level have these possible values:
|
||||||
|
# * deny - An error will be produced and the check will fail
|
||||||
|
# * warn - A warning will be produced, but the check will not fail
|
||||||
|
# * allow - No warning or error will be produced, though in some cases a note
|
||||||
|
# will be
|
||||||
|
|
||||||
|
# The values provided in this template are the default values that will be used
|
||||||
|
# when any section or field is not specified in your own configuration
|
||||||
|
|
||||||
|
# Root options
|
||||||
|
|
||||||
|
# The graph table configures how the dependency graph is constructed and thus
|
||||||
|
# which crates the checks are performed against
|
||||||
|
[graph]
|
||||||
|
# If 1 or more target triples (and optionally, target_features) are specified,
|
||||||
|
# only the specified targets will be checked when running `cargo deny check`.
|
||||||
|
# This means, if a particular package is only ever used as a target specific
|
||||||
|
# dependency, such as, for example, the `nix` crate only being used via the
|
||||||
|
# `target_family = "unix"` configuration, that only having windows targets in
|
||||||
|
# this list would mean the nix crate, as well as any of its exclusive
|
||||||
|
# dependencies not shared by any other crates, would be ignored, as the target
|
||||||
|
# list here is effectively saying which targets you are building for.
|
||||||
|
targets = [
|
||||||
|
# The triple can be any string, but only the target triples built in to
|
||||||
|
# rustc (as of 1.40) can be checked against actual config expressions
|
||||||
|
#"x86_64-unknown-linux-musl",
|
||||||
|
# You can also specify which target_features you promise are enabled for a
|
||||||
|
# particular target. target_features are currently not validated against
|
||||||
|
# the actual valid features supported by the target architecture.
|
||||||
|
#{ triple = "wasm32-unknown-unknown", features = ["atomics"] },
|
||||||
|
]
|
||||||
|
# When creating the dependency graph used as the source of truth when checks are
|
||||||
|
# executed, this field can be used to prune crates from the graph, removing them
|
||||||
|
# from the view of cargo-deny. This is an extremely heavy hammer, as if a crate
|
||||||
|
# is pruned from the graph, all of its dependencies will also be pruned unless
|
||||||
|
# they are connected to another crate in the graph that hasn't been pruned,
|
||||||
|
# so it should be used with care. The identifiers are [Package ID Specifications]
|
||||||
|
# (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html)
|
||||||
|
#exclude = []
|
||||||
|
# If true, metadata will be collected with `--all-features`. Note that this can't
|
||||||
|
# be toggled off if true, if you want to conditionally enable `--all-features` it
|
||||||
|
# is recommended to pass `--all-features` on the cmd line instead
|
||||||
|
all-features = false
|
||||||
|
# If true, metadata will be collected with `--no-default-features`. The same
|
||||||
|
# caveat with `all-features` applies
|
||||||
|
no-default-features = false
|
||||||
|
# If set, these feature will be enabled when collecting metadata. If `--features`
|
||||||
|
# is specified on the cmd line they will take precedence over this option.
|
||||||
|
#features = []
|
||||||
|
|
||||||
|
# The output table provides options for how/if diagnostics are outputted
|
||||||
|
[output]
|
||||||
|
# When outputting inclusion graphs in diagnostics that include features, this
|
||||||
|
# option can be used to specify the depth at which feature edges will be added.
|
||||||
|
# This option is included since the graphs can be quite large and the addition
|
||||||
|
# of features from the crate(s) to all of the graph roots can be far too verbose.
|
||||||
|
# This option can be overridden via `--feature-depth` on the cmd line
|
||||||
|
feature-depth = 1
|
||||||
|
|
||||||
|
# This section is considered when running `cargo deny check advisories`
|
||||||
|
# More documentation for the advisories section can be found here:
|
||||||
|
# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html
|
||||||
|
[advisories]
|
||||||
|
# The path where the advisory databases are cloned/fetched into
|
||||||
|
#db-path = "$CARGO_HOME/advisory-dbs"
|
||||||
|
# The url(s) of the advisory databases to use
|
||||||
|
#db-urls = ["https://github.com/rustsec/advisory-db"]
|
||||||
|
# A list of advisory IDs to ignore. Note that ignored advisories will still
|
||||||
|
# output a note when they are encountered.
|
||||||
|
ignore = [
|
||||||
|
#"RUSTSEC-0000-0000",
|
||||||
|
#{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" },
|
||||||
|
#"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish
|
||||||
|
#{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
|
||||||
|
]
|
||||||
|
# If this is true, then cargo deny will use the git executable to fetch advisory database.
|
||||||
|
# If this is false, then it uses a built-in git library.
|
||||||
|
# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support.
|
||||||
|
# See Git Authentication for more information about setting up git authentication.
|
||||||
|
#git-fetch-with-cli = true
|
||||||
|
|
||||||
|
# This section is considered when running `cargo deny check licenses`
|
||||||
|
# More documentation for the licenses section can be found here:
|
||||||
|
# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html
|
||||||
|
[licenses]
|
||||||
|
# List of explicitly allowed licenses
|
||||||
|
# See https://spdx.org/licenses/ for list of possible licenses
|
||||||
|
# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
|
||||||
|
allow = [
|
||||||
|
"MIT",
|
||||||
|
"Apache-2.0",
|
||||||
|
"Apache-2.0 WITH LLVM-exception",
|
||||||
|
"BSD-2-Clause",
|
||||||
|
"BSD-3-Clause",
|
||||||
|
"CC0-1.0",
|
||||||
|
"CDLA-Permissive-2.0",
|
||||||
|
"ISC",
|
||||||
|
"Unicode-3.0",
|
||||||
|
"Unlicense",
|
||||||
|
"Zlib",
|
||||||
|
"MPL-2.0",
|
||||||
|
"LGPL-2.1-or-later",
|
||||||
|
]
|
||||||
|
# The confidence threshold for detecting a license from license text.
|
||||||
|
# The higher the value, the more closely the license text must be to the
|
||||||
|
# canonical license text of a valid SPDX license file.
|
||||||
|
# [possible values: any between 0.0 and 1.0].
|
||||||
|
confidence-threshold = 0.8
|
||||||
|
# Allow 1 or more licenses on a per-crate basis, so that particular licenses
|
||||||
|
# aren't accepted for every possible crate as with the normal allow list
|
||||||
|
exceptions = [
|
||||||
|
# Each entry is the crate and version constraint, and its specific allow
|
||||||
|
# list
|
||||||
|
#{ allow = ["Zlib"], crate = "adler32" },
|
||||||
|
]
|
||||||
|
|
||||||
|
# Some crates don't have (easily) machine readable licensing information,
|
||||||
|
# adding a clarification entry for it allows you to manually specify the
|
||||||
|
# licensing information
|
||||||
|
#[[licenses.clarify]]
|
||||||
|
# The package spec the clarification applies to
|
||||||
|
#crate = "ring"
|
||||||
|
# The SPDX expression for the license requirements of the crate
|
||||||
|
#expression = "MIT AND ISC AND OpenSSL"
|
||||||
|
# One or more files in the crate's source used as the "source of truth" for
|
||||||
|
# the license expression. If the contents match, the clarification will be used
|
||||||
|
# when running the license check, otherwise the clarification will be ignored
|
||||||
|
# and the crate will be checked normally, which may produce warnings or errors
|
||||||
|
# depending on the rest of your configuration
|
||||||
|
#license-files = [
|
||||||
|
# Each entry is a crate relative path, and the (opaque) hash of its contents
|
||||||
|
#{ path = "LICENSE", hash = 0xbd0eed23 }
|
||||||
|
#]
|
||||||
|
|
||||||
|
[licenses.private]
|
||||||
|
# If true, ignores workspace crates that aren't published, or are only
|
||||||
|
# published to private registries.
|
||||||
|
# To see how to mark a crate as unpublished (to the official registry),
|
||||||
|
# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field.
|
||||||
|
ignore = false
|
||||||
|
# One or more private registries that you might publish crates to, if a crate
|
||||||
|
# is only published to private registries, and ignore is true, the crate will
|
||||||
|
# not have its license(s) checked
|
||||||
|
registries = [
|
||||||
|
#"https://sekretz.com/registry
|
||||||
|
]
|
||||||
|
|
||||||
|
# This section is considered when running `cargo deny check bans`.
|
||||||
|
# More documentation about the 'bans' section can be found here:
|
||||||
|
# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html
|
||||||
|
[bans]
|
||||||
|
# Lint level for when multiple versions of the same crate are detected
|
||||||
|
multiple-versions = "warn"
|
||||||
|
# Lint level for when a crate version requirement is `*`
|
||||||
|
wildcards = "allow"
|
||||||
|
# The graph highlighting used when creating dotgraphs for crates
|
||||||
|
# with multiple versions
|
||||||
|
# * lowest-version - The path to the lowest versioned duplicate is highlighted
|
||||||
|
# * simplest-path - The path to the version with the fewest edges is highlighted
|
||||||
|
# * all - Both lowest-version and simplest-path are used
|
||||||
|
highlight = "all"
|
||||||
|
# The default lint level for `default` features for crates that are members of
|
||||||
|
# the workspace that is being checked. This can be overridden by allowing/denying
|
||||||
|
# `default` on a crate-by-crate basis if desired.
|
||||||
|
workspace-default-features = "allow"
|
||||||
|
# The default lint level for `default` features for external crates that are not
|
||||||
|
# members of the workspace. This can be overridden by allowing/denying `default`
|
||||||
|
# on a crate-by-crate basis if desired.
|
||||||
|
external-default-features = "allow"
|
||||||
|
# List of crates that are allowed. Use with care!
|
||||||
|
allow = [
|
||||||
|
#"ansi_term@0.11.0",
|
||||||
|
#{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is allowed" },
|
||||||
|
]
|
||||||
|
# If true, workspace members are automatically allowed even when using deny-by-default
|
||||||
|
# This is useful for organizations that want to deny all external dependencies by default
|
||||||
|
# but allow their own workspace crates without having to explicitly list them
|
||||||
|
allow-workspace = false
|
||||||
|
# List of crates to deny
|
||||||
|
deny = [
|
||||||
|
#"ansi_term@0.11.0",
|
||||||
|
#{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is banned" },
|
||||||
|
# Wrapper crates can optionally be specified to allow the crate when it
|
||||||
|
# is a direct dependency of the otherwise banned crate
|
||||||
|
#{ crate = "ansi_term@0.11.0", wrappers = ["this-crate-directly-depends-on-ansi_term"] },
|
||||||
|
]
|
||||||
|
|
||||||
|
# List of features to allow/deny
|
||||||
|
# Each entry the name of a crate and a version range. If version is
|
||||||
|
# not specified, all versions will be matched.
|
||||||
|
#[[bans.features]]
|
||||||
|
#crate = "reqwest"
|
||||||
|
# Features to not allow
|
||||||
|
#deny = ["json"]
|
||||||
|
# Features to allow
|
||||||
|
#allow = [
|
||||||
|
# "rustls",
|
||||||
|
# "__rustls",
|
||||||
|
# "__tls",
|
||||||
|
# "hyper-rustls",
|
||||||
|
# "rustls",
|
||||||
|
# "rustls-pemfile",
|
||||||
|
# "rustls-tls-webpki-roots",
|
||||||
|
# "tokio-rustls",
|
||||||
|
# "webpki-roots",
|
||||||
|
#]
|
||||||
|
# If true, the allowed features must exactly match the enabled feature set. If
|
||||||
|
# this is set there is no point setting `deny`
|
||||||
|
#exact = true
|
||||||
|
|
||||||
|
# Certain crates/versions that will be skipped when doing duplicate detection.
|
||||||
|
skip = [
|
||||||
|
#"ansi_term@0.11.0",
|
||||||
|
#{ crate = "ansi_term@0.11.0", reason = "you can specify a reason why it can't be updated/removed" },
|
||||||
|
]
|
||||||
|
# Similarly to `skip` allows you to skip certain crates during duplicate
|
||||||
|
# detection. Unlike skip, it also includes the entire tree of transitive
|
||||||
|
# dependencies starting at the specified crate, up to a certain depth, which is
|
||||||
|
# by default infinite.
|
||||||
|
skip-tree = [
|
||||||
|
#"ansi_term@0.11.0", # will be skipped along with _all_ of its direct and transitive dependencies
|
||||||
|
#{ crate = "ansi_term@0.11.0", depth = 20 },
|
||||||
|
]
|
||||||
|
|
||||||
|
# This section is considered when running `cargo deny check sources`.
|
||||||
|
# More documentation about the 'sources' section can be found here:
|
||||||
|
# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html
|
||||||
|
[sources]
|
||||||
|
# Lint level for what to happen when a crate from a crate registry that is not
|
||||||
|
# in the allow list is encountered
|
||||||
|
unknown-registry = "warn"
|
||||||
|
# Lint level for what to happen when a crate from a git repository that is not
|
||||||
|
# in the allow list is encountered
|
||||||
|
unknown-git = "warn"
|
||||||
|
# List of URLs for allowed crate registries. Defaults to the crates.io index
|
||||||
|
# if not specified. If it is specified but empty, no registries are allowed.
|
||||||
|
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
|
||||||
|
# List of URLs for allowed Git repositories
|
||||||
|
allow-git = []
|
||||||
|
|
||||||
|
[sources.allow-org]
|
||||||
|
# github.com organizations to allow git sources for
|
||||||
|
github = []
|
||||||
|
# gitlab.com organizations to allow git sources for
|
||||||
|
gitlab = []
|
||||||
|
# bitbucket.org organizations to allow git sources for
|
||||||
|
bitbucket = []
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"version": "0.9.1",
|
"version": "0.9.3",
|
||||||
"description": "Verschlüsselter Ein-Datei-Container unter Windows im reinen Userland via WebDAV",
|
"description": "Verschlüsselter Ein-Datei-Container unter Windows im reinen Userland via WebDAV",
|
||||||
"homepage": "https://gitea.pansi.eu/harald/sanctum",
|
"homepage": "https://gitea.pansi.eu/harald/sanctum",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"architecture": {
|
"architecture": {
|
||||||
"64bit": {
|
"64bit": {
|
||||||
"url": "https://gitea.pansi.eu/harald/sanctum/releases/download/v0.9.1/sanctum-v0.9.1-windows-x86_64.zip",
|
"url": "https://gitea.pansi.eu/harald/sanctum/releases/download/v0.9.3/sanctum-v0.9.3-windows-x86_64.zip",
|
||||||
"hash": "0409d5e67c173983693456620b217e9498dcdc1ae54a6bc67aa8e368ee3d0883",
|
"hash": "75611155008ed0ecd6e6bbbb77fc549c5884ecc5779ca3012e66852bbb3b729b",
|
||||||
"bin": "sanctum.exe"
|
"bin": "sanctum.exe"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.6.0.schema.json
|
# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.6.0.schema.json
|
||||||
PackageIdentifier: HaraldPansi.Sanctum
|
PackageIdentifier: HaraldPansi.Sanctum
|
||||||
PackageVersion: 0.9.1
|
PackageVersion: 0.9.3
|
||||||
PackageName: Sanctum
|
PackageName: Sanctum
|
||||||
Publisher: Harald Pansi
|
Publisher: Harald Pansi
|
||||||
PublisherUrl: https://gitea.pansi.eu/harald
|
PublisherUrl: https://gitea.pansi.eu/harald
|
||||||
@@ -18,7 +18,7 @@ Tags:
|
|||||||
- webdav
|
- webdav
|
||||||
- container
|
- container
|
||||||
- plausible-deniability
|
- plausible-deniability
|
||||||
ReleaseNotesUrl: https://gitea.pansi.eu/harald/sanctum/releases/tag/v0.9.1
|
ReleaseNotesUrl: https://gitea.pansi.eu/harald/sanctum/releases/tag/v0.9.3
|
||||||
Installers:
|
Installers:
|
||||||
- Architecture: x64
|
- Architecture: x64
|
||||||
InstallerType: zip
|
InstallerType: zip
|
||||||
@@ -26,7 +26,7 @@ Installers:
|
|||||||
NestedInstallerFiles:
|
NestedInstallerFiles:
|
||||||
- RelativeFilePath: sanctum.exe
|
- RelativeFilePath: sanctum.exe
|
||||||
PortableCommandAlias: sanctum
|
PortableCommandAlias: sanctum
|
||||||
InstallerUrl: https://gitea.pansi.eu/harald/sanctum/releases/download/v0.9.1/sanctum-v0.9.1-windows-x86_64.zip
|
InstallerUrl: https://gitea.pansi.eu/harald/sanctum/releases/download/v0.9.3/sanctum-v0.9.3-windows-x86_64.zip
|
||||||
InstallerSha256: 0409d5e67c173983693456620b217e9498dcdc1ae54a6bc67aa8e368ee3d0883
|
InstallerSha256: 75611155008ed0ecd6e6bbbb77fc549c5884ecc5779ca3012e66852bbb3b729b
|
||||||
ManifestType: singleton
|
ManifestType: singleton
|
||||||
ManifestVersion: 1.6.0
|
ManifestVersion: 1.6.0
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ if (-not $Release) {
|
|||||||
$PayloadPath = Join-Path $DistDir "release_req.json"
|
$PayloadPath = Join-Path $DistDir "release_req.json"
|
||||||
$Payload = @{
|
$Payload = @{
|
||||||
tag_name = $TagName
|
tag_name = $TagName
|
||||||
name = "Sanctum $TagName (Windows x86_64)"
|
name = "Sanctum $TagName (Windows & Linux x86_64)"
|
||||||
body = $Changelog
|
body = $Changelog
|
||||||
draft = $false
|
draft = $false
|
||||||
prerelease = $false
|
prerelease = $false
|
||||||
|
|||||||
+3
-3
@@ -577,7 +577,7 @@ fn handle_init(
|
|||||||
FORMAT_VERSION
|
FORMAT_VERSION
|
||||||
);
|
);
|
||||||
println!(" • Carrier-Datei:{} ({})", carrier_name, carrier_size_str);
|
println!(" • Carrier-Datei:{} ({})", carrier_name, carrier_size_str);
|
||||||
println!(" • KDF: Argon2id pro Slot (M=64MB, T=3, P=4)");
|
println!(" • KDF: Argon2id pro Slot (M=256MB, T=4, P=4)");
|
||||||
println!(" • Kapselung: Hidden Vault liegt in Carrier-Datei im Decoy-Vault");
|
println!(" • Kapselung: Hidden Vault liegt in Carrier-Datei im Decoy-Vault");
|
||||||
println!(" • Accounting: 100% aller Chunks authentifizieren fehlerfrei unter DEK_0");
|
println!(" • Accounting: 100% aller Chunks authentifizieren fehlerfrei unter DEK_0");
|
||||||
println!(" • Dateigröße: Feste Trägergröße zur Vermeidung von Größenveränderungen");
|
println!(" • Dateigröße: Feste Trägergröße zur Vermeidung von Größenveränderungen");
|
||||||
@@ -625,7 +625,7 @@ fn handle_init(
|
|||||||
1,
|
1,
|
||||||
4,
|
4,
|
||||||
"🔑",
|
"🔑",
|
||||||
"Leite KEK via Argon2id ab (M=64MB, T=3, P=4)...",
|
"Leite KEK via Argon2id ab (M=256MB, T=4, P=4)...",
|
||||||
);
|
);
|
||||||
let salt = generate_salt();
|
let salt = generate_salt();
|
||||||
let kdf_params = KdfParams::default();
|
let kdf_params = KdfParams::default();
|
||||||
@@ -680,7 +680,7 @@ fn handle_init(
|
|||||||
" • Format: Version {} (Magic: SANCTUM\\0)",
|
" • Format: Version {} (Magic: SANCTUM\\0)",
|
||||||
FORMAT_VERSION
|
FORMAT_VERSION
|
||||||
);
|
);
|
||||||
println!(" • KDF: Argon2id (M=64MB, T=3, P=4)");
|
println!(" • KDF: Argon2id (M=256MB, T=4, P=4)");
|
||||||
println!(" • Cipher: AES-256-GCM + LZ4-Kompression (1-MB Chunks, AEAD)");
|
println!(" • Cipher: AES-256-GCM + LZ4-Kompression (1-MB Chunks, AEAD)");
|
||||||
println!(" • Dual-Vault: Slot 1 mit Zufallsrauschen initialisiert (inaktiv)");
|
println!(" • Dual-Vault: Slot 1 mit Zufallsrauschen initialisiert (inaktiv)");
|
||||||
println!();
|
println!();
|
||||||
|
|||||||
+14
-4
@@ -187,17 +187,27 @@ pub async fn mount_container(
|
|||||||
// S-06: Advisory Lock setzen, um parallele Mounts und schreibende Sync-Läufe abzuwehren
|
// S-06: Advisory Lock setzen, um parallele Mounts und schreibende Sync-Läufe abzuwehren
|
||||||
let _advisory_lock = db.acquire_advisory_lock_guard(false)?;
|
let _advisory_lock = db.acquire_advisory_lock_guard(false)?;
|
||||||
|
|
||||||
// K-01: Metadaten-Integrität via HMAC-SHA256 validieren (Format V3)
|
// K-01 / R-NEW-1: Metadaten-Integrität via HMAC-SHA256 validieren (Format V3)
|
||||||
if version >= crate::crypto::FORMAT_VERSION_V3 {
|
if version >= crate::crypto::FORMAT_VERSION_V3 {
|
||||||
match db.verify_metadata_mac_for_slot(vault_id, &dek) {
|
match db.verify_metadata_mac_status_for_slot(vault_id, &dek) {
|
||||||
Ok(true) => {
|
Ok(crate::storage::MetadataMacStatus::Valid) => {
|
||||||
if !stealth {
|
if !stealth {
|
||||||
println!(
|
println!(
|
||||||
" • Metadaten-MAC: ✔ Integrität erfolgreich verifiziert (HMAC-SHA256)"
|
" • Metadaten-MAC: ✔ Integrität erfolgreich verifiziert (HMAC-SHA256)"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(false) => {
|
Ok(crate::storage::MetadataMacStatus::PendingRebuild) => {
|
||||||
|
if !stealth {
|
||||||
|
println!(
|
||||||
|
" • Metadaten-MAC: ℹ Metadaten-MAC nach Wiederherstellung neu aufgebaut"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
db.set_active_slot_and_dek(vault_id, dek.clone());
|
||||||
|
db.update_metadata_mac()?;
|
||||||
|
db.checkpoint()?;
|
||||||
|
}
|
||||||
|
Ok(crate::storage::MetadataMacStatus::Invalid) => {
|
||||||
bail!(
|
bail!(
|
||||||
"Metadaten-MAC-Verifikation fehlgeschlagen: Die Container-Metadaten wurden manipuliert oder sind beschädigt (K-01)."
|
"Metadaten-MAC-Verifikation fehlgeschlagen: Die Container-Metadaten wurden manipuliert oder sind beschädigt (K-01)."
|
||||||
);
|
);
|
||||||
|
|||||||
+3
-3
@@ -1,7 +1,7 @@
|
|||||||
//! Plattform-Abstraktionsschicht für Sanctum.
|
//! Plattform-Abstraktionsschicht für Sanctum.
|
||||||
//!
|
//!
|
||||||
//! Dieses Modul bündelt alle betriebssystemspezifischen Funktionen
|
//! Re-Exportiert die plattformspezifischen Implementierungen (Speichersperren,
|
||||||
//! (Speichersperren, Dateimanager-Aufrufe, Signal-Monitoring, Shell-Integration)
|
//! Prozessüberwachung, Session-Lock, Plattenplatzprüfung und Shell-Integration)
|
||||||
//! für Windows, Linux und macOS unter einer einheitlichen, speichersicheren Schnittstelle.
|
//! aus `crate::windows` (welches via `#[cfg]`-Gates Windows-, Linux- und POSIX-Code bereitstellt).
|
||||||
|
|
||||||
pub use crate::windows::*;
|
pub use crate::windows::*;
|
||||||
|
|||||||
@@ -412,6 +412,11 @@ pub fn restore_slot_from_recovery_key(
|
|||||||
db.restore_meta(&meta)
|
db.restore_meta(&meta)
|
||||||
.context("Fehler beim Schreiben des rekonstruierten Headers")?;
|
.context("Fehler beim Schreiben des rekonstruierten Headers")?;
|
||||||
|
|
||||||
|
// R-NEW-1: Metadaten-MAC für den wiederhergestellten Slot unmittelbar berechnen und persistieren
|
||||||
|
db.set_active_slot_and_dek(target_slot_id, dek.clone());
|
||||||
|
db.update_metadata_mac()
|
||||||
|
.context("Fehler beim Aktualisieren des Metadaten-MAC nach Header-Rekonstruktion")?;
|
||||||
|
|
||||||
db.checkpoint()
|
db.checkpoint()
|
||||||
.context("Fehler beim WAL-Checkpoint nach Header-Rekonstruktion")?;
|
.context("Fehler beim WAL-Checkpoint nach Header-Rekonstruktion")?;
|
||||||
|
|
||||||
@@ -561,6 +566,16 @@ mod tests {
|
|||||||
.expect("Unwrap restored DEK");
|
.expect("Unwrap restored DEK");
|
||||||
assert_eq!(*dek, *active_dek);
|
assert_eq!(*dek, *active_dek);
|
||||||
|
|
||||||
|
// R-NEW-1: Nach restore_header_backup muss der MAC-Status PendingRebuild sein
|
||||||
|
let mac_status = restored_db
|
||||||
|
.verify_metadata_mac_status_for_slot(0, &active_dek)
|
||||||
|
.expect("Verify MAC status");
|
||||||
|
assert_eq!(
|
||||||
|
mac_status,
|
||||||
|
crate::storage::MetadataMacStatus::PendingRebuild,
|
||||||
|
"Nach Header-Restore ohne Plaintext-DEK muss MAC-Status PendingRebuild sein"
|
||||||
|
);
|
||||||
|
|
||||||
let _ = fs::remove_file(&container_path);
|
let _ = fs::remove_file(&container_path);
|
||||||
let _ = fs::remove_file(&backup_path);
|
let _ = fs::remove_file(&backup_path);
|
||||||
}
|
}
|
||||||
@@ -618,6 +633,19 @@ mod tests {
|
|||||||
.expect("Unwrap rescued DEK");
|
.expect("Unwrap rescued DEK");
|
||||||
assert_eq!(*dek, *unwrapped);
|
assert_eq!(*dek, *unwrapped);
|
||||||
|
|
||||||
|
// R-NEW-1: Nach recovery key restore muss der MAC sofort gültig und persistent sein
|
||||||
|
let mac_status = rescued_db
|
||||||
|
.verify_metadata_mac_status_for_slot(0, &unwrapped)
|
||||||
|
.expect("Verify MAC status");
|
||||||
|
assert_eq!(
|
||||||
|
mac_status,
|
||||||
|
crate::storage::MetadataMacStatus::Valid,
|
||||||
|
"Nach Recovery-Key-Restore muss MAC sofort Valid sein"
|
||||||
|
);
|
||||||
|
assert!(rescued_db
|
||||||
|
.verify_metadata_mac_for_slot(0, &unwrapped)
|
||||||
|
.unwrap());
|
||||||
|
|
||||||
let _ = fs::remove_file(&container_path);
|
let _ = fs::remove_file(&container_path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+47
-17
@@ -208,6 +208,18 @@ fn current_timestamp() -> u64 {
|
|||||||
.unwrap_or(0)
|
.unwrap_or(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Status der Metadaten-MAC-Integritätsprüfung (K-01 / R-NEW-1).
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum MetadataMacStatus {
|
||||||
|
/// Metadaten-MAC ist vorhanden und stimmt mit den kanonischen Metadaten überein.
|
||||||
|
Valid,
|
||||||
|
/// Container-Header wurde frisch aus einem Backup restauriert (metadata_gen == 0 && metadata_mac IS NULL).
|
||||||
|
/// Der MAC muss beim Mounten transparent mit dem aktiven DEK neu aufgebaut werden.
|
||||||
|
PendingRebuild,
|
||||||
|
/// Metadaten-MAC fehlt (bei gen > 0) oder stimmt nicht mit den berechneten Daten überein (Manipulationsverdacht).
|
||||||
|
Invalid,
|
||||||
|
}
|
||||||
|
|
||||||
impl Database {
|
impl Database {
|
||||||
/// Öffnet oder erstellt die Container-Datenbank und initialisiert die Pragmas.
|
/// Öffnet oder erstellt die Container-Datenbank und initialisiert die Pragmas.
|
||||||
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
|
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
|
||||||
@@ -2316,6 +2328,15 @@ impl Database {
|
|||||||
|
|
||||||
/// Prüft die Integrität des Metadaten-MAC für einen spezifischen Slot (0: Decoy, 1: Hidden).
|
/// Prüft die Integrität des Metadaten-MAC für einen spezifischen Slot (0: Decoy, 1: Hidden).
|
||||||
pub fn verify_metadata_mac_for_slot(&self, slot_id: u32, dek: &[u8; 32]) -> Result<bool> {
|
pub fn verify_metadata_mac_for_slot(&self, slot_id: u32, dek: &[u8; 32]) -> Result<bool> {
|
||||||
|
Ok(self.verify_metadata_mac_status_for_slot(slot_id, dek)? == MetadataMacStatus::Valid)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prüft den detaillierten Integritätsstatus des Metadaten-MAC für einen spezifischen Slot (R-NEW-1).
|
||||||
|
pub fn verify_metadata_mac_status_for_slot(
|
||||||
|
&self,
|
||||||
|
slot_id: u32,
|
||||||
|
dek: &[u8; 32],
|
||||||
|
) -> Result<MetadataMacStatus> {
|
||||||
let conn = self.conn();
|
let conn = self.conn();
|
||||||
let meta_row: Option<(u32, Option<Vec<u8>>, u64)> = conn
|
let meta_row: Option<(u32, Option<Vec<u8>>, u64)> = conn
|
||||||
.query_row(
|
.query_row(
|
||||||
@@ -2326,19 +2347,24 @@ impl Database {
|
|||||||
.optional()?;
|
.optional()?;
|
||||||
|
|
||||||
let Some((version, mac_opt, gen)) = meta_row else {
|
let Some((version, mac_opt, gen)) = meta_row else {
|
||||||
return Ok(false);
|
return Ok(MetadataMacStatus::Invalid);
|
||||||
};
|
};
|
||||||
|
|
||||||
if version < FORMAT_VERSION_V3 {
|
if version < FORMAT_VERSION_V3 {
|
||||||
return Ok(true);
|
return Ok(MetadataMacStatus::Valid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// R-NEW-1: Frisch restaurierter Container (gen == 0 && mac_opt IS NULL)
|
||||||
|
if gen == 0 && mac_opt.is_none() {
|
||||||
|
return Ok(MetadataMacStatus::PendingRebuild);
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(mac_bytes) = mac_opt else {
|
let Some(mac_bytes) = mac_opt else {
|
||||||
return Ok(false);
|
return Ok(MetadataMacStatus::Invalid);
|
||||||
};
|
};
|
||||||
|
|
||||||
if mac_bytes.len() != 32 {
|
if mac_bytes.len() != 32 {
|
||||||
return Ok(false);
|
return Ok(MetadataMacStatus::Invalid);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut expected_mac = [0u8; 32];
|
let mut expected_mac = [0u8; 32];
|
||||||
@@ -2347,12 +2373,11 @@ impl Database {
|
|||||||
|
|
||||||
let canonical = self.canonical_nodes_bytes_for_vault(slot_id)?;
|
let canonical = self.canonical_nodes_bytes_for_vault(slot_id)?;
|
||||||
let mac_key = derive_metadata_mac_key(dek);
|
let mac_key = derive_metadata_mac_key(dek);
|
||||||
Ok(verify_metadata_mac(
|
if verify_metadata_mac(&mac_key, gen, &canonical, &expected_mac) {
|
||||||
&mac_key,
|
Ok(MetadataMacStatus::Valid)
|
||||||
gen,
|
} else {
|
||||||
&canonical,
|
Ok(MetadataMacStatus::Invalid)
|
||||||
&expected_mac,
|
}
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Führt ein Upgrade des Containerformats auf Format V3 durch (Format V3 / K-01 & K-02).
|
/// Führt ein Upgrade des Containerformats auf Format V3 durch (Format V3 / K-01 & K-02).
|
||||||
@@ -2528,7 +2553,12 @@ impl Database {
|
|||||||
kdf_params TEXT NOT NULL,
|
kdf_params TEXT NOT NULL,
|
||||||
wrapped_dek BLOB NOT NULL,
|
wrapped_dek BLOB NOT NULL,
|
||||||
header_nonce BLOB NOT NULL,
|
header_nonce BLOB NOT NULL,
|
||||||
header_tag BLOB NOT NULL
|
header_tag BLOB NOT NULL,
|
||||||
|
metadata_mac BLOB,
|
||||||
|
metadata_gen INTEGER NOT NULL DEFAULT 0,
|
||||||
|
lock_pid INTEGER,
|
||||||
|
lock_host TEXT,
|
||||||
|
lock_time INTEGER
|
||||||
);",
|
);",
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
@@ -2542,8 +2572,8 @@ impl Database {
|
|||||||
}
|
}
|
||||||
let params_json = serde_json::to_string(&slot.kdf_params)?;
|
let params_json = serde_json::to_string(&slot.kdf_params)?;
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO meta (slot_id, magic, version, kdf_salt, kdf_params, wrapped_dek, header_nonce, header_tag)
|
"INSERT INTO meta (slot_id, magic, version, kdf_salt, kdf_params, wrapped_dek, header_nonce, header_tag, metadata_mac, metadata_gen)
|
||||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, NULL, 0)",
|
||||||
params![
|
params![
|
||||||
slot.slot_id,
|
slot.slot_id,
|
||||||
MAGIC_BYTES.as_slice(),
|
MAGIC_BYTES.as_slice(),
|
||||||
@@ -2559,8 +2589,8 @@ impl Database {
|
|||||||
} else {
|
} else {
|
||||||
let params_json = serde_json::to_string(&meta.kdf_params)?;
|
let params_json = serde_json::to_string(&meta.kdf_params)?;
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO meta (slot_id, magic, version, kdf_salt, kdf_params, wrapped_dek, header_nonce, header_tag)
|
"INSERT INTO meta (slot_id, magic, version, kdf_salt, kdf_params, wrapped_dek, header_nonce, header_tag, metadata_mac, metadata_gen)
|
||||||
VALUES (0, ?1, ?2, ?3, ?4, ?5, ?6, ?7)",
|
VALUES (0, ?1, ?2, ?3, ?4, ?5, ?6, ?7, NULL, 0)",
|
||||||
params![
|
params![
|
||||||
MAGIC_BYTES.as_slice(),
|
MAGIC_BYTES.as_slice(),
|
||||||
meta.version,
|
meta.version,
|
||||||
@@ -2578,8 +2608,8 @@ impl Database {
|
|||||||
let (dummy_dek, dummy_nonce, dummy_tag, dummy_salt) = generate_dummy_slot();
|
let (dummy_dek, dummy_nonce, dummy_tag, dummy_salt) = generate_dummy_slot();
|
||||||
let dummy_params_json = serde_json::to_string(&KdfParams::default())?;
|
let dummy_params_json = serde_json::to_string(&KdfParams::default())?;
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO meta (slot_id, magic, version, kdf_salt, kdf_params, wrapped_dek, header_nonce, header_tag)
|
"INSERT INTO meta (slot_id, magic, version, kdf_salt, kdf_params, wrapped_dek, header_nonce, header_tag, metadata_mac, metadata_gen)
|
||||||
VALUES (1, ?1, ?2, ?3, ?4, ?5, ?6, ?7)",
|
VALUES (1, ?1, ?2, ?3, ?4, ?5, ?6, ?7, NULL, 0)",
|
||||||
params![
|
params![
|
||||||
MAGIC_BYTES.as_slice(),
|
MAGIC_BYTES.as_slice(),
|
||||||
FORMAT_VERSION,
|
FORMAT_VERSION,
|
||||||
|
|||||||
+26
-1
@@ -890,7 +890,32 @@ pub fn get_available_disk_space<P: AsRef<std::path::Path>>(path: P) -> Option<u6
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(not(windows))]
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
use std::ffi::CString;
|
||||||
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
let p = path.as_ref();
|
||||||
|
let dir = if p.is_dir() {
|
||||||
|
p.to_path_buf()
|
||||||
|
} else {
|
||||||
|
p.parent()
|
||||||
|
.map(|parent| parent.to_path_buf())
|
||||||
|
.unwrap_or_else(|| p.to_path_buf())
|
||||||
|
};
|
||||||
|
let Ok(c_path) = CString::new(dir.as_os_str().as_bytes()) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
unsafe {
|
||||||
|
let mut stat: libc::statvfs = std::mem::zeroed();
|
||||||
|
if libc::statvfs(c_path.as_ptr(), &mut stat) == 0 {
|
||||||
|
let free = (stat.f_bavail as u64).saturating_mul(stat.f_frsize as u64);
|
||||||
|
Some(free)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(not(any(windows, unix)))]
|
||||||
{
|
{
|
||||||
let _ = path;
|
let _ = path;
|
||||||
Some(1024 * 1024 * 1024 * 1024)
|
Some(1024 * 1024 * 1024 * 1024)
|
||||||
|
|||||||
@@ -391,3 +391,292 @@ async fn test_mount_rejects_tampered_metadata_mac() {
|
|||||||
|
|
||||||
let _ = std::fs::remove_file(&container_path);
|
let _ = std::fs::remove_file(&container_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_mount_succeeds_and_rebuilds_mac_after_header_file_restore() {
|
||||||
|
use sanctum::recovery::{export_header_backup, restore_header_backup};
|
||||||
|
use sanctum::storage::MetadataMacStatus;
|
||||||
|
|
||||||
|
let temp_dir = std::env::temp_dir();
|
||||||
|
let id = std::process::id();
|
||||||
|
let container_path: PathBuf = temp_dir.join(format!("test_hdr_restore_mount_{}.sanctum", id));
|
||||||
|
let backup_path: PathBuf = temp_dir.join(format!("test_hdr_restore_mount_{}.sanctum.hdr", id));
|
||||||
|
|
||||||
|
if container_path.exists() {
|
||||||
|
let _ = std::fs::remove_file(&container_path);
|
||||||
|
}
|
||||||
|
if backup_path.exists() {
|
||||||
|
let _ = std::fs::remove_file(&backup_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
let password = "RestoreHeaderPassword2026!";
|
||||||
|
let salt = generate_salt();
|
||||||
|
let kdf_params = KdfParams {
|
||||||
|
memory_cost: MIN_MEMORY_COST_KIB,
|
||||||
|
time_cost: MIN_TIME_COST,
|
||||||
|
parallelism: 1,
|
||||||
|
};
|
||||||
|
let kek = derive_kek(password, &salt, &kdf_params).unwrap();
|
||||||
|
let dek = generate_dek();
|
||||||
|
let (wrapped_dek, nonce, tag) = wrap_dek(&kek, &dek).unwrap();
|
||||||
|
|
||||||
|
let db = Database::open(&container_path).unwrap();
|
||||||
|
db.init_schema(&salt, &kdf_params, &wrapped_dek, &nonce, &tag)
|
||||||
|
.unwrap();
|
||||||
|
db.set_active_slot_and_dek(0, dek.clone());
|
||||||
|
let _ = db.create_node(1, "important_doc.pdf", false).unwrap();
|
||||||
|
db.update_metadata_mac().unwrap();
|
||||||
|
db.checkpoint().unwrap();
|
||||||
|
drop(db);
|
||||||
|
|
||||||
|
// 1. Header-Backup exportieren
|
||||||
|
export_header_backup(&container_path, &backup_path).expect("Export header");
|
||||||
|
|
||||||
|
// 2. Header mutwillig zerstören
|
||||||
|
{
|
||||||
|
let conn = rusqlite::Connection::open(&container_path).unwrap();
|
||||||
|
conn.execute("DELETE FROM meta", []).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Header aus Backup wiederherstellen
|
||||||
|
restore_header_backup(&container_path, &backup_path).expect("Restore header");
|
||||||
|
|
||||||
|
// 4. Status vor dem Mount prüfen: muss PendingRebuild sein
|
||||||
|
{
|
||||||
|
let check_db = Database::open(&container_path).unwrap();
|
||||||
|
let status = check_db
|
||||||
|
.verify_metadata_mac_status_for_slot(0, &dek)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
MetadataMacStatus::PendingRebuild,
|
||||||
|
"Nach restore_header_backup muss Status PendingRebuild sein"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Echter Mount-Aufruf muss gelingen und den MAC transparent neu aufbauen
|
||||||
|
let auth =
|
||||||
|
sanctum::mount::ContainerAuth::Password(zeroize::Zeroizing::new(password.to_string()));
|
||||||
|
let c_path = container_path.clone();
|
||||||
|
let mount_task = tokio::spawn(async move {
|
||||||
|
sanctum::mount::mount_container(
|
||||||
|
&c_path,
|
||||||
|
'Y',
|
||||||
|
None,
|
||||||
|
Some(19482),
|
||||||
|
auth,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
});
|
||||||
|
|
||||||
|
// Kurz warten, bis mount_container die Vorabprüfung & den MAC-Rebuild vollzogen hat
|
||||||
|
tokio::time::sleep(tokio::time::Duration::from_millis(250)).await;
|
||||||
|
mount_task.abort();
|
||||||
|
|
||||||
|
// 6. Nach dem Mount: MAC muss nun Valid und persistent gespeichert sein!
|
||||||
|
let verified_db = Database::open(&container_path).unwrap();
|
||||||
|
let new_status = verified_db
|
||||||
|
.verify_metadata_mac_status_for_slot(0, &dek)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
new_status,
|
||||||
|
MetadataMacStatus::Valid,
|
||||||
|
"Nach Mount muss der MAC erfolgreich neu aufgebaut und Valid sein"
|
||||||
|
);
|
||||||
|
assert!(verified_db.verify_metadata_mac_for_slot(0, &dek).unwrap());
|
||||||
|
|
||||||
|
let _ = std::fs::remove_file(&container_path);
|
||||||
|
let _ = std::fs::remove_file(&backup_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_mount_succeeds_after_recovery_key_restore_slot0() {
|
||||||
|
use sanctum::crypto::dek_to_mnemonic;
|
||||||
|
use sanctum::recovery::restore_header_from_recovery_key;
|
||||||
|
use sanctum::storage::MetadataMacStatus;
|
||||||
|
|
||||||
|
let temp_dir = std::env::temp_dir();
|
||||||
|
let id = std::process::id();
|
||||||
|
let container_path: PathBuf = temp_dir.join(format!("test_rec_mount_slot0_{}.sanctum", id));
|
||||||
|
|
||||||
|
if container_path.exists() {
|
||||||
|
let _ = std::fs::remove_file(&container_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
let old_password = "ForgottenOldPassword2026!";
|
||||||
|
let new_password = "RescuedNewPassword2026!";
|
||||||
|
|
||||||
|
let salt = generate_salt();
|
||||||
|
let kdf_params = KdfParams {
|
||||||
|
memory_cost: MIN_MEMORY_COST_KIB,
|
||||||
|
time_cost: MIN_TIME_COST,
|
||||||
|
parallelism: 1,
|
||||||
|
};
|
||||||
|
let kek = derive_kek(old_password, &salt, &kdf_params).unwrap();
|
||||||
|
let dek = generate_dek();
|
||||||
|
let (wrapped_dek, nonce, tag) = wrap_dek(&kek, &dek).unwrap();
|
||||||
|
let phrase = dek_to_mnemonic(&dek).unwrap();
|
||||||
|
|
||||||
|
let db = Database::open(&container_path).unwrap();
|
||||||
|
db.init_schema(&salt, &kdf_params, &wrapped_dek, &nonce, &tag)
|
||||||
|
.unwrap();
|
||||||
|
db.set_active_slot_and_dek(0, dek.clone());
|
||||||
|
let _ = db.create_node(1, "my_secrets.txt", false).unwrap();
|
||||||
|
db.update_metadata_mac().unwrap();
|
||||||
|
db.checkpoint().unwrap();
|
||||||
|
drop(db);
|
||||||
|
|
||||||
|
// Header zerstören
|
||||||
|
{
|
||||||
|
let conn = rusqlite::Connection::open(&container_path).unwrap();
|
||||||
|
conn.execute("DELETE FROM meta", []).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mit 24-Wort Notfallschlüssel und neuem Passwort wiederherstellen
|
||||||
|
restore_header_from_recovery_key(&container_path, &phrase, new_password)
|
||||||
|
.expect("Restore from recovery key");
|
||||||
|
|
||||||
|
// R-NEW-1: Unmittelbar nach restore_from_recovery_key MUSS der MAC Valid sein!
|
||||||
|
let restored_db = Database::open(&container_path).unwrap();
|
||||||
|
let status = restored_db
|
||||||
|
.verify_metadata_mac_status_for_slot(0, &dek)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
status,
|
||||||
|
MetadataMacStatus::Valid,
|
||||||
|
"Nach restore_from_recovery_key muss MAC sofort Valid sein"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
restored_db.verify_metadata_mac_for_slot(0, &dek).unwrap(),
|
||||||
|
"verify_metadata_mac_for_slot muss direkt Ok(true) liefern (R-NEW-1 Fix)"
|
||||||
|
);
|
||||||
|
drop(restored_db);
|
||||||
|
|
||||||
|
// Mount mit neuem Passwort ausführen
|
||||||
|
let auth =
|
||||||
|
sanctum::mount::ContainerAuth::Password(zeroize::Zeroizing::new(new_password.to_string()));
|
||||||
|
let c_path = container_path.clone();
|
||||||
|
let mount_task = tokio::spawn(async move {
|
||||||
|
sanctum::mount::mount_container(
|
||||||
|
&c_path,
|
||||||
|
'Y',
|
||||||
|
None,
|
||||||
|
Some(19483),
|
||||||
|
auth,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
});
|
||||||
|
|
||||||
|
tokio::time::sleep(tokio::time::Duration::from_millis(250)).await;
|
||||||
|
mount_task.abort();
|
||||||
|
|
||||||
|
let _ = std::fs::remove_file(&container_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_mount_succeeds_after_recovery_key_restore_slot1() {
|
||||||
|
use rand::RngCore;
|
||||||
|
use sanctum::crypto::{dek_to_mnemonic, wrap_slot0_payload, wrap_slot1_payload};
|
||||||
|
use sanctum::recovery::restore_slot_from_recovery_key;
|
||||||
|
use sanctum::storage::MetadataMacStatus;
|
||||||
|
|
||||||
|
let temp_dir = std::env::temp_dir();
|
||||||
|
let id: u64 = rand::rngs::OsRng.next_u64();
|
||||||
|
let container_path: PathBuf = temp_dir.join(format!("test_rec_mount_slot1_{}.sanctum", id));
|
||||||
|
|
||||||
|
if container_path.exists() {
|
||||||
|
let _ = std::fs::remove_file(&container_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
let pass_decoy = "DecoyPassword2026!";
|
||||||
|
let pass_hidden_old = "HiddenPasswordOld2026!";
|
||||||
|
let pass_hidden_new = "HiddenPasswordNew2026!";
|
||||||
|
|
||||||
|
let kdf_params = KdfParams {
|
||||||
|
memory_cost: MIN_MEMORY_COST_KIB,
|
||||||
|
time_cost: MIN_TIME_COST,
|
||||||
|
parallelism: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let salt_0 = generate_salt();
|
||||||
|
let kek_0 = derive_kek(pass_decoy, &salt_0, &kdf_params).unwrap();
|
||||||
|
let dek_0 = generate_dek();
|
||||||
|
|
||||||
|
let salt_1 = generate_salt();
|
||||||
|
let kek_1 = derive_kek(pass_hidden_old, &salt_1, &kdf_params).unwrap();
|
||||||
|
let dek_1 = generate_dek();
|
||||||
|
let phrase_1 = dek_to_mnemonic(&dek_1).unwrap();
|
||||||
|
|
||||||
|
let carrier_node_id = 3i64;
|
||||||
|
let (wrapped_0, nonce_0, tag_0) = wrap_slot0_payload(&kek_0, &dek_0, carrier_node_id).unwrap();
|
||||||
|
let (wrapped_1, nonce_1, tag_1) =
|
||||||
|
wrap_slot1_payload(&kek_1, &dek_1, &dek_0, carrier_node_id).unwrap();
|
||||||
|
|
||||||
|
let db = Database::open(&container_path).unwrap();
|
||||||
|
db.init_schema_with_carrier(
|
||||||
|
&salt_0,
|
||||||
|
&kdf_params,
|
||||||
|
&wrapped_0,
|
||||||
|
&nonce_0,
|
||||||
|
&tag_0,
|
||||||
|
Some((
|
||||||
|
"carrier.dat",
|
||||||
|
10 * 1024 * 1024,
|
||||||
|
&salt_1,
|
||||||
|
&kdf_params,
|
||||||
|
&wrapped_1,
|
||||||
|
&nonce_1,
|
||||||
|
&tag_1,
|
||||||
|
&dek_0,
|
||||||
|
&dek_1,
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
db.set_active_slot_and_dek(1, dek_1.clone());
|
||||||
|
db.update_metadata_mac().unwrap();
|
||||||
|
db.checkpoint().unwrap();
|
||||||
|
drop(db);
|
||||||
|
|
||||||
|
// Slot 1 Header zerstören
|
||||||
|
{
|
||||||
|
let conn = rusqlite::Connection::open(&container_path).unwrap();
|
||||||
|
conn.execute("DELETE FROM meta WHERE slot_id = 1", [])
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slot 1 mit 24-Wort Schlüssel und neuem Passwort wiederherstellen (dek_0 vorhanden)
|
||||||
|
restore_slot_from_recovery_key(&container_path, &phrase_1, pass_hidden_new, 1, Some(&dek_0))
|
||||||
|
.expect("Restore slot 1 from recovery key");
|
||||||
|
|
||||||
|
// R-NEW-1: Unmittelbar nach restore_slot_from_recovery_key MUSS Slot 1 MAC Valid sein!
|
||||||
|
let restored_db = Database::open(&container_path).unwrap();
|
||||||
|
let status_1 = restored_db
|
||||||
|
.verify_metadata_mac_status_for_slot(1, &dek_1)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
status_1,
|
||||||
|
MetadataMacStatus::Valid,
|
||||||
|
"Nach restore_slot_from_recovery_key muss Slot 1 MAC Valid sein"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
restored_db.verify_metadata_mac_for_slot(1, &dek_1).unwrap(),
|
||||||
|
"verify_metadata_mac_for_slot(1) muss direkt Ok(true) liefern (R-NEW-1 Fix)"
|
||||||
|
);
|
||||||
|
drop(restored_db);
|
||||||
|
|
||||||
|
let _ = std::fs::remove_file(&container_path);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user