diff --git a/changes/2011.fixed.md b/changes/2011.fixed.md new file mode 100644 index 000000000..489c0d11a --- /dev/null +++ b/changes/2011.fixed.md @@ -0,0 +1 @@ +Report the hash that chain links use for every exported audit entry. `audit.export` gave a format-v2 entry's `content_hash_hex` as BLAKE3 of its signing data, which for v2 is the signature wrapper of the entry hash, so an exported chain that had switched to v2 did not link to its entries or to its head in `audit.heads`. v2 entries now export their SHA-256 entry hash; v1 entries are unchanged. diff --git a/crates/astrid-core/src/kernel_api/audit_export.rs b/crates/astrid-core/src/kernel_api/audit_export.rs index 2f3feb49e..dc5fa8e67 100644 --- a/crates/astrid-core/src/kernel_api/audit_export.rs +++ b/crates/astrid-core/src/kernel_api/audit_export.rs @@ -77,13 +77,14 @@ pub struct AuditHeadsChain { /// [`AUDIT_OMITTED_TOTAL_UNKNOWN`]. A known total never decreases, so /// `omitted_total + count` counts every entry the chain has held. pub omitted_total: u64, - /// Hex BLAKE3 content hash of the head entry (what the next entry's - /// `previous_hash` links to), or 64 zeros for an empty chain. + /// Hex content hash of the head entry (what the next entry's + /// `previous_hash` links to), or 64 zeros for an empty chain. See + /// [`AuditExportEntry::content_hash_hex`] for how it is computed. pub head_hash_hex: String, /// Head entry id, if the chain has entries. pub head_id: Option, - /// Stored RFC 3339 timestamp of the head entry. Entry signatures cover - /// whole seconds only. + /// Stored RFC 3339 timestamp of the head entry. A format-v1 entry + /// signature covers whole seconds only. pub last_timestamp: Option, /// Latest prune receipt summary, if the chain was ever pruned. Not part /// of the signed bytes. @@ -247,18 +248,23 @@ pub struct AuditExportEntry { pub index: u64, /// Entry id (UUID). pub id: String, - /// Stored RFC 3339 timestamp. The signature covers whole seconds only. + /// Stored RFC 3339 timestamp. A format-v1 signature covers whole + /// seconds only. pub timestamp: String, /// Hex hash of the previous entry; zeros for the genesis entry. pub previous_hash_hex: String, - /// Hex `BLAKE3(signing_data)`, the value the next entry's - /// `previous_hash` links to. + /// Hex content hash, the value the next entry's `previous_hash` links + /// to: `BLAKE3(signing_data)` for a format-v1 entry, and for a format-v2 + /// entry (one whose stored form has a `v2` field) the SHA-256 entry hash + /// of its canonical body. pub content_hash_hex: String, /// Hex Ed25519 signature over `signing_data`. pub signature_hex: String, /// Hex Ed25519 public key embedded in the entry. pub public_key_hex: String, - /// Hex of the exact bytes that are signed and hashed. + /// Hex of the exact bytes the signature covers. For a format-v1 entry + /// these are also the bytes hashed into `content_hash_hex`; for a + /// format-v2 entry they are the signature wrapper of that hash. pub signing_data_hex: String, /// The entry as stored, including `previous_hash`, `runtime_key` and /// `signature`. diff --git a/crates/astrid-kernel/src/kernel_router/admin/audit_handlers.rs b/crates/astrid-kernel/src/kernel_router/admin/audit_handlers.rs index 51749b506..f352ed788 100644 --- a/crates/astrid-kernel/src/kernel_router/admin/audit_handlers.rs +++ b/crates/astrid-kernel/src/kernel_router/admin/audit_handlers.rs @@ -18,7 +18,6 @@ use astrid_core::kernel_api::{ AuditHeadsSnapshot, AuditHealth, AuditPruneResult, AuditStats, }; use astrid_core::{PrincipalId, SessionId, Timestamp}; -use astrid_crypto::ContentHash; use crate::Kernel; @@ -467,7 +466,7 @@ fn export_entry(index: u64, entry: &AuditEntry) -> Result