From 84a4ec319a69682bb84efdcfd3ab376a310d4f6e Mon Sep 17 00:00:00 2001 From: Gard Kylling Date: Mon, 8 Jul 2024 09:54:05 +0200 Subject: [PATCH] Manually implement `Zeroize` Removes dependence on proc-macros when enabling the zeroize feature. --- Cargo.toml | 2 +- src/lib.rs | 92 +++++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 86 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3235400..41bb1ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -103,7 +103,7 @@ digest = { version = "0.10.1", features = [ "mac" ], optional = true } memmap2 = { version = "0.9", optional = true } rayon-core = { version = "1.12.1", optional = true } serde = { version = "1.0", default-features = false, features = ["derive"], optional = true } -zeroize = { version = "1", default-features = false, features = ["zeroize_derive"], optional = true } +zeroize = { version = "1", default-features = false, optional = true } [dev-dependencies] hmac = "0.12.0" diff --git a/src/lib.rs b/src/lib.rs index d64e18f..066b925 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -138,6 +138,8 @@ use arrayvec::{ArrayString, ArrayVec}; use core::cmp; use core::fmt; use platform::{Platform, MAX_SIMD_DEGREE, MAX_SIMD_DEGREE_OR_2}; +#[cfg(feature = "zeroize")] +use zeroize::Zeroize; /// The number of bytes in a [`Hash`](struct.Hash.html), 32. pub const OUT_LEN: usize = 32; @@ -216,7 +218,6 @@ fn counter_high(counter: u64) -> u32 { /// [`from_hex`]: #method.from_hex /// [`Display`]: https://doc.rust-lang.org/std/fmt/trait.Display.html /// [`FromStr`]: https://doc.rust-lang.org/std/str/trait.FromStr.html -#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize))] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[derive(Clone, Copy, Hash)] pub struct Hash([u8; OUT_LEN]); @@ -305,6 +306,15 @@ impl core::str::FromStr for Hash { } } +#[cfg(feature = "zeroize")] +impl Zeroize for Hash { + fn zeroize(&mut self) { + // Destructuring to trigger compile error as a reminder to update this impl. + let Self(bytes) = self; + bytes.zeroize(); + } +} + // A proper implementation of constant time equality is tricky, and we get it from the // constant_time_eq crate instead of rolling our own. However, that crate isn't compatible with // Miri, so we roll our own just for that. @@ -416,7 +426,6 @@ impl std::error::Error for HexError {} // Each chunk or parent node can produce either a 32-byte chaining value or, by // setting the ROOT flag, any number of final output bytes. The Output struct // captures the state just prior to choosing between those two possibilities. -#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize))] #[derive(Clone)] struct Output { input_chaining_value: CVWords, @@ -424,7 +433,6 @@ struct Output { block_len: u8, counter: u64, flags: u8, - #[cfg_attr(feature = "zeroize", zeroize(skip))] platform: Platform, } @@ -460,8 +468,28 @@ impl Output { } } +#[cfg(feature = "zeroize")] +impl Zeroize for Output { + fn zeroize(&mut self) { + // Destructuring to trigger compile error as a reminder to update this impl. + let Self { + input_chaining_value, + block, + block_len, + counter, + flags, + platform: _, + } = self; + + input_chaining_value.zeroize(); + block.zeroize(); + block_len.zeroize(); + counter.zeroize(); + flags.zeroize(); + } +} + #[derive(Clone)] -#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize))] struct ChunkState { cv: CVWords, chunk_counter: u64, @@ -469,7 +497,6 @@ struct ChunkState { buf_len: u8, blocks_compressed: u8, flags: u8, - #[cfg_attr(feature = "zeroize", zeroize(skip))] platform: Platform, } @@ -572,6 +599,29 @@ impl fmt::Debug for ChunkState { } } +#[cfg(feature = "zeroize")] +impl Zeroize for ChunkState { + fn zeroize(&mut self) { + // Destructuring to trigger compile error as a reminder to update this impl. + let Self { + cv, + chunk_counter, + buf, + buf_len, + blocks_compressed, + flags, + platform: _, + } = self; + + cv.zeroize(); + chunk_counter.zeroize(); + buf.zeroize(); + buf_len.zeroize(); + blocks_compressed.zeroize(); + flags.zeroize(); + } +} + // IMPLEMENTATION NOTE // =================== // The recursive function compress_subtree_wide(), implemented below, is the @@ -985,7 +1035,6 @@ fn parent_node_output( /// # } /// ``` #[derive(Clone)] -#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize))] pub struct Hasher { key: CVWords, chunk_state: ChunkState, @@ -1532,6 +1581,22 @@ impl std::io::Write for Hasher { } } +#[cfg(feature = "zeroize")] +impl Zeroize for Hasher { + fn zeroize(&mut self) { + // Destructuring to trigger compile error as a reminder to update this impl. + let Self { + key, + chunk_state, + cv_stack, + } = self; + + key.zeroize(); + chunk_state.zeroize(); + cv_stack.zeroize(); + } +} + /// An incremental reader for extended output, returned by /// [`Hasher::finalize_xof`](struct.Hasher.html#method.finalize_xof). /// @@ -1555,7 +1620,6 @@ impl std::io::Write for Hasher { /// from an unknown position in the output stream to recover its block index. Callers with strong /// secret keys aren't affected in practice, but secret offsets are a [design /// smell](https://en.wikipedia.org/wiki/Design_smell) in any case. -#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize))] #[derive(Clone)] pub struct OutputReader { inner: Output, @@ -1667,3 +1731,17 @@ impl std::io::Seek for OutputReader { Ok(self.position()) } } + +#[cfg(feature = "zeroize")] +impl Zeroize for OutputReader { + fn zeroize(&mut self) { + // Destructuring to trigger compile error as a reminder to update this impl. + let Self { + inner, + position_within_block, + } = self; + + inner.zeroize(); + position_within_block.zeroize(); + } +}