diff --git a/crates/algorithms/chacha20poly1305/Cargo.toml b/crates/algorithms/chacha20poly1305/Cargo.toml index 3b2091d69b..780a09146b 100644 --- a/crates/algorithms/chacha20poly1305/Cargo.toml +++ b/crates/algorithms/chacha20poly1305/Cargo.toml @@ -22,8 +22,11 @@ libcrux-secrets.workspace = true [dev-dependencies] rand_core = { version = "0.10" } rand = { version = "0.10", features = ["sys_rng"] } +zeroize = { version = "1.8", default-features = false } libcrux-kats = { workspace = true, features = ["chacha20poly1305"] } hex = "0.4.3" [features] check-secret-independence = ["libcrux-secrets/check-secret-independence"] +# Implement zeroize::Zeroize for the Key type +zeroize = ["libcrux-traits/zeroize", "libcrux-secrets/zeroize"] diff --git a/crates/algorithms/chacha20poly1305/tests/zeroize.rs b/crates/algorithms/chacha20poly1305/tests/zeroize.rs new file mode 100644 index 0000000000..b89877db75 --- /dev/null +++ b/crates/algorithms/chacha20poly1305/tests/zeroize.rs @@ -0,0 +1,15 @@ +#![cfg(feature = "zeroize")] + +use libcrux_chacha20poly1305::{Key, KEY_LEN}; +use libcrux_secrets::{Classify, DeclassifyRef}; +use zeroize::Zeroize; + +#[test] +fn key_zeroize_clears_bytes() { + let mut key = Key::from([0xAAu8; KEY_LEN].classify()); + assert_eq!(key.as_ref().declassify_ref(), &[0xAAu8; KEY_LEN]); + + key.zeroize(); + + assert_eq!(key.as_ref().declassify_ref(), &[0u8; KEY_LEN]); +} diff --git a/crates/utils/secrets/Cargo.toml b/crates/utils/secrets/Cargo.toml index 3f2a356ce2..4d4addd319 100644 --- a/crates/utils/secrets/Cargo.toml +++ b/crates/utils/secrets/Cargo.toml @@ -13,12 +13,15 @@ exclude = ["/proofs"] [dependencies] hax-lib.workspace = true +zeroize = { version = "1.8", default-features = false, optional = true } [target.'cfg(valgrind_ct_test)'.dependencies] crabgrind.workspace = true [features] check-secret-independence = [] +# Implement zeroize::Zeroize for Secret +zeroize = ["dep:zeroize"] [dev-dependencies] criterion = "0.8.0" diff --git a/crates/utils/secrets/src/int/classify_secret.rs b/crates/utils/secrets/src/int/classify_secret.rs index 852bb71e42..36170d4a9b 100644 --- a/crates/utils/secrets/src/int/classify_secret.rs +++ b/crates/utils/secrets/src/int/classify_secret.rs @@ -22,6 +22,13 @@ impl Clone for Secret { } } +#[cfg(feature = "zeroize")] +impl zeroize::Zeroize for Secret { + fn zeroize(&mut self) { + self.0.zeroize(); + } +} + // Any scalar type can be classified impl From for Secret { fn from(x: T) -> Secret { diff --git a/libcrux-ml-dsa/Cargo.toml b/libcrux-ml-dsa/Cargo.toml index 7fa8c51924..fbf35f567f 100644 --- a/libcrux-ml-dsa/Cargo.toml +++ b/libcrux-ml-dsa/Cargo.toml @@ -33,6 +33,7 @@ libcrux-macros.workspace = true libcrux-secrets.workspace = true hax-lib.workspace = true tls_codec = { workspace = true, optional = true } +zeroize = { version = "1.8", default-features = false, optional = true } [dev-dependencies] rand = { version = "0.10" } @@ -68,6 +69,9 @@ std = ["tls_codec?/std"] # Serialization & Deserialization using tls_codec codec = ["dep:tls_codec"] +# Zeroize secret key material on drop +zeroize = ["dep:zeroize"] + [[bench]] name = "manual44" harness = false diff --git a/libcrux-ml-dsa/src/types.rs b/libcrux-ml-dsa/src/types.rs index 62ca409a56..2b6b703e01 100644 --- a/libcrux-ml-dsa/src/types.rs +++ b/libcrux-ml-dsa/src/types.rs @@ -166,3 +166,36 @@ mod codec { impl_tls_codec_for_generic_struct!(MLDSAVerificationKey); impl_tls_codec_for_generic_struct!(MLDSASignature); } + +#[cfg(all(feature = "zeroize", not(hax)))] +mod zeroize_impls { + use super::*; + use zeroize::{Zeroize, ZeroizeOnDrop}; + + impl Zeroize for MLDSASigningKey { + fn zeroize(&mut self) { + self.value.zeroize(); + } + } + + impl Drop for MLDSASigningKey { + fn drop(&mut self) { + self.zeroize(); + } + } + + impl ZeroizeOnDrop for MLDSASigningKey {} + + impl Zeroize + for MLDSAKeyPair + { + fn zeroize(&mut self) { + self.signing_key.zeroize(); + } + } + + impl ZeroizeOnDrop + for MLDSAKeyPair + { + } +} diff --git a/libcrux-ml-dsa/tests/zeroize.rs b/libcrux-ml-dsa/tests/zeroize.rs new file mode 100644 index 0000000000..ae50057b2d --- /dev/null +++ b/libcrux-ml-dsa/tests/zeroize.rs @@ -0,0 +1,32 @@ +#![cfg(feature = "zeroize")] + +use libcrux_ml_dsa::{MLDSAKeyPair, MLDSASigningKey, MLDSAVerificationKey}; +use zeroize::Zeroize; + +const SK_SIZE: usize = 64; +const VK_SIZE: usize = 32; + +#[test] +fn signing_key_zeroize_clears_value() { + let mut key = MLDSASigningKey::::new([0xAA; SK_SIZE]); + assert_eq!(key.as_slice(), &[0xAA; SK_SIZE]); + + key.zeroize(); + + assert_eq!(key.as_slice(), &[0u8; SK_SIZE]); +} + +#[test] +fn keypair_zeroize_clears_signing_key_only() { + let mut keypair = MLDSAKeyPair:: { + signing_key: MLDSASigningKey::::new([0xAA; SK_SIZE]), + verification_key: MLDSAVerificationKey::::new([0xBB; VK_SIZE]), + }; + assert_eq!(keypair.signing_key.as_slice(), &[0xAA; SK_SIZE]); + assert_eq!(keypair.verification_key.as_slice(), &[0xBB; VK_SIZE]); + + keypair.zeroize(); + + assert_eq!(keypair.signing_key.as_slice(), &[0u8; SK_SIZE]); + assert_eq!(keypair.verification_key.as_slice(), &[0xBB; VK_SIZE]); +} diff --git a/traits/Cargo.toml b/traits/Cargo.toml index 3ae0a8c6b2..188b385ded 100644 --- a/traits/Cargo.toml +++ b/traits/Cargo.toml @@ -19,7 +19,10 @@ check-secret-independence = ["libcrux-secrets/check-secret-independence"] generic-tests = [] # whether or not the alloc crate is used alloc = [] +# Implement zeroize::Zeroize for typed key wrappers +zeroize = ["dep:zeroize", "libcrux-secrets/zeroize"] [dependencies] rand = { version = "0.10", default-features = false } libcrux-secrets.workspace = true +zeroize = { version = "1.8", default-features = false, optional = true } diff --git a/traits/src/aead/typed_owned.rs b/traits/src/aead/typed_owned.rs index cb33321d8c..4bedbe4d11 100644 --- a/traits/src/aead/typed_owned.rs +++ b/traits/src/aead/typed_owned.rs @@ -223,3 +223,13 @@ impl AsMut for Nonce { &mut self.0 } } + +#[cfg(feature = "zeroize")] +impl zeroize::Zeroize for Key +where + Algo::Key: zeroize::Zeroize, +{ + fn zeroize(&mut self) { + self.0.zeroize(); + } +}