diff --git a/crates/gitlawb-core/src/encrypt.rs b/crates/gitlawb-core/src/encrypt.rs index a6d3dc2..d8659be 100644 --- a/crates/gitlawb-core/src/encrypt.rs +++ b/crates/gitlawb-core/src/encrypt.rs @@ -6,6 +6,7 @@ use crate::identity::Keypair; use anyhow::{Context, Result}; use ed25519_dalek::VerifyingKey; +use zeroize::Zeroizing; /// X25519 public key (Montgomery u) for an Ed25519 verifying key. fn x25519_public(vk: &VerifyingKey) -> Result<[u8; 32]> { @@ -18,14 +19,18 @@ fn x25519_public(vk: &VerifyingKey) -> Result<[u8; 32]> { } /// X25519 secret scalar for an Ed25519 seed (SHA-512 of seed, lower 32, clamped). -fn x25519_secret_from_seed(seed: &[u8; 32]) -> [u8; 32] { +/// Returns the scalar wrapped in `Zeroizing`, and scrubs the intermediate +/// SHA-512 digest, so no copy of this secret material lingers in freed memory. +fn x25519_secret_from_seed(seed: &[u8; 32]) -> Zeroizing<[u8; 32]> { use sha2::{Digest, Sha512}; - let h = Sha512::digest(seed); - let mut s = [0u8; 32]; + use zeroize::Zeroize; + let mut h = Sha512::digest(seed); + let mut s = Zeroizing::new([0u8; 32]); s.copy_from_slice(&h[..32]); s[0] &= 248; s[31] &= 127; s[31] |= 64; + h.as_mut_slice().zeroize(); s } @@ -123,7 +128,7 @@ pub fn open_blob(envelope: &[u8], keypair: &Keypair) -> Result> { .context("decode header")?; let body = &envelope[p + hlen..]; - let my_x = XSecret::from(x25519_secret_from_seed(&keypair.to_seed())); + let my_x = XSecret::from(*x25519_secret_from_seed(&keypair.to_seed())); // Identities are blinded: no entry says which recipient it belongs to, so // try each one. The ChaChaBox AEAD tag authenticates, so exactly the @@ -188,7 +193,7 @@ mod tests { let seed = kp.to_seed(); let xpub_from_public = x25519_public(&kp.verifying_key()).unwrap(); let xsec = x25519_secret_from_seed(&seed); - let xpub_from_secret = crypto_box::SecretKey::from(xsec).public_key().to_bytes(); + let xpub_from_secret = crypto_box::SecretKey::from(*xsec).public_key().to_bytes(); assert_eq!(xpub_from_public, xpub_from_secret); }