From 4f1f6457ac30aeec07f363f184e71eb35a16b910 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Fri, 27 Aug 2021 12:21:29 -0600 Subject: [PATCH] argon2: add `alloc` feature (#215) Makes linking against liballoc optional, and adds an API which allows users to stack allocate their own memory blocks array instead of using a dynamically allocated `vec`. --- .github/workflows/argon2.yml | 2 ++ argon2/Cargo.toml | 5 ++-- argon2/src/block.rs | 10 +++---- argon2/src/instance.rs | 2 +- argon2/src/lib.rs | 58 +++++++++++++++++++++++++----------- argon2/src/memory.rs | 13 -------- argon2/src/params.rs | 23 ++++++++++++++ argon2/tests/kat.rs | 2 ++ argon2/tests/phc_strings.rs | 2 +- 9 files changed, 77 insertions(+), 40 deletions(-) diff --git a/.github/workflows/argon2.yml b/.github/workflows/argon2.yml index b0e3ff76..75e3cfb5 100644 --- a/.github/workflows/argon2.yml +++ b/.github/workflows/argon2.yml @@ -37,6 +37,7 @@ jobs: # target: ${{ matrix.target }} # override: true # - run: cargo build --target ${{ matrix.target }} --release --no-default-features +# - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features password-hash # - run: cargo build --target ${{ matrix.target }} --release # - run: cargo build --target ${{ matrix.target }} --release --features zeroize @@ -55,5 +56,6 @@ jobs: toolchain: ${{ matrix.rust }} override: true - run: cargo test --release --no-default-features + - run: cargo test --release --no-default-features --features password-hash - run: cargo test --release - run: cargo test --release --all-features diff --git a/argon2/Cargo.toml b/argon2/Cargo.toml index 05db00b6..8fe3b954 100644 --- a/argon2/Cargo.toml +++ b/argon2/Cargo.toml @@ -25,10 +25,11 @@ hex-literal = "0.3" password-hash = { version = "=0.3.0-pre.1", features = ["rand_core"] } [features] -default = ["password-hash", "rand"] +default = ["alloc", "password-hash", "rand"] +alloc = [] parallel = ["rayon", "std"] rand = ["password-hash/rand_core"] -std = ["password-hash/std"] +std = ["alloc", "password-hash/std"] [package.metadata.docs.rs] all-features = true diff --git a/argon2/src/block.rs b/argon2/src/block.rs index 96bc3065..99b96b90 100644 --- a/argon2/src/block.rs +++ b/argon2/src/block.rs @@ -12,7 +12,7 @@ use zeroize::Zeroize; /// Structure for the (1KB) memory block implemented as 128 64-bit words. #[derive(Copy, Clone, Debug)] -pub(crate) struct Block([u64; Self::SIZE / 8]); +pub struct Block([u64; Self::SIZE / 8]); impl Default for Block { fn default() -> Self { @@ -25,7 +25,7 @@ impl Block { pub const SIZE: usize = 1024; /// Load a block from a block-sized byte slice - pub fn load(&mut self, input: &[u8]) { + pub(crate) fn load(&mut self, input: &[u8]) { debug_assert_eq!(input.len(), Block::SIZE); for (i, chunk) in input.chunks(8).enumerate() { @@ -34,18 +34,18 @@ impl Block { } /// Iterate over the `u64` values contained in this block - pub fn iter(&self) -> slice::Iter<'_, u64> { + pub(crate) fn iter(&self) -> slice::Iter<'_, u64> { self.0.iter() } /// Iterate mutably over the `u64` values contained in this block - pub fn iter_mut(&mut self) -> slice::IterMut<'_, u64> { + pub(crate) fn iter_mut(&mut self) -> slice::IterMut<'_, u64> { self.0.iter_mut() } /// Function fills a new memory block and optionally XORs the old block over the new one. // TODO(tarcieri): optimized implementation (i.e. from opt.c instead of ref.c) - pub fn fill_block(&mut self, prev_block: Block, ref_block: Block, with_xor: bool) { + pub(crate) fn fill_block(&mut self, prev_block: Block, ref_block: Block, with_xor: bool) { let mut block_r = ref_block ^ prev_block; let mut block_tmp = block_r; diff --git a/argon2/src/instance.rs b/argon2/src/instance.rs index 71265ff7..0f585e03 100644 --- a/argon2/src/instance.rs +++ b/argon2/src/instance.rs @@ -98,7 +98,7 @@ impl<'a> Instance<'a> { memory, passes: context.params.t_cost(), lane_length, - lanes: context.lanes(), + lanes: context.params.lanes(), threads: context.params.t_cost(), alg, }; diff --git a/argon2/src/lib.rs b/argon2/src/lib.rs index 14a89c0d..3d433eaf 100644 --- a/argon2/src/lib.rs +++ b/argon2/src/lib.rs @@ -11,6 +11,9 @@ //! - **Argon2i**: optimized to resist side-channel attacks //! - **Argon2id**: (default) hybrid version combining both Argon2i and Argon2d //! +//! Support is provided for embedded (i.e. `no_std`) environments, including +//! ones without `alloc` support. +//! //! # Usage (simple with default params) //! //! Note: this example requires the `rand_core` crate with the `std` feature @@ -72,6 +75,7 @@ )] #![warn(rust_2018_idioms, missing_docs)] +#[cfg(feature = "alloc")] #[macro_use] extern crate alloc; @@ -88,6 +92,7 @@ mod version; pub use crate::{ algorithm::Algorithm, + block::Block, error::{Error, Result}, params::{Params, ParamsBuilder}, version::Version, @@ -101,13 +106,12 @@ pub use { }; use crate::{ - block::Block, instance::Instance, memory::{Memory, SYNC_POINTS}, }; use blake2::{digest, Blake2b, Digest}; -#[cfg(feature = "password-hash")] +#[cfg(all(feature = "alloc", feature = "password-hash"))] use { core::convert::{TryFrom, TryInto}, password_hash::{Decimal, Ident, Salt}, @@ -208,12 +212,35 @@ impl<'key> Argon2<'key> { } /// Hash a password and associated parameters into the provided output buffer. + #[cfg(feature = "alloc")] + #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] pub fn hash_password_into( &self, pwd: &[u8], salt: &[u8], ad: &[u8], out: &mut [u8], + ) -> Result<()> { + let mut blocks = vec![Block::default(); self.params.block_count()]; + self.hash_password_into_with_memory(pwd, salt, ad, out, &mut blocks) + } + + /// Hash a password and associated parameters into the provided output buffer. + /// + /// This method takes an explicit `memory_blocks` parameter which allows + /// the caller to provide the backing storage for the algorithm's state: + /// + /// - Users with the `alloc` feature enabled can use [`Argon2::hash_password_into`] + /// to have it allocated for them. + /// - `no_std` users on "heapless" targets can use an array of the [`Block`] type + /// to stack allocate this buffer. + pub fn hash_password_into_with_memory( + &self, + pwd: &[u8], + salt: &[u8], + ad: &[u8], + out: &mut [u8], + mut memory_blocks: impl AsMut<[Block]>, ) -> Result<()> { // Validate output length if out.len() @@ -255,15 +282,14 @@ impl<'key> Argon2<'key> { // Hashing all inputs let initial_hash = self.initial_hash(pwd, salt, ad, out); - let segment_length = - Memory::segment_length_for_params(self.params.m_cost(), self.params.p_cost()); - - let blocks_count = (segment_length * self.params.p_cost() * SYNC_POINTS) as usize; + let segment_length = self.params.segment_length(); + let block_count = self.params.block_count(); + let memory_blocks = memory_blocks + .as_mut() + .get_mut(..block_count) + .ok_or(Error::MemoryTooLittle)?; - // TODO(tarcieri): support for stack-allocated memory blocks (i.e. no alloc) - let mut blocks = vec![Block::default(); blocks_count]; - - let memory = Memory::new(&mut blocks, segment_length); + let memory = Memory::new(memory_blocks, segment_length); Instance::hash(self, self.algorithm, initial_hash, memory, out) } @@ -272,11 +298,6 @@ impl<'key> Argon2<'key> { &self.params } - /// Get the number of lanes. - pub(crate) fn lanes(&self) -> u32 { - self.params.p_cost() - } - /// Hashes all the inputs into `blockhash[PREHASH_DIGEST_LENGTH]`. pub(crate) fn initial_hash( &self, @@ -286,7 +307,7 @@ impl<'key> Argon2<'key> { out: &[u8], ) -> digest::Output { let mut digest = Blake2b::new(); - digest.update(&self.lanes().to_le_bytes()); + digest.update(&self.params.lanes().to_le_bytes()); digest.update(&(out.len() as u32).to_le_bytes()); digest.update(&self.params.m_cost().to_le_bytes()); digest.update(&self.params.t_cost().to_le_bytes()); @@ -310,7 +331,8 @@ impl<'key> Argon2<'key> { } } -#[cfg(feature = "password-hash")] +#[cfg(all(feature = "alloc", feature = "password-hash"))] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] #[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))] impl PasswordHasher for Argon2<'_> { type Params = Params; @@ -389,7 +411,7 @@ impl<'key> From<&Params> for Argon2<'key> { } } -#[cfg(all(test, feature = "password-hash"))] +#[cfg(all(test, feature = "alloc", feature = "password-hash"))] mod tests { use crate::{Algorithm, Argon2, Params, PasswordHasher, Salt, Version}; diff --git a/argon2/src/memory.rs b/argon2/src/memory.rs index 970705f0..c6a1233a 100644 --- a/argon2/src/memory.rs +++ b/argon2/src/memory.rs @@ -15,19 +15,6 @@ pub(crate) struct Memory<'a> { } impl<'a> Memory<'a> { - /// Align memory size. - /// - /// Minimum memory_blocks = 8*`L` blocks, where `L` is the number of lanes. - pub(crate) fn segment_length_for_params(m_cost: u32, lanes: u32) -> u32 { - let memory_blocks = if m_cost < 2 * SYNC_POINTS * lanes { - 2 * SYNC_POINTS * lanes - } else { - m_cost - }; - - memory_blocks / (lanes * SYNC_POINTS) - } - /// Instantiate a new memory struct pub(crate) fn new(data: &'a mut [Block], segment_length: u32) -> Self { Self { diff --git a/argon2/src/params.rs b/argon2/src/params.rs index 9b802d41..db6b1b58 100644 --- a/argon2/src/params.rs +++ b/argon2/src/params.rs @@ -110,6 +110,29 @@ impl Params { pub fn output_len(self) -> Option { self.output_len } + + /// Get the number of lanes. + pub(crate) fn lanes(self) -> u32 { + self.p_cost + } + + /// Get the segment length given the configured `m_cost` and `p_cost`. + /// + /// Minimum memory_blocks = 8*`L` blocks, where `L` is the number of lanes. + pub(crate) fn segment_length(self) -> u32 { + let memory_blocks = if self.m_cost < 2 * SYNC_POINTS * self.lanes() { + 2 * SYNC_POINTS * self.lanes() + } else { + self.m_cost + }; + + memory_blocks / (self.lanes() * SYNC_POINTS) + } + + /// Get the number of blocks required given the configured `m_cost` and `p_cost`. + pub(crate) fn block_count(self) -> usize { + (self.segment_length() * self.p_cost * SYNC_POINTS) as usize + } } impl Default for Params { diff --git a/argon2/tests/kat.rs b/argon2/tests/kat.rs index 3f63ab55..4395de3d 100644 --- a/argon2/tests/kat.rs +++ b/argon2/tests/kat.rs @@ -4,6 +4,8 @@ //! `draft-irtf-cfrg-argon2-12` Section 5: //! +#![cfg(feature = "alloc")] + // TODO(tarcieri): test full set of vectors from the reference implementation: // https://github.com/P-H-C/phc-winner-argon2/blob/master/src/test.c diff --git a/argon2/tests/phc_strings.rs b/argon2/tests/phc_strings.rs index ed2943ab..758b9daa 100644 --- a/argon2/tests/phc_strings.rs +++ b/argon2/tests/phc_strings.rs @@ -2,7 +2,7 @@ //! //! Adapted from: -#![cfg(feature = "password-hash")] +#![cfg(all(feature = "alloc", feature = "password-hash"))] use argon2::{Argon2, PasswordHash, PasswordVerifier};