Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optionally Implement Zeroize on exported types #309

Merged
merged 3 commits into from
Jul 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
7 changes: 5 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"
Expand Down
11 changes: 11 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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]);

Expand Down Expand Up @@ -371,13 +376,15 @@ 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,
block: [u8; 64],
block_len: u8,
counter: u64,
flags: u8,
#[cfg_attr(feature = "zeroize", zeroize(skip))]
platform: Platform,
}

Expand Down Expand Up @@ -414,13 +421,15 @@ impl Output {
}

#[derive(Clone)]
#[cfg_attr(feature = "zeroize", derive(zeroize::Zeroize))]
struct ChunkState {
cv: CVWords,
chunk_counter: u64,
buf: [u8; BLOCK_LEN],
buf_len: u8,
blocks_compressed: u8,
flags: u8,
#[cfg_attr(feature = "zeroize", zeroize(skip))]
platform: Platform,
}

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
58 changes: 58 additions & 0 deletions src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

}