fix(format-v3): harden system binding against replay and bypass attacks (F-01, F-02, F-03)
- F-02: Require restore_nonce token and explicit confirmation (--rebuild-mac) for PendingRebuild - F-01: Extend canonical MAC transcript to include chunk generation tuples (node_id, chunk_index, generation) and support transparent legacy migration - F-03: Make V2-to-V3 container upgrade atomic with transactional rollback and dual-slot version update - Bump version to 0.9.4 and update changelog and security docs
This commit is contained in:
+220
-53
@@ -208,15 +208,18 @@ fn current_timestamp() -> u64 {
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
/// Status der Metadaten-MAC-Integritätsprüfung (K-01 / R-NEW-1).
|
||||
/// 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 überein.
|
||||
/// Metadaten-MAC ist vorhanden und stimmt mit den kanonischen Metadaten inklusive Chunk-Generationen überein (F-01).
|
||||
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.
|
||||
/// 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).
|
||||
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).
|
||||
PendingRebuild,
|
||||
/// Metadaten-MAC fehlt (bei gen > 0) oder stimmt nicht mit den berechneten Daten überein (Manipulationsverdacht).
|
||||
/// Metadaten-MAC fehlt (ohne gültigen restore_nonce) oder stimmt nicht mit den berechneten Daten überein (Manipulationsverdacht).
|
||||
Invalid,
|
||||
}
|
||||
|
||||
@@ -480,6 +483,9 @@ impl Database {
|
||||
let _ = conn.execute("ALTER TABLE meta ADD COLUMN lock_host TEXT", []);
|
||||
let _ = conn.execute("ALTER TABLE meta ADD COLUMN lock_time INTEGER", []);
|
||||
|
||||
// Spalte restore_nonce in meta (Format V3 / F-02)
|
||||
let _ = conn.execute("ALTER TABLE meta ADD COLUMN restore_nonce BLOB", []);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -646,6 +652,7 @@ impl Database {
|
||||
header_tag BLOB NOT NULL,
|
||||
metadata_mac BLOB,
|
||||
metadata_gen INTEGER NOT NULL DEFAULT 0,
|
||||
restore_nonce BLOB,
|
||||
lock_pid INTEGER,
|
||||
lock_host TEXT,
|
||||
lock_time INTEGER
|
||||
@@ -959,6 +966,7 @@ impl Database {
|
||||
header_tag BLOB NOT NULL,
|
||||
metadata_mac BLOB,
|
||||
metadata_gen INTEGER NOT NULL DEFAULT 0,
|
||||
restore_nonce BLOB,
|
||||
lock_pid INTEGER,
|
||||
lock_host TEXT,
|
||||
lock_time INTEGER
|
||||
@@ -2192,8 +2200,61 @@ impl Database {
|
||||
self.canonical_nodes_bytes_for_vault(0)
|
||||
}
|
||||
|
||||
/// Erzeugt die deterministische kanonische Byterepräsentation für einen spezifischen Vault.
|
||||
/// 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();
|
||||
|
||||
if vault_id == 1 {
|
||||
let mut stmt = conn.prepare(
|
||||
"WITH RECURSIVE vault1(id) AS (
|
||||
SELECT 2
|
||||
UNION ALL
|
||||
SELECT n.id FROM nodes n JOIN vault1 v ON n.parent_id = v.id
|
||||
)
|
||||
SELECT c.node_id, c.chunk_index, c.generation
|
||||
FROM chunks c
|
||||
WHERE c.node_id IN (SELECT id FROM vault1)
|
||||
ORDER BY c.node_id ASC, c.chunk_index ASC",
|
||||
)?;
|
||||
let mut rows = stmt.query([])?;
|
||||
while let Some(row) = rows.next()? {
|
||||
let node_id: i64 = row.get(0)?;
|
||||
let chunk_index: u32 = row.get(1)?;
|
||||
let generation: u64 = row.get(2)?;
|
||||
buf.extend_from_slice(&node_id.to_le_bytes());
|
||||
buf.extend_from_slice(&chunk_index.to_le_bytes());
|
||||
buf.extend_from_slice(&generation.to_le_bytes());
|
||||
}
|
||||
} else {
|
||||
let mut stmt = conn.prepare(
|
||||
"WITH RECURSIVE vault0(id) AS (
|
||||
SELECT 1
|
||||
UNION ALL
|
||||
SELECT n.id FROM nodes n JOIN vault0 v ON n.parent_id = v.id
|
||||
)
|
||||
SELECT c.node_id, c.chunk_index, c.generation
|
||||
FROM chunks c
|
||||
WHERE c.node_id = 2 OR c.node_id IN (SELECT id FROM vault0)
|
||||
ORDER BY c.node_id ASC, c.chunk_index ASC",
|
||||
)?;
|
||||
let mut rows = stmt.query([])?;
|
||||
while let Some(row) = rows.next()? {
|
||||
let node_id: i64 = row.get(0)?;
|
||||
let chunk_index: u32 = row.get(1)?;
|
||||
let generation: u64 = row.get(2)?;
|
||||
buf.extend_from_slice(&node_id.to_le_bytes());
|
||||
buf.extend_from_slice(&chunk_index.to_le_bytes());
|
||||
buf.extend_from_slice(&generation.to_le_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
/// 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();
|
||||
let mut buf = Vec::new();
|
||||
|
||||
@@ -2271,7 +2332,7 @@ impl Database {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Aktualisiert den Metadaten-MAC des aktiven Slots bei strukturellen Modifikationen (Format V3 / K-01).
|
||||
/// Aktualisiert den Metadaten-MAC des aktiven Slots bei strukturellen Modifikationen (Format V3 / K-01 & F-01).
|
||||
pub fn update_metadata_mac(&self) -> Result<()> {
|
||||
let session_opt = self
|
||||
.active_session
|
||||
@@ -2328,25 +2389,26 @@ 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> {
|
||||
Ok(self.verify_metadata_mac_status_for_slot(slot_id, dek)? == MetadataMacStatus::Valid)
|
||||
let status = self.verify_metadata_mac_status_for_slot(slot_id, dek)?;
|
||||
Ok(status == MetadataMacStatus::Valid || status == MetadataMacStatus::LegacyValid)
|
||||
}
|
||||
|
||||
/// Prüft den detaillierten Integritätsstatus des Metadaten-MAC für einen spezifischen Slot (R-NEW-1).
|
||||
/// Prüft den detaillierten Integritätsstatus des Metadaten-MAC für einen spezifischen Slot (F-01 / F-02).
|
||||
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)> = conn
|
||||
let meta_row: Option<(u32, Option<Vec<u8>>, u64, Option<Vec<u8>>)> = conn
|
||||
.query_row(
|
||||
"SELECT version, metadata_mac, metadata_gen FROM meta WHERE slot_id = ?1 LIMIT 1",
|
||||
"SELECT version, metadata_mac, metadata_gen, restore_nonce 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| Ok((r.get(0)?, r.get(1).ok(), r.get(2).unwrap_or(0), r.get(3).ok())),
|
||||
)
|
||||
.optional()?;
|
||||
|
||||
let Some((version, mac_opt, gen)) = meta_row else {
|
||||
let Some((version, mac_opt, gen, restore_nonce)) = meta_row else {
|
||||
return Ok(MetadataMacStatus::Invalid);
|
||||
};
|
||||
|
||||
@@ -2354,9 +2416,16 @@ impl Database {
|
||||
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);
|
||||
// F-02: Wenn metadata_mac fehlt (NULL):
|
||||
// NUR wenn restore_nonce vorhanden ist (32 Bytes) UND gen == 0,
|
||||
// ist der Status PendingRebuild. Andernfalls strikt Invalid (Fail-Closed, K-01 / F-02).
|
||||
if mac_opt.is_none() {
|
||||
if let Some(ref nonce) = restore_nonce {
|
||||
if nonce.len() == 32 && gen == 0 {
|
||||
return Ok(MetadataMacStatus::PendingRebuild);
|
||||
}
|
||||
}
|
||||
return Ok(MetadataMacStatus::Invalid);
|
||||
}
|
||||
|
||||
let Some(mac_bytes) = mac_opt else {
|
||||
@@ -2371,18 +2440,33 @@ impl Database {
|
||||
expected_mac.copy_from_slice(&mac_bytes);
|
||||
drop(conn);
|
||||
|
||||
let canonical = self.canonical_nodes_bytes_for_vault(slot_id)?;
|
||||
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) {
|
||||
Ok(MetadataMacStatus::Valid)
|
||||
} else {
|
||||
Ok(MetadataMacStatus::Invalid)
|
||||
return Ok(MetadataMacStatus::Valid);
|
||||
}
|
||||
|
||||
// F-01 / §7: 2. Altes Transcript prüfen (nur Knoten, ohne Chunk-Generationen) für sichere Migration
|
||||
let canonical_legacy = self.canonical_nodes_bytes_for_vault_legacy(slot_id)?;
|
||||
if verify_metadata_mac(&mac_key, gen, &canonical_legacy, &expected_mac) {
|
||||
return Ok(MetadataMacStatus::LegacyValid);
|
||||
}
|
||||
|
||||
Ok(MetadataMacStatus::Invalid)
|
||||
}
|
||||
|
||||
/// 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 & F-03).
|
||||
pub fn upgrade_to_v3(&self, dek: &[u8; 32]) -> Result<()> {
|
||||
let conn = self.conn();
|
||||
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.
|
||||
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",
|
||||
[],
|
||||
@@ -2398,15 +2482,19 @@ impl Database {
|
||||
"ALTER TABLE meta ADD COLUMN metadata_gen INTEGER NOT NULL DEFAULT 0",
|
||||
[],
|
||||
);
|
||||
let _ = conn.execute("ALTER TABLE meta ADD COLUMN restore_nonce BLOB", []);
|
||||
let _ = conn.execute(
|
||||
"ALTER TABLE chunks ADD COLUMN generation INTEGER NOT NULL DEFAULT 0",
|
||||
[],
|
||||
);
|
||||
|
||||
// K-02: Alle bestehenden Chunks von alter 16-Byte-AAD auf Format V3 24-Byte-AAD (generation = 0) umverschlüsseln
|
||||
// F-03: Gesamte Migration 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
|
||||
{
|
||||
let mut chunk_stmt =
|
||||
conn.prepare("SELECT node_id, chunk_index, nonce, tag, ciphertext FROM chunks")?;
|
||||
tx.prepare("SELECT node_id, chunk_index, nonce, tag, ciphertext FROM chunks")?;
|
||||
let chunk_rows: Vec<(i64, u32, [u8; 12], [u8; 16], Vec<u8>)> = chunk_stmt
|
||||
.query_map([], |row| {
|
||||
let node_id: i64 = row.get(0)?;
|
||||
@@ -2428,8 +2516,8 @@ impl Database {
|
||||
drop(chunk_stmt);
|
||||
|
||||
for (node_id, chunk_index, nonce, tag, ct) in chunk_rows {
|
||||
if let Ok(plaintext) = crate::crypto::decrypt_chunk(
|
||||
dek,
|
||||
let (plaintext, chunk_dek) = match crate::crypto::decrypt_chunk(
|
||||
dek_0,
|
||||
node_id,
|
||||
chunk_index,
|
||||
&ct,
|
||||
@@ -2438,39 +2526,93 @@ impl Database {
|
||||
version,
|
||||
0,
|
||||
) {
|
||||
if let Ok((new_ct, new_nonce, new_tag)) = crate::crypto::encrypt_chunk(
|
||||
dek,
|
||||
node_id,
|
||||
chunk_index,
|
||||
&plaintext,
|
||||
FORMAT_VERSION_V3,
|
||||
0,
|
||||
) {
|
||||
conn.execute(
|
||||
"UPDATE chunks SET nonce = ?1, tag = ?2, ciphertext = ?3, generation = 0 WHERE node_id = ?4 AND chunk_index = ?5",
|
||||
params![
|
||||
new_nonce.as_slice(),
|
||||
new_tag.as_slice(),
|
||||
new_ct,
|
||||
Ok(pt) => (pt, dek_0),
|
||||
Err(e0) => {
|
||||
if let Some(d1) = dek_1 {
|
||||
match crate::crypto::decrypt_chunk(
|
||||
d1,
|
||||
node_id,
|
||||
chunk_index
|
||||
],
|
||||
)?;
|
||||
chunk_index,
|
||||
&ct,
|
||||
&nonce,
|
||||
&tag,
|
||||
version,
|
||||
0,
|
||||
) {
|
||||
Ok(pt) => (pt, d1),
|
||||
Err(_) => {
|
||||
bail!(
|
||||
"Upgrade auf Format V3 abgebrochen: Chunk (Node {}, Index {}) konnte weder mit DEK_0 noch mit DEK_1 entschlüsselt werden.",
|
||||
node_id, chunk_index
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
bail!(
|
||||
"Upgrade auf Format V3 abgebrochen: Chunk (Node {}, Index {}) konnte mit DEK_0 nicht entschlüsselt werden ({}). Falls dieser Container einen Legacy-Hidden-Vault enthält, wird das zweite Passwort benötigt.",
|
||||
node_id, chunk_index, e0
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let (new_ct, new_nonce, new_tag) = crate::crypto::encrypt_chunk(
|
||||
chunk_dek,
|
||||
node_id,
|
||||
chunk_index,
|
||||
&plaintext,
|
||||
FORMAT_VERSION_V3,
|
||||
0,
|
||||
)?;
|
||||
|
||||
tx.execute(
|
||||
"UPDATE chunks SET nonce = ?1, tag = ?2, ciphertext = ?3, generation = 0 WHERE node_id = ?4 AND chunk_index = ?5",
|
||||
params![
|
||||
new_nonce.as_slice(),
|
||||
new_tag.as_slice(),
|
||||
new_ct,
|
||||
node_id,
|
||||
chunk_index
|
||||
],
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
conn.execute(
|
||||
"UPDATE meta SET version = ?1, metadata_gen = 0 WHERE slot_id = 0",
|
||||
// F-03: Aktualisiere version = 3 für Slot 0 UND Slot 1 (inkl. Dummy-Slot)
|
||||
tx.execute(
|
||||
"UPDATE meta SET version = ?1, metadata_gen = 0",
|
||||
[FORMAT_VERSION_V3],
|
||||
)?;
|
||||
|
||||
tx.commit()?;
|
||||
drop(conn);
|
||||
|
||||
self.set_active_dek(Zeroizing::new(*dek));
|
||||
// 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(
|
||||
"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 {
|
||||
self.set_active_slot_and_dek(1, Zeroizing::new(*d1));
|
||||
self.update_metadata_mac()?;
|
||||
}
|
||||
}
|
||||
|
||||
// Aktive Session auf Slot 0 zurücksetzen
|
||||
self.set_active_slot_and_dek(0, Zeroizing::new(*dek_0));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -2542,6 +2684,15 @@ impl Database {
|
||||
|
||||
/// Schreibt oder stellt die Metadaten in der `meta`-Tabelle wieder her (z. B. nach Restore oder Header-Neugenerierung).
|
||||
pub fn restore_meta(&self, meta: &ContainerMeta) -> Result<()> {
|
||||
self.restore_meta_with_nonce(meta, None)
|
||||
}
|
||||
|
||||
/// Schreibt oder stellt die Metadaten in der `meta`-Tabelle mit optionalem restore_nonce Token wieder her (F-02).
|
||||
pub fn restore_meta_with_nonce(
|
||||
&self,
|
||||
meta: &ContainerMeta,
|
||||
restore_nonce: Option<&[u8; 32]>,
|
||||
) -> Result<()> {
|
||||
let conn = self.conn();
|
||||
|
||||
conn.execute_batch(
|
||||
@@ -2556,6 +2707,7 @@ impl Database {
|
||||
header_tag BLOB NOT NULL,
|
||||
metadata_mac BLOB,
|
||||
metadata_gen INTEGER NOT NULL DEFAULT 0,
|
||||
restore_nonce BLOB,
|
||||
lock_pid INTEGER,
|
||||
lock_host TEXT,
|
||||
lock_time INTEGER
|
||||
@@ -2564,6 +2716,8 @@ impl Database {
|
||||
|
||||
conn.execute("DELETE FROM meta", [])?;
|
||||
|
||||
let nonce_bytes = restore_nonce.map(|n| n.as_slice());
|
||||
|
||||
let mut has_slot1 = false;
|
||||
if !meta.slots.is_empty() {
|
||||
for slot in &meta.slots {
|
||||
@@ -2572,8 +2726,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, metadata_mac, metadata_gen)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, NULL, 0)",
|
||||
"INSERT INTO meta (slot_id, magic, version, kdf_salt, kdf_params, wrapped_dek, header_nonce, header_tag, metadata_mac, metadata_gen, restore_nonce)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, NULL, 0, ?9)",
|
||||
params![
|
||||
slot.slot_id,
|
||||
MAGIC_BYTES.as_slice(),
|
||||
@@ -2583,14 +2737,15 @@ impl Database {
|
||||
slot.wrapped_dek,
|
||||
slot.header_nonce.as_slice(),
|
||||
slot.header_tag.as_slice(),
|
||||
nonce_bytes,
|
||||
],
|
||||
)?;
|
||||
}
|
||||
} 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, metadata_mac, metadata_gen)
|
||||
VALUES (0, ?1, ?2, ?3, ?4, ?5, ?6, ?7, NULL, 0)",
|
||||
"INSERT INTO meta (slot_id, magic, version, kdf_salt, kdf_params, wrapped_dek, header_nonce, header_tag, metadata_mac, metadata_gen, restore_nonce)
|
||||
VALUES (0, ?1, ?2, ?3, ?4, ?5, ?6, ?7, NULL, 0, ?8)",
|
||||
params![
|
||||
MAGIC_BYTES.as_slice(),
|
||||
meta.version,
|
||||
@@ -2599,6 +2754,7 @@ impl Database {
|
||||
meta.wrapped_dek,
|
||||
meta.header_nonce.as_slice(),
|
||||
meta.header_tag.as_slice(),
|
||||
nonce_bytes,
|
||||
],
|
||||
)?;
|
||||
}
|
||||
@@ -2608,8 +2764,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, metadata_mac, metadata_gen)
|
||||
VALUES (1, ?1, ?2, ?3, ?4, ?5, ?6, ?7, NULL, 0)",
|
||||
"INSERT INTO meta (slot_id, magic, version, kdf_salt, kdf_params, wrapped_dek, header_nonce, header_tag, metadata_mac, metadata_gen, restore_nonce)
|
||||
VALUES (1, ?1, ?2, ?3, ?4, ?5, ?6, ?7, NULL, 0, ?8)",
|
||||
params![
|
||||
MAGIC_BYTES.as_slice(),
|
||||
FORMAT_VERSION,
|
||||
@@ -2618,6 +2774,7 @@ impl Database {
|
||||
dummy_dek.as_slice(),
|
||||
dummy_nonce.as_slice(),
|
||||
dummy_tag.as_slice(),
|
||||
nonce_bytes,
|
||||
],
|
||||
)?;
|
||||
}
|
||||
@@ -2625,6 +2782,16 @@ impl Database {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Löscht das Restore-Token nach erfolgreichem MAC-Rebuild (F-02).
|
||||
pub fn clear_restore_nonce(&self, slot_id: u32) -> Result<()> {
|
||||
let conn = self.conn();
|
||||
conn.execute(
|
||||
"UPDATE meta SET restore_nonce = NULL WHERE slot_id = ?1",
|
||||
params![slot_id],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Führt SQLite-eigene Integritäts- und Foreign-Key-Prüfungen aus.
|
||||
pub fn run_sqlite_integrity_check(&self) -> Result<Vec<String>> {
|
||||
let conn = self.conn();
|
||||
|
||||
Reference in New Issue
Block a user