- SA-01: Container-DoS / KDF-Amplification Schutz mit Pre-KDF Validierung, max 2 Slots (nur 0 und 1), Slot 0 Pflicht und strikten BLOB-Laengen - SA-02: Release-Signierung in CI entkoppelt (getrennte build und sign-and-release Jobs, Secret-Isolation) - SA-03: Pinned Download-Integritaet fuer minisign.exe in CI via SHA-256 - SA-04: Immutable Action-Pinning (@sha) und Toolchain-Pinning (1.85.0) in CI - SA-05: Session-Token vollstaendig aus URIs verbannt (403 Forbidden bei Vorkommen im Pfad/Query) - SA-06: Constant-Time Token- und Auth-Vergleiche via subtle::ConstantTimeEq - SA-07: Dokumentations-Klarstellung bzgl. logischem Shredding vs. physischer SSD/FTL/CoW-Persistenz
250 lines
8.1 KiB
Rust
250 lines
8.1 KiB
Rust
use std::sync::atomic::{AtomicBool, Ordering};
|
|
|
|
static VT_ENABLED: AtomicBool = AtomicBool::new(false);
|
|
|
|
/// Initialisiert die Windows-Konsole:
|
|
/// - Setzt Code-Page 65001 (UTF-8) für Ein- und Ausgabe (verhindert Umlaut-Fehler wie `f├╝r`)
|
|
/// - Aktiviert VIRTUAL_TERMINAL_PROCESSING für saubere ANSI-Farb- und Escape-Sequenz-Darstellung
|
|
#[cfg(windows)]
|
|
pub fn init_console() {
|
|
const CP_UTF8: u32 = 65001;
|
|
const STD_OUTPUT_HANDLE: u32 = 0xFFFFFFF5; // (DWORD)-11
|
|
const ENABLE_VIRTUAL_TERMINAL_PROCESSING: u32 = 0x0004;
|
|
|
|
extern "system" {
|
|
fn SetConsoleOutputCP(wCodePageID: u32) -> i32;
|
|
fn SetConsoleCP(wCodePageID: u32) -> i32;
|
|
fn GetStdHandle(nStdHandle: u32) -> *mut std::ffi::c_void;
|
|
fn GetConsoleMode(hConsoleHandle: *mut std::ffi::c_void, lpMode: *mut u32) -> i32;
|
|
fn SetConsoleMode(hConsoleHandle: *mut std::ffi::c_void, dwMode: u32) -> i32;
|
|
}
|
|
|
|
unsafe {
|
|
let _ = SetConsoleOutputCP(CP_UTF8);
|
|
let _ = SetConsoleCP(CP_UTF8);
|
|
|
|
let handle = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
if !handle.is_null() && handle as isize != -1 {
|
|
let mut mode: u32 = 0;
|
|
if GetConsoleMode(handle, &mut mode) != 0 {
|
|
if SetConsoleMode(handle, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0 {
|
|
VT_ENABLED.store(true, Ordering::Relaxed);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(not(windows))]
|
|
pub fn init_console() {
|
|
VT_ENABLED.store(true, Ordering::Relaxed);
|
|
}
|
|
|
|
pub fn is_vt_enabled() -> bool {
|
|
VT_ENABLED.load(Ordering::Relaxed)
|
|
}
|
|
|
|
pub fn bold(s: &str) -> String {
|
|
if is_vt_enabled() {
|
|
format!("\x1b[1m{s}\x1b[0m")
|
|
} else {
|
|
s.to_string()
|
|
}
|
|
}
|
|
|
|
pub fn dim(s: &str) -> String {
|
|
if is_vt_enabled() {
|
|
format!("\x1b[90m{s}\x1b[0m")
|
|
} else {
|
|
s.to_string()
|
|
}
|
|
}
|
|
|
|
pub fn green(s: &str) -> String {
|
|
if is_vt_enabled() {
|
|
format!("\x1b[1;32m{s}\x1b[0m")
|
|
} else {
|
|
s.to_string()
|
|
}
|
|
}
|
|
|
|
pub fn cyan(s: &str) -> String {
|
|
if is_vt_enabled() {
|
|
format!("\x1b[1;36m{s}\x1b[0m")
|
|
} else {
|
|
s.to_string()
|
|
}
|
|
}
|
|
|
|
pub fn yellow(s: &str) -> String {
|
|
if is_vt_enabled() {
|
|
format!("\x1b[1;33m{s}\x1b[0m")
|
|
} else {
|
|
s.to_string()
|
|
}
|
|
}
|
|
|
|
pub fn red(s: &str) -> String {
|
|
if is_vt_enabled() {
|
|
format!("\x1b[1;31m{s}\x1b[0m")
|
|
} else {
|
|
s.to_string()
|
|
}
|
|
}
|
|
|
|
pub fn magenta(s: &str) -> String {
|
|
if is_vt_enabled() {
|
|
format!("\x1b[1;35m{s}\x1b[0m")
|
|
} else {
|
|
s.to_string()
|
|
}
|
|
}
|
|
|
|
/// Formatiert eine Byte-Anzahl lesbar in B, KB, MB, GB oder TB.
|
|
pub fn format_bytes(bytes: u64) -> String {
|
|
const KB: f64 = 1024.0;
|
|
const MB: f64 = KB * 1024.0;
|
|
const GB: f64 = MB * 1024.0;
|
|
const TB: f64 = GB * 1024.0;
|
|
|
|
let b = bytes as f64;
|
|
if b >= TB {
|
|
format!("{:.2} TB", b / TB)
|
|
} else if b >= GB {
|
|
format!("{:.2} GB", b / GB)
|
|
} else if b >= MB {
|
|
format!("{:.2} MB", b / MB)
|
|
} else if b >= KB {
|
|
format!("{:.2} KB", b / KB)
|
|
} else {
|
|
format!("{} B", bytes)
|
|
}
|
|
}
|
|
|
|
/// Gibt einen formatierten Fortschrittsschritt aus: ` [1/4] 📦 Schrittbeschreibung...`
|
|
pub fn step(num: u8, total: u8, icon: &str, msg: &str) {
|
|
let tag = cyan(&format!("[{}/{}]", num, total));
|
|
println!(" {} {} {}", tag, icon, msg);
|
|
}
|
|
|
|
/// Gibt den 24-Wort BIP-39 Notfall-Wiederherstellungsschlüssel in einer hervorgehobenen Sicherheitsbox aus.
|
|
pub fn print_recovery_phrase_card(phrase: &str) {
|
|
let words: Vec<&str> = phrase.split_whitespace().collect();
|
|
println!();
|
|
println!(
|
|
"{}",
|
|
yellow("┌─────────────────────────────────────────────────────────────┐")
|
|
);
|
|
println!(
|
|
"{}",
|
|
yellow("│ ⚠️ 24-WORT NOTFALL-WIEDERHERSTELLUNGSSCHLÜSSEL │")
|
|
);
|
|
println!(
|
|
"{}",
|
|
yellow("├─────────────────────────────────────────────────────────────┤")
|
|
);
|
|
println!("│ Falls Sie Ihr Master-Passwort vergessen oder der Header │");
|
|
println!("│ beschädigt wird, ist dies Ihre EINZIGE Rettung! │");
|
|
println!("│ Notieren Sie die Wörter in EXAKTER Reihenfolge auf Papier! │");
|
|
println!(
|
|
"{}",
|
|
yellow("├─────────────────────────────────────────────────────────────┤")
|
|
);
|
|
|
|
for row in 0..8 {
|
|
let w1 = if row < words.len() {
|
|
format!("{:2}. {:<11}", row + 1, words[row])
|
|
} else {
|
|
"".to_string()
|
|
};
|
|
let w2 = if row + 8 < words.len() {
|
|
format!("{:2}. {:<11}", row + 9, words[row + 8])
|
|
} else {
|
|
"".to_string()
|
|
};
|
|
let w3 = if row + 16 < words.len() {
|
|
format!("{:2}. {:<11}", row + 17, words[row + 16])
|
|
} else {
|
|
"".to_string()
|
|
};
|
|
println!("│ {:<18} {:<18} {:<18} │", cyan(&w1), cyan(&w2), cyan(&w3));
|
|
}
|
|
|
|
println!(
|
|
"{}",
|
|
yellow("└─────────────────────────────────────────────────────────────┘")
|
|
);
|
|
println!();
|
|
}
|
|
|
|
/// Gibt den detaillierten Bericht einer Container-Integritätsprüfung aus.
|
|
pub fn print_verification_report(report: &crate::verify::VerificationReport) {
|
|
println!();
|
|
println!("┌─────────────────────────────────────────────────────────────┐");
|
|
println!("│ Sanctum Container-Integritätsprüfung (FSCK) │");
|
|
println!("└─────────────────────────────────────────────────────────────┘");
|
|
println!(" Container: {}", report.container_path);
|
|
println!(" Format: Version {}", report.format_version);
|
|
println!();
|
|
|
|
let sqlite_status = if report.sqlite_ok {
|
|
green("✔ OK")
|
|
} else {
|
|
red("✖ FEHLER")
|
|
};
|
|
let header_status = if report.header_ok {
|
|
green("✔ OK")
|
|
} else {
|
|
red("✖ BESCHÄDIGT")
|
|
};
|
|
let tree_status = if report.orphan_nodes == 0 {
|
|
green("✔ KONSISTENT")
|
|
} else {
|
|
red("✖ INKONSISTENT")
|
|
};
|
|
let chunk_status = if report.corrupted_chunks == 0 {
|
|
green("✔ AUTHENTIFIZIERT")
|
|
} else {
|
|
red("✖ BESCHÄDIGT")
|
|
};
|
|
|
|
println!(" • SQLite B-Tree Integrität: {}", sqlite_status);
|
|
println!(" • Header & KDF-Metadaten: {}", header_status);
|
|
println!(" • Verzeichnisbaum & Inodes: {}", tree_status);
|
|
println!(" • AEAD Chunk-Authentizität: {}", chunk_status);
|
|
println!();
|
|
println!(" Statistiken:");
|
|
println!(" - Ordner: {}", report.total_dirs);
|
|
println!(" - Dateien: {}", report.total_files);
|
|
println!(" - Daten-Chunks: {}", report.total_chunks);
|
|
if report.total_bytes_decrypted > 0 {
|
|
let mb = report.total_bytes_decrypted as f64 / (1024.0 * 1024.0);
|
|
println!(
|
|
" - Verifiziert: {:.2} MB (vollständig entschlüsselt)",
|
|
mb
|
|
);
|
|
}
|
|
|
|
if !report.errors.is_empty() {
|
|
println!();
|
|
println!("{}", red(" Gefundene Probleme / Fehler:"));
|
|
for err in &report.errors {
|
|
println!(" {} {}", red("✖"), err);
|
|
}
|
|
}
|
|
|
|
println!();
|
|
if report.is_healthy() {
|
|
println!(
|
|
" {}",
|
|
green("✔ Keine Beschädigungen oder Bitrot festgestellt. Der Container ist integer.")
|
|
);
|
|
} else {
|
|
println!(
|
|
" {}",
|
|
red("✖ ACHTUNG: Der Container weist Beschädigungen auf! Bitte Backup prüfen.")
|
|
);
|
|
}
|
|
println!();
|
|
}
|