diff --git a/Cargo.lock b/Cargo.lock index bffaee7..4cd52f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -505,6 +505,7 @@ dependencies = [ "tempfile", "thiserror", "x25519-dalek", + "zeroize", ] [[package]] diff --git a/conversations/Cargo.toml b/conversations/Cargo.toml index b77d9f6..4ea9408 100644 --- a/conversations/Cargo.toml +++ b/conversations/Cargo.toml @@ -19,6 +19,7 @@ safer-ffi = "0.1.13" thiserror = "2.0.17" x25519-dalek = { version = "2.0.1", features = ["static_secrets", "reusable_secrets", "getrandom"] } storage = { path = "../storage" } +zeroize = { version = "1.8.2", features = ["derive"] } [dev-dependencies] tempfile = "3" diff --git a/conversations/src/storage/types.rs b/conversations/src/storage/types.rs index d4d48d9..8767324 100644 --- a/conversations/src/storage/types.rs +++ b/conversations/src/storage/types.rs @@ -1,10 +1,13 @@ //! Storage record types for serialization/deserialization. +use zeroize::{Zeroize, ZeroizeOnDrop}; + use crate::crypto::PrivateKey; use crate::identity::Identity; /// Record for storing identity (secret key). -#[derive(Debug)] +/// Implements ZeroizeOnDrop to securely clear secret key from memory. +#[derive(Debug, Zeroize, ZeroizeOnDrop)] pub struct IdentityRecord { /// The identity name. pub name: String, @@ -14,7 +17,42 @@ pub struct IdentityRecord { impl From for Identity { fn from(record: IdentityRecord) -> Self { + let name = record.name.clone(); let secret = PrivateKey::from(record.secret_key); - Identity::from_secret(record.name, secret) + Identity::from_secret(name, secret) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_identity_record_zeroize() { + let secret_key = [0xAB_u8; 32]; + let mut record = IdentityRecord { + name: "test".to_string(), + secret_key, + }; + + // Get a pointer to the secret key before zeroizing + let ptr = record.secret_key.as_ptr(); + + // Manually zeroize (simulates what ZeroizeOnDrop does) + record.zeroize(); + + // Verify the memory is zeroed + // SAFETY: ptr still points to valid memory within record + unsafe { + let slice = std::slice::from_raw_parts(ptr, 32); + assert!(slice.iter().all(|&b| b == 0), "secret_key should be zeroed"); + } + + // Also verify via the struct field + assert!( + record.secret_key.iter().all(|&b| b == 0), + "secret_key field should be zeroed" + ); + assert!(record.name.is_empty(), "name should be cleared"); } }