fix(format-v3): transcript versioning in HMAC and atomic upgrade MAC (F-04, F-05)
This commit is contained in:
1 parent
3a1a1ca6ce
commit
76c23dbfa3
15 files changed
+755
-77
No files matched your search
@@ -645,6 +645,34 @@ pub fn verify_metadata_mac(
|
||||
computed.ct_eq(expected_mac).into()
|
||||
}
|
||||
|
||||
/// Berechnet den Metadaten-MAC mit Transcript-Versionierung (Format V3.2 / F-04).
|
||||
/// Format: b"SANCTUM_META_V3\0" || metadata_gen LE64 || transcript_ver LE32 || canonical
|
||||
pub fn compute_metadata_mac_v2(
|
||||
mac_key: &[u8; 32],
|
||||
metadata_gen: u64,
|
||||
transcript_ver: u32,
|
||||
canonical: &[u8],
|
||||
) -> [u8; 32] {
|
||||
let mut data = Vec::with_capacity(28 + canonical.len());
|
||||
data.extend_from_slice(b"SANCTUM_META_V3\0");
|
||||
data.extend_from_slice(&metadata_gen.to_le_bytes());
|
||||
data.extend_from_slice(&transcript_ver.to_le_bytes());
|
||||
data.extend_from_slice(canonical);
|
||||
hmac_sha256(mac_key, &data)
|
||||
}
|
||||
|
||||
/// Prüft in konstanter Zeit, ob der gegebene Metadaten-MAC mit Transcript-Versionierung gültig ist (Format V3.2 / F-04).
|
||||
pub fn verify_metadata_mac_v2(
|
||||
mac_key: &[u8; 32],
|
||||
metadata_gen: u64,
|
||||
transcript_ver: u32,
|
||||
canonical: &[u8],
|
||||
expected_mac: &[u8; 32],
|
||||
) -> bool {
|
||||
let computed = compute_metadata_mac_v2(mac_key, metadata_gen, transcript_ver, canonical);
|
||||
computed.ct_eq(expected_mac).into()
|
||||
}
|
||||
|
||||
/// Erzeugt die 16-Byte Associated Data (AAD) für einen Chunk, um Swap-Angriffe zu verhindern:
|
||||
/// node_id (8 Bytes Little-Endian) || chunk_index (8 Bytes Little-Endian).
|
||||
#[inline]
|
||||
|
||||
+21
-4
@@ -1354,7 +1354,20 @@ fn handle_upgrade_format(container_path: &Path) -> Result<()> {
|
||||
}
|
||||
};
|
||||
|
||||
if keys.version() >= FORMAT_VERSION_V3 {
|
||||
// F-05: Wenn bereits V3, aber MAC fehlt (Zombie-Zustand), repariere mit upgrade_to_v3
|
||||
let is_zombie = {
|
||||
use rusqlite::OptionalExtension;
|
||||
let conn = db.conn();
|
||||
conn.query_row(
|
||||
"SELECT 1 FROM meta WHERE slot_id = 0 AND version >= 3 AND metadata_mac IS NULL AND restore_nonce IS NULL",
|
||||
[],
|
||||
|_| Ok(true),
|
||||
)
|
||||
.optional()?
|
||||
.unwrap_or(false)
|
||||
};
|
||||
|
||||
if keys.version() >= FORMAT_VERSION_V3 && !is_zombie {
|
||||
println!(
|
||||
" • Container ist bereits Format V{} (keine Migration erforderlich).",
|
||||
keys.version()
|
||||
@@ -1365,9 +1378,13 @@ fn handle_upgrade_format(container_path: &Path) -> Result<()> {
|
||||
db.upgrade_to_v3(keys.dek())?;
|
||||
db.checkpoint()?;
|
||||
|
||||
println!(" ✔ Container erfolgreich auf Format V3 aktualisiert!");
|
||||
println!(" • Metadaten-Authentifizierung (HMAC-SHA256) aktiviert.");
|
||||
println!(" • Replay-Schutz für Chunks (24-Byte AAD mit Generation) aktiviert.");
|
||||
if is_zombie {
|
||||
println!(" ✔ Metadaten-MAC erfolgreich rekonstruiert und gebunden (Format V3 Reparatur)!");
|
||||
} else {
|
||||
println!(" ✔ Container erfolgreich auf Format V3 aktualisiert!");
|
||||
println!(" • Metadaten-Authentifizierung (HMAC-SHA256) aktiviert.");
|
||||
println!(" • Replay-Schutz für Chunks (24-Byte AAD mit Generation) aktiviert.");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
+29
-2
@@ -199,10 +199,20 @@ pub async fn mount_container(
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(crate::storage::MetadataMacStatus::NeedsTranscriptVer) => {
|
||||
if !stealth {
|
||||
println!(
|
||||
" • Metadaten-MAC: ℹ Format-V3 Metadaten-MAC auf Transcript-Version 2 aktualisiert (F-04)"
|
||||
);
|
||||
}
|
||||
db.set_active_slot_and_dek(vault_id, dek.clone());
|
||||
db.update_metadata_mac()?;
|
||||
db.checkpoint()?;
|
||||
}
|
||||
Ok(crate::storage::MetadataMacStatus::LegacyValid) => {
|
||||
if !stealth {
|
||||
println!(
|
||||
" • Metadaten-MAC: ℹ Format-V3 Metadaten-MAC auf neues Transcript migriert (F-01)"
|
||||
" • Metadaten-MAC: ℹ Format-V3 Metadaten-MAC von Legacy-Knoten auf Transcript-Version 2 migriert (F-01/F-04)"
|
||||
);
|
||||
}
|
||||
db.set_active_slot_and_dek(vault_id, dek.clone());
|
||||
@@ -245,8 +255,25 @@ pub async fn mount_container(
|
||||
db.checkpoint()?;
|
||||
}
|
||||
Ok(crate::storage::MetadataMacStatus::Invalid) => {
|
||||
// F-05: Prüfen, ob es sich um den unvollständigen Zombie-Zustand handelt
|
||||
let is_zombie = {
|
||||
use rusqlite::OptionalExtension;
|
||||
let conn = db.conn();
|
||||
conn.query_row(
|
||||
"SELECT 1 FROM meta WHERE slot_id = ?1 AND version >= 3 AND metadata_mac IS NULL AND restore_nonce IS NULL",
|
||||
[vault_id],
|
||||
|_| Ok(true),
|
||||
)
|
||||
.optional()?
|
||||
.unwrap_or(false)
|
||||
};
|
||||
if is_zombie {
|
||||
bail!(
|
||||
"Container befindet sich im unvollständigen Zustand (Format V3 ohne Metadaten-MAC). Führen Sie zur Reparatur 'sanctum upgrade-format --path <CONTAINER>' nach Entsperrung aus."
|
||||
);
|
||||
}
|
||||
bail!(
|
||||
"Metadaten-MAC-Verifikation fehlgeschlagen: Die Container-Metadaten wurden manipuliert oder sind beschädigt (K-01 / F-01 / F-02)."
|
||||
"Metadaten-MAC-Verifikation fehlgeschlagen: Die Container-Metadaten wurden manipuliert oder sind beschädigt (K-01 / F-01 / F-02 / F-04)."
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
|
||||
+185
-51
@@ -9,10 +9,10 @@ use rusqlite::{params, Connection, OptionalExtension};
|
||||
use tracing::{error, warn};
|
||||
|
||||
use crate::crypto::{
|
||||
compute_metadata_mac, decrypt_node_name, derive_kek, derive_metadata_mac_key,
|
||||
compute_metadata_mac_v2, decrypt_node_name, derive_kek, derive_metadata_mac_key,
|
||||
encrypt_node_name, generate_dummy_slot, unwrap_key_payload, validate_kdf_params,
|
||||
verify_metadata_mac, KdfParams, CHUNK_SIZE, FORMAT_VERSION, FORMAT_VERSION_V1,
|
||||
FORMAT_VERSION_V2, FORMAT_VERSION_V3, MAGIC_BYTES,
|
||||
verify_metadata_mac, verify_metadata_mac_v2, KdfParams, CHUNK_SIZE, FORMAT_VERSION,
|
||||
FORMAT_VERSION_V1, FORMAT_VERSION_V2, FORMAT_VERSION_V3, MAGIC_BYTES,
|
||||
};
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
@@ -211,10 +211,13 @@ fn current_timestamp() -> u64 {
|
||||
/// Status der Metadaten-MAC-Integritätsprüfung (K-01 / F-01 / F-02).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum MetadataMacStatus {
|
||||
/// Metadaten-MAC ist vorhanden und stimmt mit den kanonischen Metadaten inklusive Chunk-Generationen überein (F-01).
|
||||
/// Metadaten-MAC ist vorhanden und stimmt mit Transcript-Version 2 (inklusive Chunk-Generationen) überein (F-01 / F-04).
|
||||
Valid,
|
||||
/// Metadaten-MAC stimmt mit dem älteren V3-Transcript (nur Knoten-Metadaten, ohne Chunk-Generationen) überein.
|
||||
/// Wird beim Mounten transparent auf das neue V3.1-Transcript gebunden (F-01 Migration).
|
||||
/// Metadaten-MAC stammt aus v0.9.4 (Knoten + Chunk-Generationen, jedoch ohne transcript_ver im HMAC-Input).
|
||||
/// Wird beim Mounten transparent auf Transcript-Version 2 aktualisiert (F-04 Migration).
|
||||
NeedsTranscriptVer,
|
||||
/// Metadaten-MAC stimmt mit dem älteren V3.0-Transcript (nur Knoten-Metadaten aus v0.9.3) überein.
|
||||
/// Wird beim Mounten transparent auf das neue V3.2-Transcript gebunden (F-01 / F-04 Migration).
|
||||
LegacyValid,
|
||||
/// Container-Header wurde aus einem Backup restauriert (restore_nonce vorhanden und metadata_gen == 0 && metadata_mac IS NULL).
|
||||
/// Der MAC muss beim Mounten mit --rebuild-mac (oder interaktivem 'JA') neu aufgebaut werden (F-02).
|
||||
@@ -486,6 +489,12 @@ impl Database {
|
||||
// Spalte restore_nonce in meta (Format V3 / F-02)
|
||||
let _ = conn.execute("ALTER TABLE meta ADD COLUMN restore_nonce BLOB", []);
|
||||
|
||||
// Spalte transcript_ver in meta (Format V3.2 / F-04)
|
||||
let _ = conn.execute(
|
||||
"ALTER TABLE meta ADD COLUMN transcript_ver INTEGER NOT NULL DEFAULT 1",
|
||||
[],
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -653,6 +662,7 @@ impl Database {
|
||||
metadata_mac BLOB,
|
||||
metadata_gen INTEGER NOT NULL DEFAULT 0,
|
||||
restore_nonce BLOB,
|
||||
transcript_ver INTEGER NOT NULL DEFAULT 1,
|
||||
lock_pid INTEGER,
|
||||
lock_host TEXT,
|
||||
lock_time INTEGER
|
||||
@@ -967,6 +977,7 @@ impl Database {
|
||||
metadata_mac BLOB,
|
||||
metadata_gen INTEGER NOT NULL DEFAULT 0,
|
||||
restore_nonce BLOB,
|
||||
transcript_ver INTEGER NOT NULL DEFAULT 1,
|
||||
lock_pid INTEGER,
|
||||
lock_host TEXT,
|
||||
lock_time INTEGER
|
||||
@@ -2203,8 +2214,16 @@ impl Database {
|
||||
/// Erzeugt die deterministische kanonische Byterepräsentation für einen spezifischen Vault
|
||||
/// inklusive sortierter Chunk-Generationen (Format V3.1 / F-01).
|
||||
pub fn canonical_nodes_bytes_for_vault(&self, vault_id: u32) -> Result<Vec<u8>> {
|
||||
let mut buf = self.canonical_nodes_bytes_for_vault_legacy(vault_id)?;
|
||||
let conn = self.conn();
|
||||
Self::canonical_nodes_bytes_for_vault_on_conn(&self.conn(), vault_id)
|
||||
}
|
||||
|
||||
/// Erzeugt die deterministische kanonische Byterepräsentation für einen spezifischen Vault
|
||||
/// auf einer bestehenden SQLite-Verbindung oder Transaktion (F-05).
|
||||
pub fn canonical_nodes_bytes_for_vault_on_conn(
|
||||
conn: &rusqlite::Connection,
|
||||
vault_id: u32,
|
||||
) -> Result<Vec<u8>> {
|
||||
let mut buf = Self::canonical_nodes_bytes_for_vault_legacy_on_conn(conn, vault_id)?;
|
||||
|
||||
if vault_id == 1 {
|
||||
let mut stmt = conn.prepare(
|
||||
@@ -2255,7 +2274,14 @@ impl Database {
|
||||
|
||||
/// Erzeugt die Legacy-Byterepräsentation ohne Chunk-Generationen (zur Migration bestehender V3-Container, F-01).
|
||||
pub fn canonical_nodes_bytes_for_vault_legacy(&self, vault_id: u32) -> Result<Vec<u8>> {
|
||||
let conn = self.conn();
|
||||
Self::canonical_nodes_bytes_for_vault_legacy_on_conn(&self.conn(), vault_id)
|
||||
}
|
||||
|
||||
/// Erzeugt die Legacy-Byterepräsentation ohne Chunk-Generationen auf einer bestehenden SQLite-Verbindung (F-05).
|
||||
pub fn canonical_nodes_bytes_for_vault_legacy_on_conn(
|
||||
conn: &rusqlite::Connection,
|
||||
vault_id: u32,
|
||||
) -> Result<Vec<u8>> {
|
||||
let mut buf = Vec::new();
|
||||
|
||||
if vault_id == 1 {
|
||||
@@ -2365,11 +2391,11 @@ impl Database {
|
||||
let next_gen = current_gen + 1;
|
||||
let canonical = self.canonical_nodes_bytes_for_vault(slot_id)?;
|
||||
let mac_key = derive_metadata_mac_key(&dek);
|
||||
let new_mac = compute_metadata_mac(&mac_key, next_gen, &canonical);
|
||||
let new_mac = compute_metadata_mac_v2(&mac_key, next_gen, 2, &canonical);
|
||||
|
||||
let conn = self.conn();
|
||||
conn.execute(
|
||||
"UPDATE meta SET metadata_mac = ?1, metadata_gen = ?2 WHERE slot_id = ?3",
|
||||
"UPDATE meta SET metadata_mac = ?1, metadata_gen = ?2, transcript_ver = 2 WHERE slot_id = ?3",
|
||||
params![new_mac.as_slice(), next_gen, slot_id],
|
||||
)?;
|
||||
|
||||
@@ -2390,25 +2416,27 @@ 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<bool> {
|
||||
let status = self.verify_metadata_mac_status_for_slot(slot_id, dek)?;
|
||||
Ok(status == MetadataMacStatus::Valid || status == MetadataMacStatus::LegacyValid)
|
||||
Ok(status == MetadataMacStatus::Valid
|
||||
|| status == MetadataMacStatus::LegacyValid
|
||||
|| status == MetadataMacStatus::NeedsTranscriptVer)
|
||||
}
|
||||
|
||||
/// Prüft den detaillierten Integritätsstatus des Metadaten-MAC für einen spezifischen Slot (F-01 / F-02).
|
||||
/// Prüft den detaillierten Integritätsstatus des Metadaten-MAC für einen spezifischen Slot (F-01 / F-02 / F-04).
|
||||
pub fn verify_metadata_mac_status_for_slot(
|
||||
&self,
|
||||
slot_id: u32,
|
||||
dek: &[u8; 32],
|
||||
) -> Result<MetadataMacStatus> {
|
||||
let conn = self.conn();
|
||||
let meta_row: Option<(u32, Option<Vec<u8>>, u64, Option<Vec<u8>>)> = conn
|
||||
let meta_row: Option<(u32, Option<Vec<u8>>, u64, Option<Vec<u8>>, u32)> = conn
|
||||
.query_row(
|
||||
"SELECT version, metadata_mac, metadata_gen, restore_nonce FROM meta WHERE slot_id = ?1 LIMIT 1",
|
||||
"SELECT version, metadata_mac, metadata_gen, restore_nonce, transcript_ver FROM meta WHERE slot_id = ?1 LIMIT 1",
|
||||
params![slot_id],
|
||||
|r| Ok((r.get(0)?, r.get(1).ok(), r.get(2).unwrap_or(0), r.get(3).ok())),
|
||||
|r| Ok((r.get(0)?, r.get(1).ok(), r.get(2).unwrap_or(0), r.get(3).ok(), r.get(4).unwrap_or(1))),
|
||||
)
|
||||
.optional()?;
|
||||
|
||||
let Some((version, mac_opt, gen, restore_nonce)) = meta_row else {
|
||||
let Some((version, mac_opt, gen, restore_nonce, transcript_ver)) = meta_row else {
|
||||
return Ok(MetadataMacStatus::Invalid);
|
||||
};
|
||||
|
||||
@@ -2442,15 +2470,29 @@ impl Database {
|
||||
|
||||
let mac_key = derive_metadata_mac_key(dek);
|
||||
|
||||
// F-01: 1. Neues kanonisches Transcript prüfen (inklusive Chunk-Generationen)
|
||||
let canonical = self.canonical_nodes_bytes_for_vault(slot_id)?;
|
||||
if verify_metadata_mac(&mac_key, gen, &canonical, &expected_mac) {
|
||||
// F-04: Stufe 1: Rechne MAC mit ver=2 + canonical_v2 (0.9.5-native)
|
||||
// Treffer -> Valid. Fertig. Nie Legacy versuchen.
|
||||
let canonical_v2 = self.canonical_nodes_bytes_for_vault(slot_id)?;
|
||||
if verify_metadata_mac_v2(&mac_key, gen, 2, &canonical_v2, &expected_mac) {
|
||||
return Ok(MetadataMacStatus::Valid);
|
||||
}
|
||||
|
||||
// F-01 / §7: 2. Altes Transcript prüfen (nur Knoten, ohne Chunk-Generationen) für sichere Migration
|
||||
// F-04: Wenn Stufe 1 fehlschlägt und der Container in SQLite bereits transcript_ver >= 2
|
||||
// hat, ist kein Fallback auf Legacy erlaubt (Verhinderung von Replay-Angriffen nach Rebind).
|
||||
if transcript_ver >= 2 {
|
||||
return Ok(MetadataMacStatus::Invalid);
|
||||
}
|
||||
|
||||
// F-04: Stufe 2: 0.9.4-native (ohne transcript_ver im Input, canonical_v2 mit Generationen)
|
||||
if verify_metadata_mac(&mac_key, gen, &canonical_v2, &expected_mac) {
|
||||
return Ok(MetadataMacStatus::NeedsTranscriptVer);
|
||||
}
|
||||
|
||||
// F-04: Stufe 3: 0.9.3-native (ohne transcript_ver oder ver=1, canonical_legacy nur Knoten)
|
||||
let canonical_legacy = self.canonical_nodes_bytes_for_vault_legacy(slot_id)?;
|
||||
if verify_metadata_mac(&mac_key, gen, &canonical_legacy, &expected_mac) {
|
||||
if verify_metadata_mac(&mac_key, gen, &canonical_legacy, &expected_mac)
|
||||
|| verify_metadata_mac_v2(&mac_key, gen, 1, &canonical_legacy, &expected_mac)
|
||||
{
|
||||
return Ok(MetadataMacStatus::LegacyValid);
|
||||
}
|
||||
|
||||
@@ -2462,18 +2504,25 @@ impl Database {
|
||||
self.upgrade_to_v3_ext(dek, None)
|
||||
}
|
||||
|
||||
/// Führt ein transaktionales Upgrade des Containerformats auf Format V3 durch (F-03).
|
||||
/// Läuft in einer einzigen atomaren SQLite-Transaktion. Bei jeglichem Chunk-Fehler
|
||||
/// wird ein Rollback durchgeführt und der Fehler fail-closed propagiert.
|
||||
/// Führt ein transaktionales Upgrade des Containerformats auf Format V3 durch (F-03 / F-05).
|
||||
/// Läuft in einer einzigen atomaren SQLite-Transaktion. Sowohl die Re-Verschlüsselung der Chunks,
|
||||
/// das Setzen von version = 3 als auch die Berechnung und Speicherung des Metadaten-MACs (transcript_ver = 2)
|
||||
/// erfolgen vor dem Commit (F-05). Bei jeglichem Chunk-Fehler wird ein Rollback durchgeführt.
|
||||
pub fn upgrade_to_v3_ext(&self, dek_0: &[u8; 32], dek_1: Option<&[u8; 32]>) -> Result<()> {
|
||||
let mut conn = self.conn();
|
||||
let version: u32 = conn.query_row(
|
||||
"SELECT version FROM meta WHERE slot_id = 0 LIMIT 1",
|
||||
[],
|
||||
|r| r.get(0),
|
||||
)?;
|
||||
let (version, mac_opt, nonce_opt): (u32, Option<Vec<u8>>, Option<Vec<u8>>) = conn
|
||||
.query_row(
|
||||
"SELECT version, metadata_mac, restore_nonce FROM meta WHERE slot_id = 0 LIMIT 1",
|
||||
[],
|
||||
|r| Ok((r.get(0)?, r.get(1).ok(), r.get(2).ok())),
|
||||
)?;
|
||||
|
||||
if version >= FORMAT_VERSION_V3 {
|
||||
// F-05: Zombie-Container Reparatur (V3, MAC NULL, restore_nonce NULL)
|
||||
if mac_opt.is_none() && nonce_opt.is_none() {
|
||||
drop(conn);
|
||||
return self.repair_v3_mac(dek_0, dek_1);
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -2483,12 +2532,16 @@ impl Database {
|
||||
[],
|
||||
);
|
||||
let _ = conn.execute("ALTER TABLE meta ADD COLUMN restore_nonce BLOB", []);
|
||||
let _ = conn.execute(
|
||||
"ALTER TABLE meta ADD COLUMN transcript_ver INTEGER NOT NULL DEFAULT 1",
|
||||
[],
|
||||
);
|
||||
let _ = conn.execute(
|
||||
"ALTER TABLE chunks ADD COLUMN generation INTEGER NOT NULL DEFAULT 0",
|
||||
[],
|
||||
);
|
||||
|
||||
// F-03: Gesamte Migration in einer einzigen atomaren SQLite-Transaktion
|
||||
// F-03 / F-05: Gesamte Migration inkl. MAC-Persistierung in einer einzigen atomaren SQLite-Transaktion
|
||||
let tx = conn.transaction()?;
|
||||
|
||||
// Alle bestehenden Chunks von alter 16-Byte-AAD auf Format V3 24-Byte-AAD (generation = 0) umverschlüsseln
|
||||
@@ -2578,44 +2631,124 @@ impl Database {
|
||||
}
|
||||
}
|
||||
|
||||
// F-03: Aktualisiere version = 3 für Slot 0 UND Slot 1 (inkl. Dummy-Slot)
|
||||
// F-05: In derselben Transaktion vor dem Commit: Metadaten-MAC berechnen und persistieren
|
||||
let canonical_0 = Self::canonical_nodes_bytes_for_vault_on_conn(&tx, 0)?;
|
||||
let mac_key_0 = derive_metadata_mac_key(dek_0);
|
||||
let mac_0 = compute_metadata_mac_v2(&mac_key_0, 1, 2, &canonical_0);
|
||||
|
||||
tx.execute(
|
||||
"UPDATE meta SET version = ?1, metadata_gen = 0",
|
||||
[FORMAT_VERSION_V3],
|
||||
"UPDATE meta SET version = ?1, metadata_mac = ?2, metadata_gen = 1, transcript_ver = 2 WHERE slot_id = 0",
|
||||
params![FORMAT_VERSION_V3, mac_0.as_slice()],
|
||||
)?;
|
||||
|
||||
tx.commit()?;
|
||||
drop(conn);
|
||||
|
||||
// Slot 0 Metadaten-MAC berechnen und persistieren
|
||||
self.set_active_slot_and_dek(0, Zeroizing::new(*dek_0));
|
||||
self.update_metadata_mac()?;
|
||||
|
||||
// Falls echte SQLite-Knoten unter Root 2 existieren (Legacy Hidden Vault) und dek_1 vorliegt,
|
||||
// auch Slot 1 MAC aktualisieren. (Bei Modell A bleibt Carrier-Inner Carrier-AEAD).
|
||||
let has_vault1_nodes: bool = {
|
||||
let conn = self.conn();
|
||||
conn.query_row(
|
||||
let has_vault1_nodes: bool = tx
|
||||
.query_row(
|
||||
"SELECT 1 FROM nodes WHERE parent_id = 2 LIMIT 1",
|
||||
[],
|
||||
|_| Ok(true),
|
||||
)
|
||||
.optional()?
|
||||
.unwrap_or(false)
|
||||
};
|
||||
.unwrap_or(false);
|
||||
|
||||
if has_vault1_nodes {
|
||||
if let Some(d1) = dek_1 {
|
||||
self.set_active_slot_and_dek(1, Zeroizing::new(*d1));
|
||||
self.update_metadata_mac()?;
|
||||
let canonical_1 = Self::canonical_nodes_bytes_for_vault_on_conn(&tx, 1)?;
|
||||
let mac_key_1 = derive_metadata_mac_key(d1);
|
||||
let mac_1 = compute_metadata_mac_v2(&mac_key_1, 1, 2, &canonical_1);
|
||||
tx.execute(
|
||||
"UPDATE meta SET version = ?1, metadata_mac = ?2, metadata_gen = 1, transcript_ver = 2 WHERE slot_id = 1",
|
||||
params![FORMAT_VERSION_V3, mac_1.as_slice()],
|
||||
)?;
|
||||
}
|
||||
} else {
|
||||
tx.execute(
|
||||
"UPDATE meta SET version = ?1, metadata_gen = 0, transcript_ver = 2 WHERE slot_id != 0 AND (metadata_mac IS NULL OR slot_id = 1)",
|
||||
params![FORMAT_VERSION_V3],
|
||||
)?;
|
||||
}
|
||||
|
||||
// Aktive Session auf Slot 0 zurücksetzen
|
||||
tx.commit()?;
|
||||
drop(conn);
|
||||
|
||||
// Aktive Session auf Slot 0 setzen
|
||||
self.set_active_slot_and_dek(0, Zeroizing::new(*dek_0));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Repariert einen unvollständigen Zombie-Container (version >= 3, metadata_mac IS NULL, restore_nonce IS NULL) (F-05).
|
||||
/// Bricht strikt ab, falls restore_nonce gesetzt ist (dies ist F-02 vorbehalten).
|
||||
pub fn repair_v3_mac(&self, dek_0: &[u8; 32], dek_1: Option<&[u8; 32]>) -> Result<()> {
|
||||
let mut conn = self.conn();
|
||||
let (version, mac_opt, nonce_opt): (u32, Option<Vec<u8>>, Option<Vec<u8>>) = conn
|
||||
.query_row(
|
||||
"SELECT version, metadata_mac, restore_nonce FROM meta WHERE slot_id = 0 LIMIT 1",
|
||||
[],
|
||||
|r| Ok((r.get(0)?, r.get(1).ok(), r.get(2).ok())),
|
||||
)?;
|
||||
|
||||
if version < FORMAT_VERSION_V3 {
|
||||
bail!("Reparatur abgebrochen: Container ist nicht Format V3.");
|
||||
}
|
||||
|
||||
if nonce_opt.is_some() {
|
||||
bail!(
|
||||
"Reparatur abgebrochen: Container enthält restore_nonce Token. Starten Sie 'sanctum mount --rebuild-mac' (F-02)."
|
||||
);
|
||||
}
|
||||
|
||||
if mac_opt.is_some() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let _ = conn.execute(
|
||||
"ALTER TABLE meta ADD COLUMN transcript_ver INTEGER NOT NULL DEFAULT 1",
|
||||
[],
|
||||
);
|
||||
|
||||
let tx = conn.transaction()?;
|
||||
|
||||
let canonical_0 = Self::canonical_nodes_bytes_for_vault_on_conn(&tx, 0)?;
|
||||
let mac_key_0 = derive_metadata_mac_key(dek_0);
|
||||
let mac_0 = compute_metadata_mac_v2(&mac_key_0, 1, 2, &canonical_0);
|
||||
|
||||
tx.execute(
|
||||
"UPDATE meta SET version = ?1, metadata_mac = ?2, metadata_gen = 1, transcript_ver = 2 WHERE slot_id = 0",
|
||||
params![FORMAT_VERSION_V3, mac_0.as_slice()],
|
||||
)?;
|
||||
|
||||
let has_vault1_nodes: bool = tx
|
||||
.query_row(
|
||||
"SELECT 1 FROM nodes WHERE parent_id = 2 LIMIT 1",
|
||||
[],
|
||||
|_| Ok(true),
|
||||
)
|
||||
.optional()?
|
||||
.unwrap_or(false);
|
||||
|
||||
if has_vault1_nodes {
|
||||
if let Some(d1) = dek_1 {
|
||||
let canonical_1 = Self::canonical_nodes_bytes_for_vault_on_conn(&tx, 1)?;
|
||||
let mac_key_1 = derive_metadata_mac_key(d1);
|
||||
let mac_1 = compute_metadata_mac_v2(&mac_key_1, 1, 2, &canonical_1);
|
||||
tx.execute(
|
||||
"UPDATE meta SET version = ?1, metadata_mac = ?2, metadata_gen = 1, transcript_ver = 2 WHERE slot_id = 1",
|
||||
params![FORMAT_VERSION_V3, mac_1.as_slice()],
|
||||
)?;
|
||||
}
|
||||
} else {
|
||||
tx.execute(
|
||||
"UPDATE meta SET version = ?1, metadata_gen = 0, transcript_ver = 2 WHERE slot_id != 0 AND (metadata_mac IS NULL OR slot_id = 1)",
|
||||
params![FORMAT_VERSION_V3],
|
||||
)?;
|
||||
}
|
||||
|
||||
tx.commit()?;
|
||||
drop(conn);
|
||||
self.set_active_slot_and_dek(0, Zeroizing::new(*dek_0));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Erzwingt einen SQLite WAL Checkpoint und leert das Write-Ahead-Log.
|
||||
pub fn checkpoint(&self) -> Result<()> {
|
||||
let conn = self.conn();
|
||||
@@ -2708,6 +2841,7 @@ impl Database {
|
||||
metadata_mac BLOB,
|
||||
metadata_gen INTEGER NOT NULL DEFAULT 0,
|
||||
restore_nonce BLOB,
|
||||
transcript_ver INTEGER NOT NULL DEFAULT 1,
|
||||
lock_pid INTEGER,
|
||||
lock_host TEXT,
|
||||
lock_time INTEGER
|
||||
|
||||
Reference in new issue
Block a user