diff --git a/Cargo.toml b/Cargo.toml index 4301f8f..b73c0c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ bouncycastle-utils = { path = "./crypto/utils", version = "0.1.1" } # *** External Dependencies *** criterion = "0.8.2" # only for benchmarking, not used in lib or cli build +zeroize = { version = "1.9", features = ["zeroize_derive"] } [profile.release] lto = "thin" diff --git a/crypto/core/Cargo.toml b/crypto/core/Cargo.toml index 2b9c969..c259452 100644 --- a/crypto/core/Cargo.toml +++ b/crypto/core/Cargo.toml @@ -5,6 +5,7 @@ edition.workspace = true [dependencies] bouncycastle-utils.workspace = true +zeroize.workspace = true [dev-dependencies] bouncycastle-rng.workspace = true \ No newline at end of file diff --git a/crypto/core/src/key_material.rs b/crypto/core/src/key_material.rs index 48f903e..f1b1c54 100644 --- a/crypto/core/src/key_material.rs +++ b/crypto/core/src/key_material.rs @@ -44,6 +44,8 @@ use bouncycastle_utils::{ct, min}; use core::cmp::{Ordering, PartialOrd}; use core::fmt; +use zeroize::{DefaultIsZeroes, Zeroize, ZeroizeOnDrop}; + /// Sometimes you just need a zero-length dummy key. pub type KeyMaterial0 = KeyMaterial<0>; @@ -146,8 +148,6 @@ pub trait KeyMaterialTrait { fn is_full_entropy(&self) -> bool; - fn zeroize(&mut self); - /// Is simply an alias to [KeyMaterialTrait::set_key_len], however, this does not require [KeyMaterialTrait::allow_hazardous_operations] /// since truncation is a safe operation. /// If truncating below the current security strength, the security strength will be lowered accordingly. @@ -172,7 +172,7 @@ pub trait KeyMaterialTrait { /// A wrapper for holding bytes-like key material (symmetric keys or seeds) which aims to apply a /// strict typing system to prevent many kinds of mis-use mistakes. /// The capacity of the internal buffer can be set at compile-time via the param. -#[derive(Clone)] +#[derive(Clone, Zeroize, ZeroizeOnDrop)] pub struct KeyMaterial { buf: [u8; KEY_LEN], key_len: usize, @@ -183,8 +183,9 @@ pub struct KeyMaterial { impl Secret for KeyMaterial {} -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub enum KeyType { + #[default] /// The KeyMaterial is zeroized and MUST NOT be used for any cryptographic operation in this state. Zeroized, @@ -204,6 +205,8 @@ pub enum KeyType { SymmetricCipherKey, } +impl DefaultIsZeroes for KeyType {} + impl Default for KeyMaterial { /// Create a new empty (zeroized) instance. fn default() -> Self { @@ -539,12 +542,6 @@ impl KeyMaterialTrait for KeyMaterial { } } - fn zeroize(&mut self) { - self.buf.fill(0u8); - self.key_len = 0; - self.key_type = KeyType::Zeroized; - } - fn truncate(&mut self, new_len: usize) -> Result<(), KeyMaterialError> { if new_len > self.key_len { return Err(KeyMaterialError::InvalidLength); @@ -647,10 +644,3 @@ impl fmt::Debug for KeyMaterial { ) } } - -/// Zeroize the key material on drop. -impl Drop for KeyMaterial { - fn drop(&mut self) { - self.zeroize() - } -} diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index 089e282..44e3fa4 100644 --- a/crypto/core/src/traits.rs +++ b/crypto/core/src/traits.rs @@ -4,6 +4,7 @@ use crate::errors::{HashError, KDFError, KEMError, MACError, RNGError, Signature use crate::key_material::KeyMaterialTrait; use core::fmt::{Debug, Display}; use core::marker::Sized; +use zeroize::{DefaultIsZeroes, ZeroizeOnDrop}; // Imports needed for docs #[allow(unused_imports)] @@ -346,8 +347,9 @@ pub trait MAC: Sized { fn max_security_strength(&self) -> SecurityStrength; } -#[derive(Eq, PartialEq, PartialOrd, Clone, Debug)] +#[derive(Eq, PartialEq, PartialOrd, Clone, Copy, Debug, Default)] pub enum SecurityStrength { + #[default] None, _112bit, _128bit, @@ -387,6 +389,8 @@ impl SecurityStrength { } } +impl DefaultIsZeroes for SecurityStrength {} + /// An interface for random number generation. /// This interface is meant to be simpler and more ergonomic than the interfaces provided by the /// `rng` crate, but that one should @@ -418,10 +422,7 @@ pub trait RNG: Default { /// A trait that forces an object to implement a zeroizing Drop() as well as Debug and Display that /// will not log the sensitive contents, even in error or crash-dump scenarios. -#[allow(drop_bounds)] // Since rust auto-implements Drop, there's a lint that explicitly bounding on Drop is useless. -// I disagree because I want to force things that are secrets to manually implement Drop that zeroizes the data. -// So I'm turning off this lint. -pub trait Secret: Drop + Debug + Display {} +pub trait Secret: ZeroizeOnDrop + Debug + Display {} /// Pre-Hashed Signature is an extension to [Signature] that adds functionality specific to signature /// primatives that can operate on a pre-hashed message instead of the full message. diff --git a/crypto/core/tests/key_material_tests.rs b/crypto/core/tests/key_material_tests.rs index 5e773fd..45dd23e 100644 --- a/crypto/core/tests/key_material_tests.rs +++ b/crypto/core/tests/key_material_tests.rs @@ -6,6 +6,7 @@ mod test_key_material { KeyMaterialTrait, KeyType, }; use bouncycastle_core::traits::SecurityStrength; + use zeroize::Zeroize; const DUMMY_KEY: &[u8; 64] = b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\ \x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F\ @@ -172,16 +173,32 @@ mod test_key_material { #[test] fn zeroize() { + let assert_construction = |key: &KeyMaterial<32>| { + assert_eq!(key.key_len(), 32); + assert_eq!(key.key_type(), KeyType::BytesLowEntropy); + assert_eq!(key.security_strength(), SecurityStrength::None); + }; + + let assert_zeroization = |key: &mut KeyMaterial<32>| { + let key_len = key.key_len(); + assert_eq!(key_len, 0); + assert_eq!(key.key_type(), KeyType::Zeroized); + + key.allow_hazardous_operations(); + let mut buf = vec![0u8; key_len]; + buf.copy_from_slice(key.ref_to_bytes()); + assert!(buf.iter().all(|&b| b == 0)); + }; + let mut key = KeyMaterial256::from_bytes(&DUMMY_KEY[..32]).unwrap(); + assert_construction(&key); key.zeroize(); - let key_len = key.key_len(); - assert_eq!(key_len, 0); - assert_eq!(key.key_type(), KeyType::Zeroized); + assert_zeroization(&mut key); - key.allow_hazardous_operations(); - let mut buf = vec![0u8; key_len]; - buf.copy_from_slice(key.ref_to_bytes()); - assert!(buf.iter().all(|&b| b == 0)); + let mut key = KeyMaterial256::from_bytes(&DUMMY_KEY[..32]).unwrap(); + assert_construction(&key); + unsafe { core::ptr::drop_in_place(&mut key) }; + assert_zeroization(&mut key); } #[test] diff --git a/crypto/mldsa-lowmemory/Cargo.toml b/crypto/mldsa-lowmemory/Cargo.toml index 83ccce7..50d7906 100644 --- a/crypto/mldsa-lowmemory/Cargo.toml +++ b/crypto/mldsa-lowmemory/Cargo.toml @@ -9,6 +9,7 @@ bouncycastle-sha2.workspace = true bouncycastle-sha3.workspace = true bouncycastle-rng.workspace = true bouncycastle-utils.workspace = true +zeroize.workspace = true [dev-dependencies] bouncycastle-core-test-framework.workspace = true diff --git a/crypto/mldsa-lowmemory/src/mldsa_keys.rs b/crypto/mldsa-lowmemory/src/mldsa_keys.rs index b805d89..1b85e95 100644 --- a/crypto/mldsa-lowmemory/src/mldsa_keys.rs +++ b/crypto/mldsa-lowmemory/src/mldsa_keys.rs @@ -28,6 +28,8 @@ use bouncycastle_core::traits::{ use core::fmt; use core::fmt::{Debug, Display, Formatter}; +use zeroize::ZeroizeOnDrop; + // imports just for docs #[allow(unused_imports)] use crate::mldsa::MLDSATrait; @@ -294,7 +296,7 @@ pub trait MLDSAPrivateKeyTrait< } /// Internal structure for holding a seed-based private key for ML-DSA. -#[derive(Clone, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq, ZeroizeOnDrop)] pub struct MLDSASeedPrivateKey< const LAMBDA: i32, const GAMMA2: i32, @@ -314,41 +316,6 @@ pub struct MLDSASeedPrivateKey< K: [u8; 32], } -impl< - const LAMBDA: i32, - const GAMMA2: i32, - const k: usize, - const l: usize, - const eta: usize, - const S1_PACKED_LEN: usize, - const S2_PACKED_LEN: usize, - const T1_PACKED_LEN: usize, - const SK_LEN: usize, - const PK_LEN: usize, - const FULL_SK_LEN: usize, -> Drop - for MLDSASeedPrivateKey< - LAMBDA, - GAMMA2, - k, - l, - eta, - S1_PACKED_LEN, - S2_PACKED_LEN, - T1_PACKED_LEN, - PK_LEN, - SK_LEN, - FULL_SK_LEN, - > -{ - fn drop(&mut self) { - // seed is a KeyMaterialSized which will zeroize itself - self.rho.fill(0u8); - self.rho_prime.fill(0u8); - self.K.fill(0u8); - } -} - impl< const LAMBDA: i32, const GAMMA2: i32, diff --git a/crypto/mldsa-lowmemory/src/polynomial.rs b/crypto/mldsa-lowmemory/src/polynomial.rs index dc2438c..6d134fe 100644 --- a/crypto/mldsa-lowmemory/src/polynomial.rs +++ b/crypto/mldsa-lowmemory/src/polynomial.rs @@ -7,11 +7,13 @@ use core::fmt; use core::fmt::{Debug, Display, Formatter}; use core::ops::{Index, IndexMut}; +use zeroize::ZeroizeOnDrop; + /// A polynomial over the ML-DSA ring. /// Dev note: this doesn't strictly need to be pub ... ie there's no good reason for a caller to use this class directly, /// but in order to test the Debug and Display traits, you need STD, so those can't be tested from inline tests in this file /// and the real unit tests are in a different crate, so here we are. -#[derive(Clone)] +#[derive(Clone, ZeroizeOnDrop)] pub struct Polynomial { pub(crate) coeffs: [i32; N], } @@ -248,12 +250,6 @@ impl Polynomial { impl Secret for Polynomial {} -impl Drop for Polynomial { - fn drop(&mut self) { - self.coeffs.fill(0i32); - } -} - impl Debug for Polynomial { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "Polynomial (data masked)") diff --git a/crypto/mldsa/Cargo.toml b/crypto/mldsa/Cargo.toml index cddc8c6..291e3f9 100644 --- a/crypto/mldsa/Cargo.toml +++ b/crypto/mldsa/Cargo.toml @@ -9,6 +9,7 @@ bouncycastle-sha2.workspace = true bouncycastle-sha3.workspace = true bouncycastle-rng.workspace = true bouncycastle-utils.workspace = true +zeroize.workspace = true [dev-dependencies] bouncycastle-core-test-framework.workspace = true diff --git a/crypto/mldsa/src/matrix.rs b/crypto/mldsa/src/matrix.rs index c0ef5d2..8699abb 100644 --- a/crypto/mldsa/src/matrix.rs +++ b/crypto/mldsa/src/matrix.rs @@ -6,9 +6,10 @@ use crate::mldsa::H; use crate::polynomial::Polynomial; use bouncycastle_core::traits::XOF; use core::ops::{Index, IndexMut}; +use zeroize::ZeroizeOnDrop; /// A matrix over the ML-DSA ring. -#[derive(Clone)] +#[derive(Clone, ZeroizeOnDrop)] pub struct Matrix(/*pub(crate)*/ [[Polynomial; l]; k]); /// Convenience function to avoid ".0" all over the place. @@ -62,7 +63,7 @@ impl Matrix { // Technically all matrices and some vectors are only part of the public key and might not need to be zeroized, // but I'll leave it zeroizing for now and leave this as a potential future optimization. -#[derive(Clone)] +#[derive(Clone, ZeroizeOnDrop)] pub(crate) struct Vector { pub(crate) vec: [Polynomial; k], } diff --git a/crypto/mldsa/src/mldsa_keys.rs b/crypto/mldsa/src/mldsa_keys.rs index c1643ba..93358eb 100644 --- a/crypto/mldsa/src/mldsa_keys.rs +++ b/crypto/mldsa/src/mldsa_keys.rs @@ -14,6 +14,7 @@ use bouncycastle_core::key_material::KeyMaterial; use bouncycastle_core::traits::{Secret, SignaturePrivateKey, SignaturePublicKey, XOF}; use core::fmt; use core::fmt::{Debug, Display, Formatter}; +use zeroize::ZeroizeOnDrop; // imports just for docs #[allow(unused_imports)] @@ -403,7 +404,7 @@ impl< } /// An ML-DSA private key. -#[derive(Clone)] +#[derive(Clone, ZeroizeOnDrop)] pub struct MLDSAPrivateKey< const k: usize, const l: usize, @@ -805,20 +806,11 @@ impl - Drop for MLDSAPrivateKey -{ - fn drop(&mut self) { - self.K.fill(0u8); - // s1, s2, t0, seed have their own zeroizing drop - } -} /// A fully expanded ML-DSA private key that includes the intermediate values needed for performing /// multiple sign operations with the same private key, which causes the private ey struct to take up /// more memory, but results in more efficient repeated sign() operations. -#[derive(Clone)] +#[derive(Clone, ZeroizeOnDrop)] pub struct MLDSAPrivateKeyExpanded< const k: usize, const l: usize, @@ -876,22 +868,6 @@ impl< { } -impl< - const k: usize, - const l: usize, - const eta: usize, - PK: MLDSAPublicKeyInternalTrait, - SK: MLDSAPrivateKeyTrait - + MLDSAPrivateKeyInternalTrait, - const SK_LEN: usize, - const PK_LEN: usize, -> Drop for MLDSAPrivateKeyExpanded -{ - fn drop(&mut self) { - // Nothing to do since self.sk already impls zeroizing Drop - } -} - impl< const k: usize, const l: usize, diff --git a/crypto/mldsa/src/polynomial.rs b/crypto/mldsa/src/polynomial.rs index b5f0999..b19951a 100644 --- a/crypto/mldsa/src/polynomial.rs +++ b/crypto/mldsa/src/polynomial.rs @@ -9,11 +9,13 @@ use core::fmt; use core::fmt::{Debug, Display, Formatter}; use core::ops::{Index, IndexMut}; +use zeroize::ZeroizeOnDrop; + /// A polynomial over the ML-DSA ring. /// Dev note: this doesn't strictly need to be pub ... ie there's no good reason for a caller to use this class directly, /// but in order to test the Debug and Display traits, you need STD, so those can't be tested from inline tests in this file /// and the real unit tests are in a different crate, so here we are. -#[derive(Clone)] +#[derive(Clone, ZeroizeOnDrop)] pub struct Polynomial { pub(crate) coeffs: [i32; N], } @@ -95,7 +97,6 @@ impl Polynomial { // } // but since BOUND is a constant here, we'll just do a debug_assert to make sure the value is what we expect. debug_assert!(BOUND <= (q - 1) / 8); - let mut t: i32; for x in self.coeffs.iter() { t = *x >> 31; @@ -237,12 +238,6 @@ impl Polynomial { impl Secret for Polynomial {} -impl Drop for Polynomial { - fn drop(&mut self) { - self.coeffs.fill(0i32); - } -} - impl Debug for Polynomial { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "Polynomial (data masked)") diff --git a/crypto/mlkem-lowmemory/Cargo.toml b/crypto/mlkem-lowmemory/Cargo.toml index 8ccb067..d172442 100644 --- a/crypto/mlkem-lowmemory/Cargo.toml +++ b/crypto/mlkem-lowmemory/Cargo.toml @@ -8,6 +8,7 @@ bouncycastle-core.workspace = true bouncycastle-sha3.workspace = true bouncycastle-rng.workspace = true bouncycastle-utils.workspace = true +zeroize.workspace = true [dev-dependencies] bouncycastle-core-test-framework.workspace = true diff --git a/crypto/mlkem-lowmemory/src/mlkem_keys.rs b/crypto/mlkem-lowmemory/src/mlkem_keys.rs index b9e58f3..5f6f4d1 100644 --- a/crypto/mlkem-lowmemory/src/mlkem_keys.rs +++ b/crypto/mlkem-lowmemory/src/mlkem_keys.rs @@ -24,6 +24,8 @@ use bouncycastle_sha3::SHA3_256; use core::fmt; use core::fmt::{Debug, Display, Formatter}; +use zeroize::ZeroizeOnDrop; + // imports just for docs /* Pub Types */ @@ -236,7 +238,7 @@ impl Display } /// An ML-KEM private key. -#[derive(Clone)] +#[derive(Clone, ZeroizeOnDrop)] pub struct MLKEMSeedPrivateKey< const k: usize, const eta1: i16, @@ -659,22 +661,3 @@ impl< write!(f, "MLKEMSeedPrivateKey {{ alg: {}, pub_key_hash: {:x?} }}", alg, &pk_hash,) } } - -/// Zeroizing drop -impl< - const k: usize, - const eta1: i16, - const LAMBDA: i16, - const SK_LEN: usize, - const FULL_SK_LEN: usize, - const PK_LEN: usize, - const T_PACKED_LEN: usize, -> Drop for MLKEMSeedPrivateKey -{ - fn drop(&mut self) { - self.rho.fill(0u8); - self.sigma.fill(0u8); - self.z.fill(0u8); - self.seed_d.fill(0u8); - } -} diff --git a/crypto/mlkem-lowmemory/src/polynomial.rs b/crypto/mlkem-lowmemory/src/polynomial.rs index bec4ee4..6b7c6aa 100644 --- a/crypto/mlkem-lowmemory/src/polynomial.rs +++ b/crypto/mlkem-lowmemory/src/polynomial.rs @@ -9,11 +9,13 @@ use core::fmt; use core::fmt::{Debug, Display, Formatter}; use core::ops::{Index, IndexMut}; +use zeroize::ZeroizeOnDrop; + /// A polynomial over the ML-KEM ring. /// Dev note: this doesn't strictly need to be pub ... ie there's no good reason for a caller to use this class directly, /// but in order to test the Debug and Display traits, you need STD, so those can't be tested from inline tests in this file /// and the real unit tests are in a different crate, so here we are. -#[derive(Clone)] +#[derive(Clone, ZeroizeOnDrop)] pub struct Polynomial { pub(crate) coeffs: [i16; N], } @@ -320,12 +322,6 @@ impl Polynomial { impl Secret for Polynomial {} -impl Drop for Polynomial { - fn drop(&mut self) { - self.coeffs.fill(0i16); - } -} - impl Debug for Polynomial { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "Polynomial (data masked)") diff --git a/crypto/mlkem/Cargo.toml b/crypto/mlkem/Cargo.toml index 8235352..7824ec7 100644 --- a/crypto/mlkem/Cargo.toml +++ b/crypto/mlkem/Cargo.toml @@ -8,6 +8,7 @@ bouncycastle-core.workspace = true bouncycastle-sha3.workspace = true bouncycastle-rng.workspace = true bouncycastle-utils.workspace = true +zeroize.workspace = true [dev-dependencies] bouncycastle-core-test-framework.workspace = true diff --git a/crypto/mlkem/src/matrix.rs b/crypto/mlkem/src/matrix.rs index 100d6e0..2c08665 100644 --- a/crypto/mlkem/src/matrix.rs +++ b/crypto/mlkem/src/matrix.rs @@ -7,7 +7,9 @@ use crate::mlkem::{N, q}; use crate::polynomial; use crate::polynomial::Polynomial; -#[derive(Clone)] +use zeroize::ZeroizeOnDrop; + +#[derive(Clone, ZeroizeOnDrop)] /// A matrix over the ML-KEM ring. pub struct Matrix { /*pub(crate)*/ mat: [[Polynomial; l]; k], @@ -81,7 +83,7 @@ impl Matrix { // Technically all matrices and some vectors are only part of the public key and might not need to be zeroized, // but I'll leave it zeroizing for now and leave this as a potential future optimization. -#[derive(Clone)] +#[derive(Clone, ZeroizeOnDrop)] pub(crate) struct Vector { pub(crate) vec: [Polynomial; k], } @@ -308,4 +310,4 @@ impl Vector { u } -} +} \ No newline at end of file diff --git a/crypto/mlkem/src/mlkem_keys.rs b/crypto/mlkem/src/mlkem_keys.rs index 327878c..fff19eb 100644 --- a/crypto/mlkem/src/mlkem_keys.rs +++ b/crypto/mlkem/src/mlkem_keys.rs @@ -12,6 +12,8 @@ use bouncycastle_sha3::SHA3_256; use core::fmt; use core::fmt::{Debug, Display, Formatter}; +use zeroize::ZeroizeOnDrop; + // imports just for docs #[allow(unused_imports)] use crate::mlkem::MLKEMTrait; @@ -683,6 +685,15 @@ impl< } } +impl< + const k: usize, + PK: MLKEMPublicKeyInternalTrait, + const SK_LEN: usize, + const PK_LEN: usize, +> ZeroizeOnDrop for MLKEMPrivateKey +{ +} + /// Zeroizing drop impl< const k: usize, @@ -704,7 +715,7 @@ impl< /// A fully expanded ML-KEM private key that includes the intermediate values needed for performing /// multiple decaps operations with the same private key, which causes the private key struct to /// take up more memory, but results in more efficient repeated decaps() operations. -#[derive(Clone)] +#[derive(Clone, ZeroizeOnDrop)] pub struct MLKEMPrivateKeyExpanded< const k: usize, PK: MLKEMPublicKeyInternalTrait, @@ -796,20 +807,6 @@ impl< { } -impl< - const k: usize, - PK: MLKEMPublicKeyInternalTrait, - SK: MLKEMPrivateKeyTrait - + MLKEMPrivateKeyInternalTrait, - const SK_LEN: usize, - const PK_LEN: usize, -> Drop for MLKEMPrivateKeyExpanded -{ - fn drop(&mut self) { - // Nothing to do since self.sk already impls zeroizing Drop - } -} - impl< const k: usize, PK: MLKEMPublicKeyInternalTrait, diff --git a/crypto/mlkem/src/polynomial.rs b/crypto/mlkem/src/polynomial.rs index 951cc13..375c188 100644 --- a/crypto/mlkem/src/polynomial.rs +++ b/crypto/mlkem/src/polynomial.rs @@ -10,11 +10,13 @@ use crate::aux_functions::{ use crate::mlkem::{N, q}; use bouncycastle_core::traits::Secret; +use zeroize::ZeroizeOnDrop; + /// A polynomial over the ML-KEM ring. /// Dev note: this doesn't strictly need to be pub ... ie there's no good reason for a caller to use this class directly, /// but in order to test the Debug and Display traits, you need STD, so those can't be tested from inline tests in this file /// and the real unit tests are in a different crate, so here we are. -#[derive(Clone)] +#[derive(Clone, ZeroizeOnDrop)] pub struct Polynomial { pub(crate) coeffs: [i16; N], } @@ -328,12 +330,6 @@ pub(crate) fn base_mult_montgomery(a: &Polynomial, b: &Polynomial) -> Polynomial impl Secret for Polynomial {} -impl Drop for Polynomial { - fn drop(&mut self) { - self.coeffs.fill(0i16); - } -} - impl Debug for Polynomial { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "Polynomial (data masked)")