From 907ac2365572e4714c798d65a7d7c425f505baeb Mon Sep 17 00:00:00 2001 From: Elichai Turkel Date: Tue, 6 Jun 2023 18:06:17 +0300 Subject: [PATCH 1/3] Implement Zeroize on exported types --- Cargo.toml | 5 ++++- src/lib.rs | 11 +++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 8df138741..7c2e7a328 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -77,17 +77,20 @@ no_avx2 = [] no_avx512 = [] no_neon = [] +zeroize = ["zeroize_crate", "arrayvec/zeroize"] + [package.metadata.docs.rs] # Document Hasher::update_rayon on docs.rs. features = ["rayon"] [dependencies] arrayref = "0.3.5" -arrayvec = { version = "0.7.0", default-features = false } +arrayvec = { version = "0.7.4", default-features = false } constant_time_eq = "0.3.0" rayon = { version = "1.2.1", optional = true } cfg-if = "1.0.0" digest = { version = "0.10.1", features = [ "mac" ], optional = true } +zeroize_crate = { package = "zeroize", version = "1", default-features = false, features = ["zeroize_derive"], optional = true } [dev-dependencies] hex = "0.4.2" diff --git a/src/lib.rs b/src/lib.rs index ac61fb27b..e2a4d9c7a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -67,6 +67,10 @@ #![cfg_attr(not(feature = "std"), no_std)] +#[cfg(feature = "zeroize")] +extern crate zeroize_crate as zeroize; // Needed because `zeroize::Zeroize` assumes the crate is named `zeroize`. + + #[cfg(test)] mod test; @@ -197,6 +201,7 @@ 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))] #[derive(Clone, Copy, Hash)] pub struct Hash([u8; OUT_LEN]); @@ -371,6 +376,7 @@ 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, @@ -378,6 +384,7 @@ struct Output { block_len: u8, counter: u64, flags: u8, + #[cfg_attr(feature = "zeroize", zeroize(skip))] platform: Platform, } @@ -414,6 +421,7 @@ impl Output { } #[derive(Clone)] +#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize))] struct ChunkState { cv: CVWords, chunk_counter: u64, @@ -421,6 +429,7 @@ struct ChunkState { buf_len: u8, blocks_compressed: u8, flags: u8, + #[cfg_attr(feature = "zeroize", zeroize(skip))] platform: Platform, } @@ -942,6 +951,7 @@ fn parent_node_output( /// # } /// ``` #[derive(Clone)] +#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize))] pub struct Hasher { key: CVWords, chunk_state: ChunkState, @@ -1366,6 +1376,7 @@ 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, From 07524df45fcbe018b54575a62c94fde7465c9065 Mon Sep 17 00:00:00 2001 From: Elichai Turkel Date: Sun, 9 Jul 2023 19:34:37 +0300 Subject: [PATCH 2/3] Add tests for Zeroize --- .github/workflows/ci.yml | 4 +-- src/test.rs | 58 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c1a88aafd..e0065f270 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,10 +53,10 @@ jobs: run: cargo run --quiet working-directory: ./tools/instruction_set_support # Default tests plus Rayon and RustCrypto trait implementations. - - run: cargo test --features=rayon,traits-preview + - run: cargo test --features=rayon,traits-preview,zeroize # Same but with only one thread in the Rayon pool. This can find deadlocks. - name: "again with RAYON_NUM_THREADS=1" - run: cargo test --features=rayon,traits-preview + run: cargo test --features=rayon,traits-preview,zeroize env: RAYON_NUM_THREADS: 1 # no_std tests. diff --git a/src/test.rs b/src/test.rs index 60bbe8cca..0d94f4465 100644 --- a/src/test.rs +++ b/src/test.rs @@ -628,3 +628,61 @@ const fn test_hash_const_conversions() { let hash = crate::Hash::from_bytes(bytes); _ = hash.as_bytes(); } + +#[cfg(feature = "zeroize")] +#[test] +fn test_zeroize() { + use zeroize::Zeroize; + + let mut hash = crate::Hash([42; 32]); + hash.zeroize(); + assert_eq!(hash.0, [0u8; 32]); + + let mut hasher = crate::Hasher { + chunk_state: crate::ChunkState { + cv: [42; 8], + chunk_counter: 42, + buf: [42; 64], + buf_len: 42, + blocks_compressed: 42, + flags: 42, + platform: crate::Platform::Portable, + }, + key: [42; 8], + cv_stack: [[42; 32]; { crate::MAX_DEPTH + 1 }].into(), + }; + hasher.zeroize(); + assert_eq!(hasher.chunk_state.cv, [0; 8]); + assert_eq!(hasher.chunk_state.chunk_counter, 0); + assert_eq!(hasher.chunk_state.buf, [0; 64]); + assert_eq!(hasher.chunk_state.buf_len, 0); + assert_eq!(hasher.chunk_state.blocks_compressed, 0); + assert_eq!(hasher.chunk_state.flags, 0); + assert!(matches!(hasher.chunk_state.platform, crate::Platform::Portable)); + assert_eq!(hasher.key, [0; 8]); + assert_eq!(&*hasher.cv_stack, &[[0u8; 32]; 0]); + + + let mut output_reader = crate::OutputReader { + inner: crate::Output { + input_chaining_value: [42; 8], + block: [42; 64], + counter: 42, + block_len: 42, + flags: 42, + platform: crate::Platform::Portable, + }, + position_within_block: 42, + }; + + + output_reader.zeroize(); + assert_eq!(output_reader.inner.input_chaining_value, [0; 8]); + assert_eq!(output_reader.inner.block, [0; 64]); + assert_eq!(output_reader.inner.counter, 0); + assert_eq!(output_reader.inner.block_len, 0); + assert_eq!(output_reader.inner.flags, 0); + assert!(matches!(output_reader.inner.platform, crate::Platform::Portable)); + assert_eq!(output_reader.position_within_block, 0); + +} \ No newline at end of file From 023c89fac506f25daa5ea64164e0c8a847cc0fff Mon Sep 17 00:00:00 2001 From: Elichai Turkel Date: Sun, 9 Jul 2023 19:34:55 +0300 Subject: [PATCH 3/3] Remove unneeded digest/std in std feature --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 7c2e7a328..e05bd1aa0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ neon = [] # --no-default-features, the only way to use the SIMD implementations in this # crate is to enable the corresponding instruction sets statically for the # entire build, with e.g. RUSTFLAGS="-C target-cpu=native". -std = ["digest/std"] +std = [] # The "rayon" feature (defined below as an optional dependency) enables the # `Hasher::update_rayon` method, for multithreaded hashing. However, even if