From badbe3bd188adde07bd7fc811920d93b5373b802 Mon Sep 17 00:00:00 2001 From: harald Date: Mon, 21 Sep 2026 08:15:17 +0200 Subject: [PATCH] fix(recovery): resolve metadata MAC desync after recovery (R-NEW-1) & complete release workflow (CI-01) - R-NEW-1: Recalculate metadata HMAC upon recovery-key restore and mark backup restores as PendingRebuild to rebuild transparently on first mount - CI-01: Update release.yaml to compile both Windows x86_64 and Linux musl with pinned Zig 0.16.0 and cargo-zigbuild 0.23.4 - W-1: Implement statvfs quota determination on Unix via libc - Add RELEASE_PROCESS.md documenting release architecture and steps - Bump version to 0.9.2 across manifests, lockfile, docs, Scoop and WinGet --- .gitea/workflows/release.yaml | 100 ++++++-- CHANGELOG.md | 23 ++ Cargo.lock | 3 +- Cargo.toml | 5 +- RELEASE_PROCESS.md | 62 +++++ SECURITY_AUDIT.md | 23 +- THREAT_MODEL.md | 2 +- packages/scoop/sanctum.json | 6 +- packages/winget/HaraldPansi.Sanctum.yaml | 8 +- scripts/publish-release.ps1 | 2 +- src/mount.rs | 18 +- src/platform/mod.rs | 6 +- src/recovery.rs | 28 +++ src/storage.rs | 64 +++-- src/windows.rs | 27 ++- tests/mount_security_test.rs | 289 +++++++++++++++++++++++ 16 files changed, 607 insertions(+), 59 deletions(-) create mode 100644 RELEASE_PROCESS.md diff --git a/.gitea/workflows/release.yaml b/.gitea/workflows/release.yaml index c46d44d..a8e6cc6 100644 --- a/.gitea/workflows/release.yaml +++ b/.gitea/workflows/release.yaml @@ -8,7 +8,7 @@ on: jobs: # SA-02 & SA-04: Entkoppelte Build- & Test-Umgebung ohne Zugriff auf Signatur-Secrets build: - name: Build & Test (Windows x86_64) + name: Build & Test (Windows x86_64 & Linux musl) runs-on: windows-latest steps: - name: Checkout Code @@ -18,39 +18,101 @@ jobs: uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de # master commit pinned with: 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 run: cargo test --all --verbose - - name: Build Release Binary + - name: Build Windows Release Binary run: cargo build --release + - name: Cross-build Linux musl binary + run: cargo-zigbuild zigbuild --target x86_64-unknown-linux-musl --release + - name: Package Artifacts id: package shell: pwsh run: | $Tag = "${{ gitea.ref_name }}" $DistDir = "dist" - $PackageName = "sanctum-${Tag}-windows-x86_64" - $StagingDir = "${DistDir}/${PackageName}" - $ZipFile = "${DistDir}/${PackageName}.zip" + if (-not (Test-Path $DistDir)) { New-Item -ItemType Directory -Path $DistDir -Force | Out-Null } - New-Item -ItemType Directory -Path $StagingDir -Force | Out-Null - Copy-Item "target/release/sanctum.exe" "$StagingDir/" - Copy-Item "README.md" "$StagingDir/" - Copy-Item "LICENSE" "$StagingDir/" - Copy-Item "CHANGELOG.md" "$StagingDir/" + # 1. Windows Package + $PackageNameWin = "sanctum-${Tag}-windows-x86_64" + $StagingDirWin = "${DistDir}/${PackageNameWin}" + $ZipFile = "${DistDir}/${PackageNameWin}.zip" - 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() - $ExeHash = (Get-FileHash -Path "target/release/sanctum.exe" -Algorithm SHA256).Hash.ToLower() - - @("$ZipHash ${PackageName}.zip", "$ExeHash sanctum.exe") | Set-Content -Path "${DistDir}/SHA256SUMS.txt" -Encoding utf8 + $ExeHash = (Get-FileHash -Path "${DistDir}/sanctum.exe" -Algorithm SHA256).Hash.ToLower() + $TarHash = (Get-FileHash -Path $TarGzFile -Algorithm SHA256).Hash.ToLower() + $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 "PACKAGE_NAME=$PackageName" >> $env:GITHUB_OUTPUT + echo "PACKAGE_NAME=$PackageNameWin" >> $env:GITHUB_OUTPUT - name: Upload Build Artifacts uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 @@ -103,10 +165,11 @@ jobs: $MinisignExe = "minisign" } + $Tag = "${{ gitea.ref_name }}" $KeyFile = "sanctum-ci-release.key" [System.IO.File]::WriteAllText($KeyFile, $env:MINISIGN_SECRET_KEY) 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")) { Write-Error "Minisign-Signierung fehlgeschlagen!" exit 1 @@ -120,6 +183,9 @@ jobs: with: files: | dist/*.zip + dist/*.tar.gz + dist/sanctum + dist/sanctum.exe dist/SHA256SUMS.txt dist/SHA256SUMS.txt.minisig body_path: CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 435782a..9e50848 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,29 @@ 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.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 ### Sicherheits-Härtung, Pfadvalidierung & Migration-Dokumentation diff --git a/Cargo.lock b/Cargo.lock index 85bca85..5f5a925 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1457,7 +1457,7 @@ checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" [[package]] name = "sanctum" -version = "0.9.1" +version = "0.9.2" dependencies = [ "aes-gcm", "anyhow", @@ -1473,6 +1473,7 @@ dependencies = [ "http-body-util", "hyper", "hyper-util", + "libc", "lz4_flex", "minisign-verify", "rand", diff --git a/Cargo.toml b/Cargo.toml index 995ae68..a4e2100 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sanctum" -version = "0.9.1" +version = "0.9.2" edition = "2021" authors = ["Harald Pansi ", "Sanctum Engineering Team"] description = "Verschlüsselter Ein-Datei-Container unter Windows im reinen Userland via WebDAV" @@ -48,6 +48,9 @@ tempfile = "3" [target.'cfg(windows)'.dependencies] tray-item = "0.10" +[target.'cfg(unix)'.dependencies] +libc = "0.2" + [profile.release] opt-level = 3 lto = true diff --git a/RELEASE_PROCESS.md b/RELEASE_PROCESS.md new file mode 100644 index 0000000..fe7306b --- /dev/null +++ b/RELEASE_PROCESS.md @@ -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-windows-x86_64.zip` (inkl. `sanctum.exe`, Dokumentation und Assets). + - Erstellung von `sanctum-v-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:`. + - **Gitea Release**: + - Veröffentlicht das Release unter `https://gitea.pansi.eu/harald/sanctum/releases/tag/v` mit allen 6 Assets: + - `sanctum-v-windows-x86_64.zip` + - `sanctum.exe` + - `sanctum-v-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 + ``` diff --git a/SECURITY_AUDIT.md b/SECURITY_AUDIT.md index c7b4111..7c69803 100644 --- a/SECURITY_AUDIT.md +++ b/SECURITY_AUDIT.md @@ -1,17 +1,17 @@ -# Sanctum Security Audit & Remediation Log (v0.9.1) +# Sanctum Security Audit & Remediation Log (v0.9.2) -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 und Abschnitt 7 den Recovery-MAC-Fix (R-NEW-1) aus v0.9.2. --- ## Audit-Zusammenfassung -- **Zielversion:** Sanctum v0.9.1 (Basis v0.9.0) -- **Behobene Findings:** 39 / 39 (100%) -- **Test-Ergebnis:** 128 / 128 Tests erfolgreich (100% Pass Rate) +- **Zielversion:** Sanctum v0.9.2 (Basis v0.9.1) +- **Behobene Findings:** 39 / 39 (100%) Audit-Findings + R-NEW-1 - **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). - **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. --- @@ -19,6 +19,7 @@ Dieses Dokument fasst alle 39 Findings aus drei umfassenden externen Sicherheits | 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-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`) | @@ -136,8 +137,18 @@ Dieses Dokument fasst alle 39 Findings aus drei umfassenden externen Sicherheits - **Migration-Dokumentation (V1 → 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`). + --- ## 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. \ No newline at end of file +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. \ No newline at end of file diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md index 6725d5c..23eac11 100644 --- a/THREAT_MODEL.md +++ b/THREAT_MODEL.md @@ -1,6 +1,6 @@ # 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.2** im reinen Userland-Betrieb. --- diff --git a/packages/scoop/sanctum.json b/packages/scoop/sanctum.json index 93103a0..b0187ab 100644 --- a/packages/scoop/sanctum.json +++ b/packages/scoop/sanctum.json @@ -1,12 +1,12 @@ { - "version": "0.9.1", + "version": "0.9.2", "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.9.1/sanctum-v0.9.1-windows-x86_64.zip", - "hash": "0409d5e67c173983693456620b217e9498dcdc1ae54a6bc67aa8e368ee3d0883", + "url": "https://gitea.pansi.eu/harald/sanctum/releases/download/v0.9.2/sanctum-v0.9.2-windows-x86_64.zip", + "hash": "5828fca32b7d0581b740ef4f3cfb21c3717f92ee80d2e3f62e953a1ef083ac18", "bin": "sanctum.exe" } }, diff --git a/packages/winget/HaraldPansi.Sanctum.yaml b/packages/winget/HaraldPansi.Sanctum.yaml index af66f2d..b841143 100644 --- a/packages/winget/HaraldPansi.Sanctum.yaml +++ b/packages/winget/HaraldPansi.Sanctum.yaml @@ -1,6 +1,6 @@ # yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.6.0.schema.json PackageIdentifier: HaraldPansi.Sanctum -PackageVersion: 0.9.1 +PackageVersion: 0.9.2 PackageName: Sanctum Publisher: Harald Pansi PublisherUrl: https://gitea.pansi.eu/harald @@ -18,7 +18,7 @@ Tags: - webdav - container - 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.2 Installers: - Architecture: x64 InstallerType: zip @@ -26,7 +26,7 @@ Installers: NestedInstallerFiles: - RelativeFilePath: sanctum.exe PortableCommandAlias: sanctum - InstallerUrl: https://gitea.pansi.eu/harald/sanctum/releases/download/v0.9.1/sanctum-v0.9.1-windows-x86_64.zip - InstallerSha256: 0409d5e67c173983693456620b217e9498dcdc1ae54a6bc67aa8e368ee3d0883 + InstallerUrl: https://gitea.pansi.eu/harald/sanctum/releases/download/v0.9.2/sanctum-v0.9.2-windows-x86_64.zip + InstallerSha256: 5828fca32b7d0581b740ef4f3cfb21c3717f92ee80d2e3f62e953a1ef083ac18 ManifestType: singleton ManifestVersion: 1.6.0 diff --git a/scripts/publish-release.ps1 b/scripts/publish-release.ps1 index 509935a..e06e122 100644 --- a/scripts/publish-release.ps1 +++ b/scripts/publish-release.ps1 @@ -85,7 +85,7 @@ if (-not $Release) { $PayloadPath = Join-Path $DistDir "release_req.json" $Payload = @{ tag_name = $TagName - name = "Sanctum $TagName (Windows x86_64)" + name = "Sanctum $TagName (Windows & Linux x86_64)" body = $Changelog draft = $false prerelease = $false diff --git a/src/mount.rs b/src/mount.rs index b11e50c..45f936b 100644 --- a/src/mount.rs +++ b/src/mount.rs @@ -187,17 +187,27 @@ pub async fn mount_container( // S-06: Advisory Lock setzen, um parallele Mounts und schreibende Sync-Läufe abzuwehren 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 { - match db.verify_metadata_mac_for_slot(vault_id, &dek) { - Ok(true) => { + match db.verify_metadata_mac_status_for_slot(vault_id, &dek) { + Ok(crate::storage::MetadataMacStatus::Valid) => { if !stealth { println!( " • 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!( "Metadaten-MAC-Verifikation fehlgeschlagen: Die Container-Metadaten wurden manipuliert oder sind beschädigt (K-01)." ); diff --git a/src/platform/mod.rs b/src/platform/mod.rs index fc8b2d5..f4e4e99 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -1,7 +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. +//! Re-Exportiert die plattformspezifischen Implementierungen (Speichersperren, +//! Prozessüberwachung, Session-Lock, Plattenplatzprüfung und Shell-Integration) +//! aus `crate::windows` (welches via `#[cfg]`-Gates Windows-, Linux- und POSIX-Code bereitstellt). pub use crate::windows::*; diff --git a/src/recovery.rs b/src/recovery.rs index 0cb3eed..dd12ea6 100644 --- a/src/recovery.rs +++ b/src/recovery.rs @@ -412,6 +412,11 @@ pub fn restore_slot_from_recovery_key( db.restore_meta(&meta) .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() .context("Fehler beim WAL-Checkpoint nach Header-Rekonstruktion")?; @@ -561,6 +566,16 @@ mod tests { .expect("Unwrap restored 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(&backup_path); } @@ -618,6 +633,19 @@ mod tests { .expect("Unwrap rescued DEK"); 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); } } diff --git a/src/storage.rs b/src/storage.rs index 2e488da..74ef3b4 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -208,6 +208,18 @@ fn current_timestamp() -> u64 { .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 { /// Öffnet oder erstellt die Container-Datenbank und initialisiert die Pragmas. pub fn open>(path: P) -> Result { @@ -2316,6 +2328,15 @@ impl Database { /// 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 { + 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 { let conn = self.conn(); let meta_row: Option<(u32, Option>, u64)> = conn .query_row( @@ -2326,19 +2347,24 @@ impl Database { .optional()?; let Some((version, mac_opt, gen)) = meta_row else { - return Ok(false); + return Ok(MetadataMacStatus::Invalid); }; 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 { - return Ok(false); + return Ok(MetadataMacStatus::Invalid); }; if mac_bytes.len() != 32 { - return Ok(false); + return Ok(MetadataMacStatus::Invalid); } let mut expected_mac = [0u8; 32]; @@ -2347,12 +2373,11 @@ impl Database { let canonical = self.canonical_nodes_bytes_for_vault(slot_id)?; let mac_key = derive_metadata_mac_key(dek); - Ok(verify_metadata_mac( - &mac_key, - gen, - &canonical, - &expected_mac, - )) + if verify_metadata_mac(&mac_key, gen, &canonical, &expected_mac) { + Ok(MetadataMacStatus::Valid) + } else { + Ok(MetadataMacStatus::Invalid) + } } /// 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, wrapped_dek 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)?; conn.execute( - "INSERT INTO meta (slot_id, magic, version, kdf_salt, kdf_params, wrapped_dek, header_nonce, header_tag) - VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)", + "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, NULL, 0)", params![ slot.slot_id, MAGIC_BYTES.as_slice(), @@ -2559,8 +2589,8 @@ impl Database { } else { let params_json = serde_json::to_string(&meta.kdf_params)?; conn.execute( - "INSERT INTO meta (slot_id, magic, version, kdf_salt, kdf_params, wrapped_dek, header_nonce, header_tag) - VALUES (0, ?1, ?2, ?3, ?4, ?5, ?6, ?7)", + "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, NULL, 0)", params![ MAGIC_BYTES.as_slice(), meta.version, @@ -2578,8 +2608,8 @@ impl Database { let (dummy_dek, dummy_nonce, dummy_tag, dummy_salt) = generate_dummy_slot(); let dummy_params_json = serde_json::to_string(&KdfParams::default())?; conn.execute( - "INSERT INTO meta (slot_id, magic, version, kdf_salt, kdf_params, wrapped_dek, header_nonce, header_tag) - VALUES (1, ?1, ?2, ?3, ?4, ?5, ?6, ?7)", + "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, NULL, 0)", params![ MAGIC_BYTES.as_slice(), FORMAT_VERSION, diff --git a/src/windows.rs b/src/windows.rs index 49f69eb..b55d7ff 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -890,7 +890,32 @@ pub fn get_available_disk_space>(path: P) -> Option