use argon2::{Algorithm, Argon2, Version};
use digest::{
Digest, HashMarker, OutputSizeUser,
block_buffer::Eager,
core_api::{
BlockSizeUser, BufferKindUser, CoreProxy, FixedOutputCore, UpdateCore,
},
typenum::{IsLess, Le, NonZero, U256},
};
use rayon::prelude::*;
use std::marker::PhantomData;
use zeroize::Zeroize;
use crate::errors::ArrkeyError;
use crate::keyspace::Keyspace;
use crate::params::Params;
#[derive(Clone, Debug)]
pub struct Attributes<'a, D> {
arr: Vec<Vec<u8>>,
argon2_context: Argon2<'a>,
context_string: Option<String>,
constraint: u8,
_digest: PhantomData<D>,
}
impl<'a, D> Attributes<'a, D>
where
D: Digest + CoreProxy + OutputSizeUser + Sync,
D::Core: Sync
+ HashMarker
+ UpdateCore
+ FixedOutputCore
+ BufferKindUser<BufferKind = Eager>
+ Default
+ Clone
+ BlockSizeUser,
<D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
{
pub fn new(arr: Vec<Vec<u8>>, params: Params) -> Result<Self, ArrkeyError> {
if arr.len() <= 1 {
return Err(ArrkeyError::AttributesTooFew);
}
let argon2_params = argon2::Params::new(
params.m_cost,
params.t_cost,
params.p_cost,
Some(32),
)
.expect("password hashing params already validated");
let argon2_context =
Argon2::new(Algorithm::Argon2id, Version::V0x13, argon2_params);
Ok(Self {
arr,
argon2_context,
context_string: params.context,
constraint: params.constraint,
_digest: PhantomData,
})
}
pub fn harden(&self, salt: &[u8]) -> Result<Keyspace<D>, ArrkeyError> {
let mut arr_salts = self.derive_salts(salt);
let hardened: Vec<[u8; 32]> = self
.arr
.par_iter()
.zip(arr_salts.par_iter())
.map(|(pwd, salt)| -> Result<[u8; 32], ArrkeyError> {
let mut tmp = [0u8; 32];
self.argon2_context
.hash_password_into(pwd, salt, &mut tmp)
.map_err(|_| ArrkeyError::HardenFail)?;
Ok(tmp)
})
.collect::<Result<Vec<_>, _>>()?;
arr_salts.zeroize();
Ok(Keyspace::new(hardened, self.constraint))
}
pub fn len(&self) -> usize {
self.arr.len()
}
fn derive_salts(&self, base_salt: &[u8]) -> Vec<Vec<u8>> {
(0..self.arr.len())
.map(|i| {
let mut hasher = D::new();
hasher.update(base_salt);
if let Some(ctx) = &self.context_string {
hasher.update(ctx.as_bytes());
}
hasher.update(&i.to_le_bytes());
hasher.finalize().to_vec()
})
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use sha2::Sha256;
const ATTR_1: &'static [u8; 18] = b"Pigs on the Wing I";
const ATTR_2: &'static [u8; 4] = b"Dogs";
const ATTR_3: &'static [u8; 27] = b"Pigs (Three Different Ones)";
const ATTR_4: &'static [u8; 5] = b"Sheep";
const ATTR_5: &'static [u8; 19] = b"Pigs on the Wing II";
fn get_arr() -> Vec<Vec<u8>> {
vec![
ATTR_1.to_vec(),
ATTR_2.to_vec(),
ATTR_3.to_vec(),
ATTR_4.to_vec(),
ATTR_5.to_vec(),
]
}
#[test]
fn new_produces_attributes_collection() {
let arr = get_arr();
let result = Attributes::<Sha256>::new(arr, Params::default());
assert!(result.is_ok());
}
#[test]
fn new_too_few() {
let arr_empty: Vec<Vec<u8>> = vec![];
let arr_one: Vec<Vec<u8>> = vec![ATTR_1.to_vec()];
let result = Attributes::<Sha256>::new(arr_empty, Params::default());
assert_eq!(result.unwrap_err(), ArrkeyError::AttributesTooFew);
let result = Attributes::<Sha256>::new(arr_one, Params::default());
assert_eq!(result.unwrap_err(), ArrkeyError::AttributesTooFew);
}
#[test]
fn harden_produces_keyspace() {
let arr = get_arr();
let salt = b"Animals";
let result = Attributes::<Sha256>::new(arr, Params::default());
assert!(result.is_ok());
let attributes = result.unwrap();
let result = attributes.harden(salt);
assert!(result.is_ok());
}
#[test]
fn harden_fails_argon2_failure() {
let huge_attribute = vec![0u8; (u32::MAX as usize) + 1];
let arr = vec![ATTR_1.to_vec(), huge_attribute];
let salt = b"Animals";
let result = Attributes::<Sha256>::new(arr, Params::default());
assert!(result.is_ok());
let attributes = result.unwrap();
let result = attributes.harden(salt);
assert_eq!(result.unwrap_err(), ArrkeyError::HardenFail);
}
#[test]
fn len_returns_collection_length() {
let arr = get_arr();
let arr_len = arr.len();
let result = Attributes::<Sha256>::new(arr, Params::default());
assert!(result.is_ok());
let attributes = result.unwrap();
let len = attributes.len();
assert_eq!(len, arr_len);
}
}