Parent: #108
Problem
crates/identity/src/lib.rs:75-78:
#[derive(Clone)]
pub struct Identity {
secret_key: SecretKey,
}
crates/crypto/src/lib.rs:54-70:
pub struct ChannelKey(pub(crate) [u8; 32]);
Neither type implements Drop to zeroize its bytes. Rust's default drop leaves the secret material in the freed allocation, where it can persist in RAM (and potentially swap) until overwritten. For long-lived processes like the relay or worker nodes, this is a real forensic concern.
Fix
-
Add zeroize = { version = "1", features = ["derive"] } to the workspace dependencies.
-
In willow-crypto:
use zeroize::{Zeroize, ZeroizeOnDrop};
#[derive(Clone, ZeroizeOnDrop)]
pub struct ChannelKey(#[zeroize] pub(crate) [u8; 32]);
-
In willow-identity:
#[derive(Clone, ZeroizeOnDrop)]
pub struct Identity {
#[zeroize(skip)] // SecretKey already impls zeroize itself (check iroh-base)
secret_key: SecretKey,
}
If iroh-base::SecretKey does not already zeroize, wrap the bytes in a zeroizing newtype and convert on the boundary.
-
Also zeroize any temporary Vec<u8> returned from Identity::to_bytes() before drop — consider a callback-based API:
pub fn with_secret_bytes<F, R>(&self, f: F) -> R
where
F: FnOnce(&[u8]) -> R,
{
let mut bytes = self.secret_key.to_bytes().to_vec();
let r = f(&bytes);
bytes.zeroize();
r
}
Test
Not really testable in safe Rust (we can't introspect freed memory). Worth adding a doc-test / compile-pass test that both types implement ZeroizeOnDrop:
fn _assert_zeroize_on_drop() {
fn assert<T: zeroize::ZeroizeOnDrop>() {}
assert::<ChannelKey>();
assert::<Identity>();
}
Out of scope
- Zeroizing transient serialized forms in the transport crate.
- Preventing the OS from swapping secret memory (mlock). Separate issue if needed.
Parent: #108
Problem
crates/identity/src/lib.rs:75-78:crates/crypto/src/lib.rs:54-70:Neither type implements
Dropto zeroize its bytes. Rust's default drop leaves the secret material in the freed allocation, where it can persist in RAM (and potentially swap) until overwritten. For long-lived processes like the relay or worker nodes, this is a real forensic concern.Fix
Add
zeroize = { version = "1", features = ["derive"] }to the workspace dependencies.In
willow-crypto:In
willow-identity:If
iroh-base::SecretKeydoes not already zeroize, wrap the bytes in a zeroizing newtype and convert on the boundary.Also zeroize any temporary
Vec<u8>returned fromIdentity::to_bytes()before drop — consider a callback-based API:Test
Not really testable in safe Rust (we can't introspect freed memory). Worth adding a doc-test / compile-pass test that both types implement
ZeroizeOnDrop:Out of scope