1use argon2::{Algorithm, Argon2, Version};
2use digest::{
3 Digest, HashMarker, OutputSizeUser,
4 block_buffer::Eager,
5 core_api::{
6 BlockSizeUser, BufferKindUser, CoreProxy, FixedOutputCore, UpdateCore,
7 },
8 typenum::{IsLess, Le, NonZero, U256},
9};
10use rayon::prelude::*;
11use std::marker::PhantomData;
12use zeroize::Zeroize;
13
14use crate::errors::ArrkeyError;
15use crate::keyspace::Keyspace;
16use crate::params::Params;
17
18#[derive(Clone, Debug)]
21pub struct Attributes<'a, D> {
22 arr: Vec<Vec<u8>>,
23 argon2_context: Argon2<'a>,
24 context_string: Option<String>,
25 constraint: u8,
26 _digest: PhantomData<D>,
27}
28
29impl<'a, D> Attributes<'a, D>
30where
31 D: Digest + CoreProxy + OutputSizeUser + Sync,
32 D::Core: Sync
33 + HashMarker
34 + UpdateCore
35 + FixedOutputCore
36 + BufferKindUser<BufferKind = Eager>
37 + Default
38 + Clone
39 + BlockSizeUser,
40 <D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
41 Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
42{
43 pub fn new(arr: Vec<Vec<u8>>, params: Params) -> Result<Self, ArrkeyError> {
48 if arr.len() <= 1 {
49 return Err(ArrkeyError::AttributesTooFew);
50 }
51
52 let argon2_params = argon2::Params::new(
53 params.m_cost,
54 params.t_cost,
55 params.p_cost,
56 Some(32),
57 )
58 .expect("password hashing params already validated");
59
60 let argon2_context =
61 Argon2::new(Algorithm::Argon2id, Version::V0x13, argon2_params);
62
63 Ok(Self {
64 arr,
65 argon2_context,
66 context_string: params.context,
67 constraint: params.constraint,
68 _digest: PhantomData,
69 })
70 }
71
72 pub fn harden(&self, salt: &[u8]) -> Result<Keyspace<D>, ArrkeyError> {
83 let mut arr_salts = self.derive_salts(salt);
84
85 let hardened: Vec<[u8; 32]> = self
86 .arr
87 .par_iter()
88 .zip(arr_salts.par_iter())
89 .map(|(pwd, salt)| -> Result<[u8; 32], ArrkeyError> {
90 let mut tmp = [0u8; 32];
91 self.argon2_context
92 .hash_password_into(pwd, salt, &mut tmp)
93 .map_err(|_| ArrkeyError::HardenFail)?;
94 Ok(tmp)
95 })
96 .collect::<Result<Vec<_>, _>>()?;
97 arr_salts.zeroize();
98
99 Ok(Keyspace::new(hardened, self.constraint))
100 }
101
102 pub fn len(&self) -> usize {
104 self.arr.len()
105 }
106
107 fn derive_salts(&self, base_salt: &[u8]) -> Vec<Vec<u8>> {
108 (0..self.arr.len())
109 .map(|i| {
110 let mut hasher = D::new();
111
112 hasher.update(base_salt);
113 if let Some(ctx) = &self.context_string {
114 hasher.update(ctx.as_bytes());
115 }
116 hasher.update(&i.to_le_bytes());
117
118 hasher.finalize().to_vec()
119 })
120 .collect()
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127 use sha2::Sha256;
128
129 const ATTR_1: &'static [u8; 18] = b"Pigs on the Wing I";
130 const ATTR_2: &'static [u8; 4] = b"Dogs";
131 const ATTR_3: &'static [u8; 27] = b"Pigs (Three Different Ones)";
132 const ATTR_4: &'static [u8; 5] = b"Sheep";
133 const ATTR_5: &'static [u8; 19] = b"Pigs on the Wing II";
134
135 fn get_arr() -> Vec<Vec<u8>> {
136 vec![
137 ATTR_1.to_vec(),
138 ATTR_2.to_vec(),
139 ATTR_3.to_vec(),
140 ATTR_4.to_vec(),
141 ATTR_5.to_vec(),
142 ]
143 }
144
145 #[test]
146 fn new_produces_attributes_collection() {
147 let arr = get_arr();
148
149 let result = Attributes::<Sha256>::new(arr, Params::default());
151 assert!(result.is_ok());
152 }
153
154 #[test]
155 fn new_too_few() {
156 let arr_empty: Vec<Vec<u8>> = vec![];
157 let arr_one: Vec<Vec<u8>> = vec![ATTR_1.to_vec()];
158
159 let result = Attributes::<Sha256>::new(arr_empty, Params::default());
161 assert_eq!(result.unwrap_err(), ArrkeyError::AttributesTooFew);
162
163 let result = Attributes::<Sha256>::new(arr_one, Params::default());
165 assert_eq!(result.unwrap_err(), ArrkeyError::AttributesTooFew);
166 }
167
168 #[test]
169 fn harden_produces_keyspace() {
170 let arr = get_arr();
171 let salt = b"Animals";
172
173 let result = Attributes::<Sha256>::new(arr, Params::default());
175 assert!(result.is_ok());
176 let attributes = result.unwrap();
177
178 let result = attributes.harden(salt);
180 assert!(result.is_ok());
181 }
182
183 #[test]
184 fn harden_fails_argon2_failure() {
185 let huge_attribute = vec![0u8; (u32::MAX as usize) + 1];
186 let arr = vec![ATTR_1.to_vec(), huge_attribute];
187 let salt = b"Animals";
188
189 let result = Attributes::<Sha256>::new(arr, Params::default());
191 assert!(result.is_ok());
192 let attributes = result.unwrap();
193
194 let result = attributes.harden(salt);
196 assert_eq!(result.unwrap_err(), ArrkeyError::HardenFail);
197 }
198
199 #[test]
200 fn len_returns_collection_length() {
201 let arr = get_arr();
202 let arr_len = arr.len();
203
204 let result = Attributes::<Sha256>::new(arr, Params::default());
206 assert!(result.is_ok());
207 let attributes = result.unwrap();
208
209 let len = attributes.len();
211 assert_eq!(len, arr_len);
212 }
213}