fix(crypto): K-03 — emergency card persistence warning and rekey command

This commit is contained in:
2026-09-19 00:58:39 +02:00
parent 36a4094336
commit bdddd812ac
8 changed files with 560 additions and 9 deletions
+128 -4
View File
@@ -1128,6 +1128,126 @@ impl Database {
}
}
/// Verschlüsselt alle Chunks eines Vaults mit einem neuen DEK um (K-03 Rekeying).
pub fn rekey_vault(
&self,
slot_id: u32,
old_dek: &[u8; 32],
new_dek: &[u8; 32],
version: u32,
) -> Result<()> {
let mut conn = self.conn.lock().unwrap();
let tx = conn.transaction()?;
// Ermittle alle Node-IDs dieses Vaults
let node_ids: Vec<i64> = if slot_id == 1 {
let mut stmt = tx.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 id FROM vault1",
)?;
let rows = stmt.query_map([], |r| r.get(0))?;
rows.filter_map(|r| r.ok()).collect()
} else {
let mut stmt = tx.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 id FROM vault0",
)?;
let rows = stmt.query_map([], |r| r.get(0))?;
rows.filter_map(|r| r.ok()).collect()
};
// Option B: Falls Slot 1, verschlüsselte Dateinamen unter parent_id = 2 umverschlüsseln
if slot_id == 1 {
let mut name_stmt =
tx.prepare("SELECT id, parent_id, name FROM nodes WHERE parent_id = 2")?;
let node_rows: Vec<(i64, i64, String)> = name_stmt
.query_map([], |r| Ok((r.get(0)?, r.get(1)?, r.get(2)?)))?
.filter_map(|r| r.ok())
.collect();
drop(name_stmt);
for (id, parent_id, enc_name) in node_rows {
if let Some(decrypted_name) =
crate::crypto::decrypt_node_name(old_dek, parent_id, &enc_name)
{
let new_enc =
crate::crypto::encrypt_node_name(new_dek, parent_id, &decrypted_name);
tx.execute(
"UPDATE nodes SET name = ?1 WHERE id = ?2",
params![new_enc, id],
)?;
}
}
}
// Chunks der betroffenen Nodes umverschlüsseln
for nid in node_ids {
let mut chunk_stmt = tx.prepare(
"SELECT chunk_index, generation, nonce, tag, ciphertext FROM chunks WHERE node_id = ?1",
)?;
let chunk_rows: Vec<(u32, u64, [u8; 12], [u8; 16], Vec<u8>)> = chunk_stmt
.query_map(params![nid], |row| {
let chunk_index: u32 = row.get(0)?;
let generation: i64 = row.get(1).unwrap_or(0);
let nonce_vec: Vec<u8> = row.get(2)?;
let tag_vec: Vec<u8> = row.get(3)?;
let ciphertext: Vec<u8> = row.get(4)?;
let mut nonce = [0u8; 12];
let mut tag = [0u8; 16];
if nonce_vec.len() == 12 {
nonce.copy_from_slice(&nonce_vec);
}
if tag_vec.len() == 16 {
tag.copy_from_slice(&tag_vec);
}
Ok((chunk_index, generation as u64, nonce, tag, ciphertext))
})?
.filter_map(|r| r.ok())
.collect();
drop(chunk_stmt);
for (chunk_index, generation, nonce, tag, ct) in chunk_rows {
let plaintext = crate::crypto::decrypt_chunk(
old_dek,
nid,
chunk_index,
&ct,
&nonce,
&tag,
version,
generation,
)?;
let (new_ct, new_nonce, new_tag) = crate::crypto::encrypt_chunk(
new_dek,
nid,
chunk_index,
&plaintext,
version,
generation,
)?;
tx.execute(
"UPDATE chunks SET nonce = ?1, tag = ?2, ciphertext = ?3 WHERE node_id = ?4 AND chunk_index = ?5",
params![
new_nonce.as_slice(),
new_tag.as_slice(),
new_ct,
nid,
chunk_index,
],
)?;
}
}
tx.commit()?;
Ok(())
}
/// Löst einen hierarchischen Pfad innerhalb eines bestimmten Vaults auf.
pub fn resolve_path_in_vault(
&self,
@@ -1824,7 +1944,12 @@ 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))
Ok(verify_metadata_mac(
&mac_key,
gen,
&canonical,
&expected_mac,
))
}
/// Führt ein Upgrade des Containerformats auf Format V3 durch (Format V3 / K-01 & K-02).
@@ -1852,9 +1977,8 @@ impl Database {
// K-02: 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",
)?;
let mut chunk_stmt =
conn.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)?;