diff --git a/Cargo.lock b/Cargo.lock index 453187802..1e7012dcd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,6 +6,7 @@ version = 3 name = "argon2" version = "0.2.4" dependencies = [ + "base64ct", "blake2", "hex-literal", "password-hash", @@ -269,7 +270,7 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "password-hash" version = "0.3.0-pre.1" -source = "git+https://github.com/rustcrypto/traits.git#9e0de9d0aa33f0a7c687d7ae0809e3194027ba6c" +source = "git+https://github.com/rustcrypto/traits.git#c759117b348ec195fab4ec835c260f0b4c402389" dependencies = [ "base64ct", "rand_core", diff --git a/argon2/Cargo.toml b/argon2/Cargo.toml index 8fe3b9541..779e27c63 100644 --- a/argon2/Cargo.toml +++ b/argon2/Cargo.toml @@ -15,6 +15,7 @@ edition = "2018" readme = "README.md" [dependencies] +base64ct = "1" blake2 = { version = "0.9", default-features = false } password-hash = { version = "=0.3.0-pre.1", optional = true } rayon = { version = "1", optional = true } diff --git a/argon2/src/error.rs b/argon2/src/error.rs index 0e030f1ef..3fa3c4843 100644 --- a/argon2/src/error.rs +++ b/argon2/src/error.rs @@ -9,7 +9,6 @@ use password_hash::errors::InvalidValue; pub type Result = core::result::Result; /// Error type. -// TODO(tarcieri): consolidate/replace with `password_hash::Error` #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum Error { /// Associated data is too long. @@ -18,6 +17,12 @@ pub enum Error { /// Algorithm identifier invalid. AlgorithmInvalid, + /// "B64" encoding is invalid. + B64Encoding(base64ct::Error), + + /// Key ID is too long. + KeyIdTooLong, + /// Memory cost is too small. MemoryTooLittle, @@ -60,6 +65,8 @@ impl fmt::Display for Error { f.write_str(match self { Error::AdTooLong => "associated data is too long", Error::AlgorithmInvalid => "algorithm identifier invalid", + Error::B64Encoding(inner) => return write!(f, "B64 encoding invalid: {}", inner), + Error::KeyIdTooLong => "key ID is too long", Error::MemoryTooLittle => "memory cost is too small", Error::MemoryTooMuch => "memory cost is too large", Error::OutputTooShort => "output is too short", @@ -76,26 +83,32 @@ impl fmt::Display for Error { } } +impl From for Error { + fn from(err: base64ct::Error) -> Error { + Error::B64Encoding(err) + } +} + #[cfg(feature = "password-hash")] #[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))] impl From for password_hash::Error { fn from(err: Error) -> password_hash::Error { match err { - Error::AdTooLong => password_hash::Error::ParamValueInvalid(InvalidValue::TooLong), + Error::AdTooLong => InvalidValue::TooLong.param_error(), Error::AlgorithmInvalid => password_hash::Error::Algorithm, - Error::MemoryTooLittle => { - password_hash::Error::ParamValueInvalid(InvalidValue::TooShort) - } - Error::MemoryTooMuch => password_hash::Error::ParamValueInvalid(InvalidValue::TooLong), + Error::B64Encoding(inner) => password_hash::Error::B64Encoding(inner), + Error::KeyIdTooLong => InvalidValue::TooLong.param_error(), + Error::MemoryTooLittle => InvalidValue::TooShort.param_error(), + Error::MemoryTooMuch => InvalidValue::TooLong.param_error(), Error::PwdTooLong => password_hash::Error::Password, Error::OutputTooShort => password_hash::Error::OutputTooShort, Error::OutputTooLong => password_hash::Error::OutputTooLong, - Error::SaltTooShort => password_hash::Error::SaltInvalid(InvalidValue::TooShort), - Error::SaltTooLong => password_hash::Error::SaltInvalid(InvalidValue::TooLong), - Error::SecretTooLong => password_hash::Error::ParamValueInvalid(InvalidValue::TooLong), - Error::ThreadsTooFew => password_hash::Error::ParamValueInvalid(InvalidValue::TooShort), - Error::ThreadsTooMany => password_hash::Error::ParamValueInvalid(InvalidValue::TooLong), - Error::TimeTooSmall => password_hash::Error::ParamValueInvalid(InvalidValue::TooShort), + Error::SaltTooShort => InvalidValue::TooShort.salt_error(), + Error::SaltTooLong => InvalidValue::TooLong.salt_error(), + Error::SecretTooLong => InvalidValue::TooLong.param_error(), + Error::ThreadsTooFew => InvalidValue::TooShort.param_error(), + Error::ThreadsTooMany => InvalidValue::TooLong.param_error(), + Error::TimeTooSmall => InvalidValue::TooShort.param_error(), Error::VersionInvalid => password_hash::Error::Version, } } diff --git a/argon2/src/instance.rs b/argon2/src/instance.rs index 0f585e03d..58175588a 100644 --- a/argon2/src/instance.rs +++ b/argon2/src/instance.rs @@ -34,7 +34,7 @@ struct Position { index: u32, } -/// Argon2 instance: memory pointer, number of passes, amount of memory, type, +/// Argon2 instance: memory buffer, number of passes, amount of memory, type, /// and derived values. /// /// Used to evaluate the number and location of blocks to construct in each @@ -405,11 +405,11 @@ fn next_addresses(address_block: &mut Block, input_block: &mut Block, zero_block /// BLAKE2b with an extended output, as described in the Argon2 paper fn blake2b_long(inputs: &[&[u8]], mut out: &mut [u8]) -> Result<()> { - if out.len() < Params::MIN_OUTPUT_LENGTH as usize { + if out.len() < Params::MIN_OUTPUT_LEN as usize { return Err(Error::OutputTooLong); } - if out.len() > Params::MAX_OUTPUT_LENGTH as usize { + if out.len() > Params::MAX_OUTPUT_LEN as usize { return Err(Error::OutputTooLong); } diff --git a/argon2/src/lib.rs b/argon2/src/lib.rs index 3d433eaf9..0d39f9eae 100644 --- a/argon2/src/lib.rs +++ b/argon2/src/lib.rs @@ -113,53 +113,30 @@ use blake2::{digest, Blake2b, Digest}; #[cfg(all(feature = "alloc", feature = "password-hash"))] use { - core::convert::{TryFrom, TryInto}, - password_hash::{Decimal, Ident, Salt}, + core::convert::TryFrom, + password_hash::{Decimal, Ident, ParamsString, Salt}, }; /// Maximum password length in bytes. -pub const MAX_PWD_LENGTH: usize = 0xFFFFFFFF; - -/// Minimum and maximum associated data length in bytes. -pub const MAX_AD_LENGTH: usize = 0xFFFFFFFF; +pub const MAX_PWD_LEN: usize = 0xFFFFFFFF; /// Minimum and maximum salt length in bytes. -pub const MIN_SALT_LENGTH: usize = 8; +pub const MIN_SALT_LEN: usize = 8; /// Maximum salt length in bytes. -pub const MAX_SALT_LENGTH: usize = 0xFFFFFFFF; +pub const MAX_SALT_LEN: usize = 0xFFFFFFFF; /// Maximum secret key length in bytes. -pub const MAX_SECRET_LENGTH: usize = 0xFFFFFFFF; +pub const MAX_SECRET_LEN: usize = 0xFFFFFFFF; /// Argon2 context. /// -/// Holds the following Argon2 inputs: -/// -/// - output array and its length, -/// - password and its length, -/// - salt and its length, -/// - secret and its length, -/// - associated data and its length, -/// - number of passes, amount of used memory (in KBytes, can be rounded up a bit) -/// - number of parallel threads that will be run. -/// -/// All the parameters above affect the output hash value. -/// Additionally, two function pointers can be provided to allocate and -/// deallocate the memory (if NULL, memory will be allocated internally). -/// Also, three flags indicate whether to erase password, secret as soon as they -/// are pre-hashed (and thus not needed anymore), and the entire memory -/// -/// Simplest situation: you have output array `out[8]`, password is stored in -/// `pwd[32]`, salt is stored in `salt[16]`, you do not have keys nor associated -/// data. +/// This is the primary type of this crate's API, and contains the following: /// -/// You need to spend 1 GB of RAM and you run 5 passes of Argon2d with -/// 4 parallel lanes. -/// -/// You want to erase the password, but you're OK with last pass not being -/// erased. -// TODO(tarcieri): replace `Params`-related fields with an internally-stored struct +/// - Argon2 [`Algorithm`] variant to be used +/// - Argon2 [`Version`] to be used +/// - Default set of [`Params`] to be used +/// - (Optional) Secret key a.k.a. "pepper" to be used #[derive(Clone)] pub struct Argon2<'key> { /// Algorithm to use @@ -199,7 +176,7 @@ impl<'key> Argon2<'key> { version: Version, params: Params, ) -> Result { - if MAX_SECRET_LENGTH < secret.len() { + if MAX_SECRET_LEN < secret.len() { return Err(Error::SecretTooLong); } @@ -214,15 +191,9 @@ 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<()> { + pub fn hash_password_into(&self, pwd: &[u8], salt: &[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) + self.hash_password_into_with_memory(pwd, salt, out, &mut blocks) } /// Hash a password and associated parameters into the provided output buffer. @@ -238,49 +209,33 @@ impl<'key> Argon2<'key> { &self, pwd: &[u8], salt: &[u8], - ad: &[u8], out: &mut [u8], mut memory_blocks: impl AsMut<[Block]>, ) -> Result<()> { // Validate output length - if out.len() - < self - .params - .output_len() - .unwrap_or(Params::MIN_OUTPUT_LENGTH) - { + if out.len() < self.params.output_len().unwrap_or(Params::MIN_OUTPUT_LEN) { return Err(Error::OutputTooShort); } - if out.len() - > self - .params - .output_len() - .unwrap_or(Params::MAX_OUTPUT_LENGTH) - { + if out.len() > self.params.output_len().unwrap_or(Params::MAX_OUTPUT_LEN) { return Err(Error::OutputTooLong); } - if pwd.len() > MAX_PWD_LENGTH { + if pwd.len() > MAX_PWD_LEN { return Err(Error::PwdTooLong); } // Validate salt (required param) - if salt.len() < MIN_SALT_LENGTH { + if salt.len() < MIN_SALT_LEN { return Err(Error::SaltTooShort); } - if salt.len() > MAX_SALT_LENGTH { + if salt.len() > MAX_SALT_LEN { return Err(Error::SaltTooLong); } - // Validate associated data (optional param) - if ad.len() > MAX_AD_LENGTH { - return Err(Error::AdTooLong); - } - // Hashing all inputs - let initial_hash = self.initial_hash(pwd, salt, ad, out); + let initial_hash = self.initial_hash(pwd, salt, out); let segment_length = self.params.segment_length(); let block_count = self.params.block_count(); @@ -298,12 +253,11 @@ impl<'key> Argon2<'key> { &self.params } - /// Hashes all the inputs into `blockhash[PREHASH_DIGEST_LENGTH]`. + /// Hashes all the inputs into `blockhash[PREHASH_DIGEST_LEN]`. pub(crate) fn initial_hash( &self, pwd: &[u8], salt: &[u8], - ad: &[u8], out: &[u8], ) -> digest::Output { let mut digest = Blake2b::new(); @@ -325,8 +279,8 @@ impl<'key> Argon2<'key> { digest.update(0u32.to_le_bytes()); } - digest.update(&(ad.len() as u32).to_le_bytes()); - digest.update(ad); + digest.update(&(self.params.data().len() as u32).to_le_bytes()); + digest.update(self.params.data()); digest.finalize() } } @@ -348,22 +302,19 @@ impl PasswordHasher for Argon2<'_> { let salt = Salt::try_from(salt.as_ref())?; let mut salt_arr = [0u8; 64]; let salt_bytes = salt.b64_decode(&mut salt_arr)?; - - // TODO(tarcieri): support the `data` parameter (i.e. associated data) - let ad = b""; let output_len = self .params .output_len() - .unwrap_or(Params::DEFAULT_OUTPUT_LENGTH); + .unwrap_or(Params::DEFAULT_OUTPUT_LEN); let output = password_hash::Output::init_with(output_len, |out| { - Ok(self.hash_password_into(password, salt_bytes, ad, out)?) + Ok(self.hash_password_into(password, salt_bytes, out)?) })?; Ok(PasswordHash { algorithm: self.algorithm.ident(), version: Some(self.version.into()), - params: self.params.try_into()?, + params: ParamsString::try_from(&self.params)?, salt: Some(salt), hash: Some(output), }) @@ -407,7 +358,7 @@ impl<'key> From for Argon2<'key> { impl<'key> From<&Params> for Argon2<'key> { fn from(params: &Params) -> Self { - Self::from(*params) + Self::from(params.clone()) } } diff --git a/argon2/src/params.rs b/argon2/src/params.rs index db6b1b58d..23a6159a2 100644 --- a/argon2/src/params.rs +++ b/argon2/src/params.rs @@ -1,7 +1,8 @@ //! Argon2 password hash parameters. use crate::{Error, Result, SYNC_POINTS}; -use core::convert::TryFrom; +use base64ct::{Base64Unpadded as B64, Encoding}; +use core::{convert::TryFrom, str::FromStr}; #[cfg(feature = "password-hash")] use { @@ -12,8 +13,7 @@ use { /// Argon2 password hash parameters. /// /// These are parameters which can be encoded into a PHC hash string. -// TODO(tarcieri): make members private, ensure `Params` is always valid? -#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct Params { /// Memory size, expressed in kilobytes, between 1 and (2^32)-1. /// @@ -30,6 +30,12 @@ pub struct Params { /// Value is an integer in decimal (1 to 3 digits). p_cost: u32, + /// Key identifier. + keyid: KeyId, + + /// Associated data. + data: AssociatedData, + /// Size of the output (in bytes). output_len: Option, } @@ -62,24 +68,30 @@ impl Params { /// Minimum and maximum number of threads (i.e. parallelism). pub const MAX_P_COST: u32 = 0xFFFFFF; + /// Maximum length of a key ID in bytes. + pub const MAX_KEYID_LEN: usize = 8; + + /// Maximum length of associated data in bytes. + pub const MAX_DATA_LEN: usize = 32; + /// Default output length. - pub const DEFAULT_OUTPUT_LENGTH: usize = 32; + pub const DEFAULT_OUTPUT_LEN: usize = 32; /// Minimum digest size in bytes. - pub const MIN_OUTPUT_LENGTH: usize = 4; + pub const MIN_OUTPUT_LEN: usize = 4; /// Maximum digest size in bytes. - pub const MAX_OUTPUT_LENGTH: usize = 0xFFFFFFFF; + pub const MAX_OUTPUT_LEN: usize = 0xFFFFFFFF; /// Create new parameters. pub fn new(m_cost: u32, t_cost: u32, p_cost: u32, output_len: Option) -> Result { - let mut builder = ParamsBuilder::new() - .m_cost(m_cost)? - .t_cost(t_cost)? - .p_cost(p_cost)?; + let mut builder = ParamsBuilder::new(); + builder.m_cost(m_cost)?; + builder.t_cost(t_cost)?; + builder.p_cost(p_cost)?; if let Some(len) = output_len { - builder = builder.output_len(len)?; + builder.output_len(len)?; } builder.params() @@ -88,38 +100,52 @@ impl Params { /// Memory size, expressed in kilobytes, between 1 and (2^32)-1. /// /// Value is an integer in decimal (1 to 10 digits). - pub fn m_cost(self) -> u32 { + pub fn m_cost(&self) -> u32 { self.m_cost } /// Number of iterations, between 1 and (2^32)-1. /// /// Value is an integer in decimal (1 to 10 digits). - pub fn t_cost(self) -> u32 { + pub fn t_cost(&self) -> u32 { self.t_cost } /// Degree of parallelism, between 1 and 255. /// /// Value is an integer in decimal (1 to 3 digits). - pub fn p_cost(self) -> u32 { + pub fn p_cost(&self) -> u32 { self.p_cost } + /// Key identifier: byte slice between 0 and 8 bytes in length. + /// + /// Defaults to an empty byte slice. + pub fn keyid(&self) -> &[u8] { + self.keyid.as_bytes() + } + + /// Associated data: byte slice between 0 and 32 bytes in length. + /// + /// Defaults to an empty byte slice. + pub fn data(&self) -> &[u8] { + self.data.as_bytes() + } + /// Length of the output (in bytes). - pub fn output_len(self) -> Option { + pub fn output_len(&self) -> Option { self.output_len } /// Get the number of lanes. - pub(crate) fn lanes(self) -> u32 { + 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 { + 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 { @@ -130,7 +156,7 @@ impl Params { } /// Get the number of blocks required given the configured `m_cost` and `p_cost`. - pub(crate) fn block_count(self) -> usize { + pub(crate) fn block_count(&self) -> usize { (self.segment_length() * self.p_cost * SYNC_POINTS) as usize } } @@ -141,35 +167,141 @@ impl Default for Params { m_cost: Self::DEFAULT_M_COST, t_cost: Self::DEFAULT_T_COST, p_cost: Self::DEFAULT_P_COST, + keyid: KeyId::default(), + data: AssociatedData::default(), output_len: None, } } } +macro_rules! param_buf { + ($ty:ident, $name:expr, $max_len:expr, $error:expr, $doc:expr) => { + #[doc = $doc] + #[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)] + pub struct $ty { + /// Byte array + bytes: [u8; Self::MAX_LEN], + + /// Length of byte array + len: usize, + } + + impl $ty { + /// Maximum length in bytes + pub const MAX_LEN: usize = $max_len; + + #[doc = "Create a new"] + #[doc = $name] + #[doc = "from a slice."] + pub fn new(slice: &[u8]) -> Result { + let mut bytes = [0u8; Self::MAX_LEN]; + let len = slice.len(); + bytes.get_mut(..len).ok_or($error)?.copy_from_slice(slice); + Ok(Self { bytes, len }) + } + + #[doc = "Decode"] + #[doc = $name] + #[doc = " from a B64 string"] + pub fn from_b64(s: &str) -> Result { + let mut bytes = [0u8; Self::MAX_LEN]; + Self::new(B64::decode(s, &mut bytes)?) + } + + /// Borrow the inner value as a byte slice. + pub fn as_bytes(&self) -> &[u8] { + &self.bytes[..self.len] + } + + /// Get the length in bytes. + #[allow(dead_code)] + pub fn len(&self) -> usize { + self.len + } + + /// Is this value empty? + #[allow(dead_code)] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + } + + impl AsRef<[u8]> for $ty { + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } + } + + impl FromStr for $ty { + type Err = Error; + + fn from_str(s: &str) -> Result { + Self::from_b64(s) + } + } + + impl TryFrom<&[u8]> for $ty { + type Error = Error; + + fn try_from(bytes: &[u8]) -> Result { + Self::new(bytes) + } + } + }; +} + +// KeyId +param_buf!( + KeyId, + "KeyId", + Params::MAX_KEYID_LEN, + Error::KeyIdTooLong, + "Key identifier" +); + +// AssociatedData +param_buf!( + AssociatedData, + "AssociatedData", + Params::MAX_DATA_LEN, + Error::AdTooLong, + "Associated data" +); + #[cfg(feature = "password-hash")] #[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))] impl<'a> TryFrom<&'a PasswordHash<'a>> for Params { type Error = password_hash::Error; fn try_from(hash: &'a PasswordHash<'a>) -> password_hash::Result { - let mut params = ParamsBuilder::new(); + let mut builder = ParamsBuilder::new(); for (ident, value) in hash.params.iter() { match ident.as_str() { - "m" => params = params.m_cost(value.decimal()?)?, - "t" => params = params.t_cost(value.decimal()?)?, - "p" => params = params.p_cost(value.decimal()?)?, - "keyid" => (), // Ignored; correct key must be given to `Argon2` context - // TODO(tarcieri): `data` parameter + "m" => { + builder.m_cost(value.decimal()?)?; + } + "t" => { + builder.t_cost(value.decimal()?)?; + } + "p" => { + builder.p_cost(value.decimal()?)?; + } + "keyid" => { + builder.params.keyid = ident.as_str().parse()?; + } + "data" => { + builder.params.data = ident.as_str().parse()?; + } _ => return Err(password_hash::Error::ParamNameInvalid), } } if let Some(output) = &hash.hash { - params = params.output_len(output.len())?; + builder.output_len(output.len())?; } - Ok(params.try_into()?) + Ok(builder.try_into()?) } } @@ -179,15 +311,35 @@ impl<'a> TryFrom for ParamsString { type Error = password_hash::Error; fn try_from(params: Params) -> password_hash::Result { + ParamsString::try_from(¶ms) + } +} + +#[cfg(feature = "password-hash")] +#[cfg_attr(docsrs, doc(cfg(feature = "password-hash")))] +impl<'a> TryFrom<&Params> for ParamsString { + type Error = password_hash::Error; + + fn try_from(params: &Params) -> password_hash::Result { let mut output = ParamsString::new(); output.add_decimal("m", params.m_cost)?; output.add_decimal("t", params.t_cost)?; output.add_decimal("p", params.p_cost)?; + + if !params.keyid.is_empty() { + output.add_b64_bytes("keyid", params.keyid.as_bytes())?; + } + + if !params.data.is_empty() { + output.add_b64_bytes("data", params.data.as_bytes())?; + } + Ok(output) } } /// Builder for Argon2 [`Params`]. +#[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct ParamsBuilder { /// Parameters being constructed params: Params, @@ -202,7 +354,7 @@ impl ParamsBuilder { } /// Set memory size, expressed in kilobytes, between 1 and (2^32)-1. - pub fn m_cost(mut self, m_cost: u32) -> Result { + pub fn m_cost(&mut self, m_cost: u32) -> Result<&mut Self> { if m_cost < Params::MIN_M_COST { return Err(Error::MemoryTooLittle); } @@ -216,7 +368,7 @@ impl ParamsBuilder { } /// Set number of iterations, between 1 and (2^32)-1. - pub fn t_cost(mut self, t_cost: u32) -> Result { + pub fn t_cost(&mut self, t_cost: u32) -> Result<&mut Self> { if t_cost < Params::MIN_T_COST { return Err(Error::TimeTooSmall); } @@ -228,7 +380,7 @@ impl ParamsBuilder { } /// Set degree of parallelism, between 1 and 255. - pub fn p_cost(mut self, p_cost: u32) -> Result { + pub fn p_cost(&mut self, p_cost: u32) -> Result<&mut Self> { if p_cost < Params::MIN_P_COST { return Err(Error::ThreadsTooFew); } @@ -241,13 +393,29 @@ impl ParamsBuilder { Ok(self) } + /// Set key identifier. + /// + /// Must be 8-bytes or less. + pub fn keyid(&mut self, keyid: &[u8]) -> Result<&mut Self> { + self.params.keyid = KeyId::new(keyid)?; + Ok(self) + } + + /// Set associated data. + /// + /// Must be 32-bytes or less. + pub fn data(&mut self, bytes: &[u8]) -> Result<&mut Self> { + self.params.data = AssociatedData::new(bytes)?; + Ok(self) + } + /// Set length of the output (in bytes). - pub fn output_len(mut self, len: usize) -> Result { - if len < Params::MIN_OUTPUT_LENGTH { + pub fn output_len(&mut self, len: usize) -> Result<&mut Self> { + if len < Params::MIN_OUTPUT_LEN { return Err(Error::OutputTooShort); } - if len > Params::MAX_OUTPUT_LENGTH { + if len > Params::MAX_OUTPUT_LEN { return Err(Error::OutputTooLong); } @@ -270,12 +438,6 @@ impl ParamsBuilder { } } -impl Default for ParamsBuilder { - fn default() -> Self { - Self::new() - } -} - impl TryFrom for Params { type Error = Error; diff --git a/argon2/tests/kat.rs b/argon2/tests/kat.rs index 4395de3d6..736a65e5b 100644 --- a/argon2/tests/kat.rs +++ b/argon2/tests/kat.rs @@ -9,9 +9,19 @@ // 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 -use argon2::{Algorithm, Argon2, Params, Version}; +use argon2::{Algorithm, Argon2, Params, ParamsBuilder, Version}; use hex_literal::hex; +/// Params used by the KATs. +fn example_params() -> Params { + let mut builder = ParamsBuilder::new(); + builder.m_cost(32).unwrap(); + builder.t_cost(3).unwrap(); + builder.p_cost(4).unwrap(); + builder.data(&[0x04; 12]).unwrap(); + builder.params().unwrap() +} + /// ======================================= /// Argon2d version number 16 /// ======================================= @@ -34,26 +44,20 @@ use hex_literal::hex; fn argon2d_v0x10() { let algorithm = Algorithm::Argon2d; let version = Version::V0x10; - let m_cost = 32; - let t_cost = 3; - let parallelism = 4; + let params = example_params(); let password = [0x01; 32]; let salt = [0x02; 16]; let secret = [0x03; 8]; - let ad = [0x04; 12]; let expected_tag = hex!( " 96 a9 d4 e5 a1 73 40 92 c8 5e 29 f4 10 a4 59 14 a5 dd 1f 5c bf 08 b2 67 0d a6 8a 02 85 ab f3 2b - " + " ); - let params = Params::new(m_cost, t_cost, parallelism, None).unwrap(); let ctx = Argon2::new_with_secret(&secret, algorithm, version, params).unwrap(); - let mut out = [0u8; 32]; - ctx.hash_password_into(&password, &salt, &ad, &mut out) - .unwrap(); + ctx.hash_password_into(&password, &salt, &mut out).unwrap(); assert_eq!(out, expected_tag); } @@ -80,26 +84,20 @@ fn argon2d_v0x10() { fn argon2i_v0x10() { let algorithm = Algorithm::Argon2i; let version = Version::V0x10; - let m_cost = 32; - let t_cost = 3; - let parallelism = 4; + let params = example_params(); let password = [0x01; 32]; let salt = [0x02; 16]; let secret = [0x03; 8]; - let ad = [0x04; 12]; let expected_tag = hex!( " 87 ae ed d6 51 7a b8 30 cd 97 65 cd 82 31 ab b2 e6 47 a5 de e0 8f 7c 05 e0 2f cb 76 33 35 d0 fd - " + " ); - let params = Params::new(m_cost, t_cost, parallelism, None).unwrap(); let ctx = Argon2::new_with_secret(&secret, algorithm, version, params).unwrap(); - let mut out = [0u8; 32]; - ctx.hash_password_into(&password, &salt, &ad, &mut out) - .unwrap(); + ctx.hash_password_into(&password, &salt, &mut out).unwrap(); assert_eq!(out, expected_tag); } @@ -126,26 +124,20 @@ fn argon2i_v0x10() { fn argon2id_v0x10() { let algorithm = Algorithm::Argon2id; let version = Version::V0x10; - let m_cost = 32; - let t_cost = 3; - let parallelism = 4; + let params = example_params(); let password = [0x01; 32]; let salt = [0x02; 16]; let secret = [0x03; 8]; - let ad = [0x04; 12]; let expected_tag = hex!( " b6 46 15 f0 77 89 b6 6b 64 5b 67 ee 9e d3 b3 77 ae 35 0b 6b fc bb 0f c9 51 41 ea 8f 32 26 13 c0 - " + " ); - let params = Params::new(m_cost, t_cost, parallelism, None).unwrap(); let ctx = Argon2::new_with_secret(&secret, algorithm, version, params).unwrap(); - let mut out = [0u8; 32]; - ctx.hash_password_into(&password, &salt, &ad, &mut out) - .unwrap(); + ctx.hash_password_into(&password, &salt, &mut out).unwrap(); assert_eq!(out, expected_tag); } @@ -183,13 +175,10 @@ fn argon2id_v0x10() { fn argon2d_v0x13() { let algorithm = Algorithm::Argon2d; let version = Version::V0x13; - let m_cost = 32; - let t_cost = 3; - let parallelism = 4; + let params = example_params(); let password = [0x01; 32]; let salt = [0x02; 16]; let secret = [0x03; 8]; - let ad = [0x04; 12]; let expected_tag = hex!( " 51 2b 39 1b 6f 11 62 97 @@ -199,12 +188,9 @@ fn argon2d_v0x13() { " ); - let params = Params::new(m_cost, t_cost, parallelism, None).unwrap(); let ctx = Argon2::new_with_secret(&secret, algorithm, version, params).unwrap(); - let mut out = [0u8; 32]; - ctx.hash_password_into(&password, &salt, &ad, &mut out) - .unwrap(); + ctx.hash_password_into(&password, &salt, &mut out).unwrap(); assert_eq!(out, expected_tag); } @@ -242,28 +228,22 @@ fn argon2d_v0x13() { fn argon2i_v0x13() { let algorithm = Algorithm::Argon2i; let version = Version::V0x13; - let m_cost = 32; - let t_cost = 3; - let parallelism = 4; + let params = example_params(); let password = [0x01; 32]; let salt = [0x02; 16]; let secret = [0x03; 8]; - let ad = [0x04; 12]; let expected_tag = hex!( " c8 14 d9 d1 dc 7f 37 aa 13 f0 d7 7f 24 94 bd a1 c8 de 6b 01 6d d3 88 d2 99 52 a4 c4 67 2b 6c e8 - " + " ); - let params = Params::new(m_cost, t_cost, parallelism, None).unwrap(); let ctx = Argon2::new_with_secret(&secret, algorithm, version, params).unwrap(); - let mut out = [0u8; 32]; - ctx.hash_password_into(&password, &salt, &ad, &mut out) - .unwrap(); + ctx.hash_password_into(&password, &salt, &mut out).unwrap(); assert_eq!(out, expected_tag); } @@ -291,26 +271,20 @@ fn argon2i_v0x13() { fn argon2id_v0x13() { let algorithm = Algorithm::Argon2id; let version = Version::V0x13; - let m_cost = 32; - let t_cost = 3; - let parallelism = 4; + let params = example_params(); let password = [0x01; 32]; let salt = [0x02; 16]; let secret = [0x03; 8]; - let ad = [0x04; 12]; let expected_tag = hex!( " 0d 64 0d f5 8d 78 76 6c 08 c0 37 a3 4a 8b 53 c9 d0 1e f0 45 2d 75 b6 5e b5 25 20 e9 6b 01 e6 59 - " + " ); - let params = Params::new(m_cost, t_cost, parallelism, None).unwrap(); let ctx = Argon2::new_with_secret(&secret, algorithm, version, params).unwrap(); - let mut out = [0u8; 32]; - ctx.hash_password_into(&password, &salt, &ad, &mut out) - .unwrap(); + ctx.hash_password_into(&password, &salt, &mut out).unwrap(); assert_eq!(out, expected_tag); } diff --git a/pbkdf2/src/simple.rs b/pbkdf2/src/simple.rs index 5f0927714..ca5245f5d 100644 --- a/pbkdf2/src/simple.rs +++ b/pbkdf2/src/simple.rs @@ -197,7 +197,7 @@ impl<'a> TryFrom<&'a PasswordHash<'a>> for Params { value .decimal()? .try_into() - .map_err(|_| Error::ParamValueInvalid(InvalidValue::Malformed))?, + .map_err(|_| InvalidValue::Malformed.param_error())?, ) } _ => return Err(Error::ParamNameInvalid), @@ -207,10 +207,8 @@ impl<'a> TryFrom<&'a PasswordHash<'a>> for Params { if let Some(len) = output_length { if let Some(hash) = &hash.hash { match hash.len().cmp(&len) { - Ordering::Less => return Err(Error::ParamValueInvalid(InvalidValue::TooShort)), - Ordering::Greater => { - return Err(Error::ParamValueInvalid(InvalidValue::TooLong)) - } + Ordering::Less => return Err(InvalidValue::TooShort.param_error()), + Ordering::Greater => return Err(InvalidValue::TooLong.param_error()), Ordering::Equal => (), } } @@ -257,13 +255,13 @@ impl McfHasher for Pbkdf2 { let mut count_arr = [0u8; 4]; if Base64::decode(count, &mut count_arr)?.len() != 4 { - return Err(Error::ParamValueInvalid(InvalidValue::Malformed)); + return Err(InvalidValue::Malformed.param_error()); } let count = u32::from_be_bytes(count_arr); (count, salt, hash) } - _ => return Err(Error::ParamValueInvalid(InvalidValue::Malformed)), + _ => return Err(InvalidValue::Malformed.param_error()), }; let salt = Salt::new(b64_strip(salt))?; diff --git a/scrypt/src/params.rs b/scrypt/src/params.rs index 9bc69ec5f..ec0b26c8f 100644 --- a/scrypt/src/params.rs +++ b/scrypt/src/params.rs @@ -138,7 +138,7 @@ impl<'a> TryFrom<&'a PasswordHash<'a>> for Params { log_n = value .decimal()? .try_into() - .map_err(|_| Error::ParamValueInvalid(InvalidValue::Malformed))? + .map_err(|_| InvalidValue::Malformed.param_error())? } "r" => r = value.decimal()?, "p" => p = value.decimal()?, @@ -146,8 +146,7 @@ impl<'a> TryFrom<&'a PasswordHash<'a>> for Params { } } - Params::new(log_n, r, p) - .map_err(|_| password_hash::Error::ParamValueInvalid(InvalidValue::Malformed)) + Params::new(log_n, r, p).map_err(|_| InvalidValue::Malformed.param_error()) } } diff --git a/scrypt/src/simple.rs b/scrypt/src/simple.rs index 3153a8fe0..79494ea52 100644 --- a/scrypt/src/simple.rs +++ b/scrypt/src/simple.rs @@ -76,25 +76,24 @@ impl McfHasher for Scrypt { [Some(""), Some("rscrypt"), Some("0"), Some(p), Some(s), Some(h), Some(""), None] => { let pvec = Base64::decode_vec(p)?; if pvec.len() != 3 { - return Err(Error::ParamValueInvalid(InvalidValue::Malformed)); + return Err(InvalidValue::Malformed.param_error()); } (pvec[0], pvec[1] as u32, pvec[2] as u32, s, h) } [Some(""), Some("rscrypt"), Some("1"), Some(p), Some(s), Some(h), Some(""), None] => { let pvec = Base64::decode_vec(p)?; if pvec.len() != 9 { - return Err(Error::ParamValueInvalid(InvalidValue::Malformed)); + return Err(InvalidValue::Malformed.param_error()); } let log_n = pvec[0]; let r = u32::from_le_bytes(pvec[1..5].try_into().unwrap()); let p = u32::from_le_bytes(pvec[5..9].try_into().unwrap()); (log_n, r, p, s, h) } - _ => return Err(Error::ParamValueInvalid(InvalidValue::Malformed)), + _ => return Err(InvalidValue::Malformed.param_error()), }; - let params = Params::new(log_n, r, p) - .map_err(|_| Error::ParamValueInvalid(InvalidValue::Malformed))?; + let params = Params::new(log_n, r, p).map_err(|_| InvalidValue::Malformed.param_error())?; let salt = Salt::new(b64_strip(salt))?; let hash = Output::b64_decode(b64_strip(hash))?;