diff --git a/.github/workflows/xaes-256-gcm.yml b/.github/workflows/xaes-256-gcm.yml index 32e06dad..ae38c54a 100644 --- a/.github/workflows/xaes-256-gcm.yml +++ b/.github/workflows/xaes-256-gcm.yml @@ -69,7 +69,7 @@ jobs: - run: ${{ matrix.deps }} - run: cargo test --target ${{ matrix.target }} --lib --no-default-features - run: cargo test --target ${{ matrix.target }} --lib - #- run: cargo test --target ${{ matrix.target }} --lib --features zeroize + - run: cargo test --target ${{ matrix.target }} --lib --features zeroize - run: cargo test --target ${{ matrix.target }} --all-features --lib - run: cargo test --target ${{ matrix.target }} --all-features --release - run: cargo test --target ${{ matrix.target }} --all-features --doc diff --git a/Cargo.lock b/Cargo.lock index 874a89c6..5c65b573 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -495,6 +495,7 @@ dependencies = [ "aes-gcm", "cipher", "hex-literal", + "zeroize", ] [[package]] diff --git a/xaes-256-gcm/CHANGELOG.md b/xaes-256-gcm/CHANGELOG.md index 8297e1ee..1e5edefb 100644 --- a/xaes-256-gcm/CHANGELOG.md +++ b/xaes-256-gcm/CHANGELOG.md @@ -6,3 +6,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## 0.1.0 (TBD) - Initial release +- Add the optional `zeroize` feature by implementing `ZeroizeOnDrop` for + `Xaes256Gcm` to clear internal key material diff --git a/xaes-256-gcm/Cargo.toml b/xaes-256-gcm/Cargo.toml index d62fd139..cd9af917 100644 --- a/xaes-256-gcm/Cargo.toml +++ b/xaes-256-gcm/Cargo.toml @@ -20,7 +20,10 @@ aead = { version = "0.6", default-features = false } aes = "0.9" aes-gcm = { version = "0.11", default-features = false, features = ["aes"] } cipher = "0.5" + +# optional dependencies aead-stream = { version = "0.6", optional = true, default-features = false } +zeroize = { version = "1.9.0", optional = true, default-features = false } [dev-dependencies] aead = { version = "0.6", features = ["dev"], default-features = false } @@ -32,6 +35,7 @@ alloc = ["aead/alloc", "aead-stream?/alloc", "aes-gcm/alloc"] arrayvec = ["aead/arrayvec", "aes-gcm/arrayvec"] getrandom = ["aes-gcm/getrandom"] rand_core = ["aead/rand_core", "aes-gcm/rand_core"] +zeroize = ["dep:zeroize", "aes/zeroize", "aes-gcm/zeroize"] [lints] workspace = true diff --git a/xaes-256-gcm/src/lib.rs b/xaes-256-gcm/src/lib.rs index 7f071996..7c6f3702 100644 --- a/xaes-256-gcm/src/lib.rs +++ b/xaes-256-gcm/src/lib.rs @@ -118,8 +118,17 @@ impl AeadInOut for Xaes256Gcm { } let (n1, n) = nonce.split_ref::<>::Output>(); - let k = self.derive_key(n1); - Aes256Gcm::new(&k).encrypt_inout_detached(n, associated_data, buffer) + let mut k = Key::::default(); + self.derive_key(n1, &mut k); + let cipher = Aes256Gcm::new(&k); + + #[cfg(feature = "zeroize")] + { + use zeroize::Zeroize; + k.as_mut_slice().zeroize(); + } + + cipher.encrypt_inout_detached(n, associated_data, buffer) } fn decrypt_inout_detached( @@ -137,14 +146,23 @@ impl AeadInOut for Xaes256Gcm { } let (n1, n) = nonce.split_ref::<>::Output>(); - let k = self.derive_key(n1); - Aes256Gcm::new(&k).decrypt_inout_detached(n, associated_data, buffer, tag) + let mut k = Key::::default(); + self.derive_key(n1, &mut k); + let cipher = Aes256Gcm::new(&k); + + #[cfg(feature = "zeroize")] + { + use zeroize::Zeroize; + k.as_mut_slice().zeroize(); + } + + cipher.decrypt_inout_detached(n, associated_data, buffer, tag) } } impl Xaes256Gcm { // Implements steps 3 - 5 of the spec. - fn derive_key(&self, n1: &Nonce<>::Output>) -> Key { + fn derive_key(&self, n1: &Nonce<>::Output>, key: &mut Key) { // M1 = 0x00 || 0x01 || X || 0x00 || N[:12] let mut m1 = Block::default(); m1[..4].copy_from_slice(&[0, 1, b'X', 0]); @@ -158,7 +176,6 @@ impl Xaes256Gcm { // Kₘ = AES-256ₖ(M1 ⊕ K1) // Kₙ = AES-256ₖ(M2 ⊕ K1) // Kₓ = Kₘ || Kₙ = AES-256ₖ(M1 ⊕ K1) || AES-256ₖ(M2 ⊕ K1) - let mut key: Key = Array::default(); let (km, kn) = key.split_ref_mut::<>::Output>(); for i in 0..km.len() { km[i] = m1[i] ^ self.k1[i]; @@ -169,7 +186,6 @@ impl Xaes256Gcm { self.aes.encrypt_block(km); self.aes.encrypt_block(kn); - key } } @@ -178,3 +194,16 @@ impl fmt::Debug for Xaes256Gcm { f.debug_struct("Xaes256Gcm").finish_non_exhaustive() } } + +impl Drop for Xaes256Gcm { + fn drop(&mut self) { + #[cfg(feature = "zeroize")] + { + use zeroize::Zeroize; + self.k1.as_mut_slice().zeroize(); + } + } +} + +#[cfg(feature = "zeroize")] +impl zeroize::ZeroizeOnDrop for Xaes256Gcm where Aes256: zeroize::ZeroizeOnDrop {} diff --git a/xaes-256-gcm/tests/xaes256gcm.rs b/xaes-256-gcm/tests/xaes256gcm.rs index 62b85d7e..3c2dcfd3 100644 --- a/xaes-256-gcm/tests/xaes256gcm.rs +++ b/xaes-256-gcm/tests/xaes256gcm.rs @@ -32,3 +32,16 @@ const TEST_VECTORS: &[TestVector<[u8; 32], [u8; 24]>] = &[ ]; tests!(Xaes256Gcm, TEST_VECTORS); + +/// Compile-time assertion that the `zeroize` feature enables the +/// `ZeroizeOnDrop` implementation for `Xaes256Gcm`. +#[cfg(feature = "zeroize")] +#[test] +fn zeroize_on_drop() { + use zeroize::ZeroizeOnDrop; + + fn assert_zeroize_on_drop(_: T) {} + + let key = hex!("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F"); + assert_zeroize_on_drop(Xaes256Gcm::new(&key.into())); +}