1mod kdf;
15mod password_construction;
16
17pub const MIN_PASSWORD_LENGTH: u32 = 4;
18pub const MAX_PASSWORD_LENGTH: u32 = 128;
19
20const DEVELOPMENT_M_COST: u32 = 8;
21const DEVELOPMENT_T_COST: u32 = 1;
22const DEVELOPMENT_P_COST: u32 = 1;
23const STANDARD_M_COST: u32 = 65_536;
24const STANDARD_T_COST: u32 = 3;
25const STANDARD_P_COST: u32 = 4;
26const HARDENED_M_COST: u32 = 2_097_152;
27const HARDENED_T_COST: u32 = 1;
28const HARDENED_P_COST: u32 = 4;
29
30#[derive(Clone, Copy, Debug, Eq, PartialEq)]
31pub struct CharacterSets {
36 pub uppercase: bool,
37 pub lowercase: bool,
38 pub digits: bool,
39 pub special: bool,
40}
41
42#[derive(Clone, Copy, Debug, Eq, PartialEq)]
43pub enum CharacterSet {
48 Uppercase,
49 Lowercase,
50 Digits,
51 Special,
52}
53
54impl CharacterSet {
55 pub const ALL: [Self; 4] = [
56 Self::Uppercase,
57 Self::Lowercase,
58 Self::Digits,
59 Self::Special,
60 ];
61
62 pub fn label(self) -> &'static str {
63 match self {
64 Self::Uppercase => "Uppercase",
65 Self::Lowercase => "Lowercase",
66 Self::Digits => "Digits",
67 Self::Special => "Special",
68 }
69 }
70
71 pub fn description(self) -> &'static str {
72 match self {
73 Self::Uppercase => "uppercase letters (A-Z)",
74 Self::Lowercase => "lowercase letters (a-z)",
75 Self::Digits => "digits (0-9)",
76 Self::Special => "special characters (!@#$%^&*)",
77 }
78 }
79}
80
81impl CharacterSets {
82 pub const ALL: Self = Self::new(true, true, true, true);
83
84 pub const fn new(uppercase: bool, lowercase: bool, digits: bool, special: bool) -> Self {
85 Self {
86 uppercase,
87 lowercase,
88 digits,
89 special,
90 }
91 }
92
93 pub(crate) const fn selected_count(self) -> usize {
94 self.uppercase as usize
95 + self.lowercase as usize
96 + self.digits as usize
97 + self.special as usize
98 }
99
100 pub fn with(self, character_set: CharacterSet, selected: bool) -> Self {
101 match character_set {
102 CharacterSet::Uppercase => Self {
103 uppercase: selected,
104 ..self
105 },
106 CharacterSet::Lowercase => Self {
107 lowercase: selected,
108 ..self
109 },
110 CharacterSet::Digits => Self {
111 digits: selected,
112 ..self
113 },
114 CharacterSet::Special => Self {
115 special: selected,
116 ..self
117 },
118 }
119 }
120}
121
122#[derive(Clone, Copy, Debug, Eq, PartialEq)]
123pub struct PasswordSettings {
128 pub length: u32,
129 pub character_sets: CharacterSets,
130}
131
132impl PasswordSettings {
133 pub const fn new(length: u32, character_sets: CharacterSets) -> Self {
134 Self {
135 length,
136 character_sets,
137 }
138 }
139}
140
141#[derive(Clone, Copy, Debug, Eq, PartialEq)]
142pub enum Argon2Profile {
153 Development,
154 Standard,
155 Hardened,
156}
157
158#[derive(Clone, Copy, Debug, Eq, PartialEq)]
159pub struct Argon2Settings {
160 pub m_cost: u32,
161 pub t_cost: u32,
162 pub p_cost: u32,
163}
164
165impl Argon2Settings {
166 pub const fn new(m_cost: u32, t_cost: u32, p_cost: u32) -> Self {
167 Self {
168 m_cost,
169 t_cost,
170 p_cost,
171 }
172 }
173}
174
175impl Argon2Profile {
176 pub const ALL: [Self; 3] = [Self::Development, Self::Standard, Self::Hardened];
177
178 pub fn label(self) -> &'static str {
179 match self {
180 Self::Development => "Development",
181 Self::Standard => "Standard",
182 Self::Hardened => "Hardened",
183 }
184 }
185
186 pub fn description(self) -> &'static str {
187 match self {
188 Self::Development => "8 KiB memory, 1 iteration, 1 lane",
189 Self::Standard => "64 MiB memory, 3 iterations, 4 lanes",
190 Self::Hardened => "2 GiB memory, 1 iteration, 4 lanes",
191 }
192 }
193
194 pub fn settings(self) -> Argon2Settings {
195 match self {
196 Self::Development => {
197 Argon2Settings::new(DEVELOPMENT_M_COST, DEVELOPMENT_T_COST, DEVELOPMENT_P_COST)
198 }
199 Self::Standard => {
200 Argon2Settings::new(STANDARD_M_COST, STANDARD_T_COST, STANDARD_P_COST)
201 }
202 Self::Hardened => {
203 Argon2Settings::new(HARDENED_M_COST, HARDENED_T_COST, HARDENED_P_COST)
204 }
205 }
206 }
207}
208
209pub fn enhance(
237 seed: &[u8],
238 context: &[u8],
239 password_settings: &PasswordSettings,
240 settings: &Argon2Settings,
241) -> String {
242 let salt = kdf::hash_salt(context);
243 let key = kdf::derive_key(seed, &salt, settings);
244 password_construction::construct_password(&key, password_settings)
245}
246
247#[cfg(test)]
248mod tests {
249 use super::{
250 Argon2Profile, DEVELOPMENT_M_COST, DEVELOPMENT_P_COST, DEVELOPMENT_T_COST, HARDENED_M_COST,
251 HARDENED_P_COST, HARDENED_T_COST, STANDARD_M_COST, STANDARD_P_COST, STANDARD_T_COST,
252 };
253
254 #[test]
255 fn argon2_profiles_use_expected_settings() {
256 let development = Argon2Profile::Development.settings();
257 let standard = Argon2Profile::Standard.settings();
258 let hardened = Argon2Profile::Hardened.settings();
259
260 assert_eq!(
261 (development.m_cost, development.t_cost, development.p_cost),
262 (DEVELOPMENT_M_COST, DEVELOPMENT_T_COST, DEVELOPMENT_P_COST)
263 );
264 assert_eq!(
265 (standard.m_cost, standard.t_cost, standard.p_cost),
266 (STANDARD_M_COST, STANDARD_T_COST, STANDARD_P_COST)
267 );
268 assert_eq!(
269 (hardened.m_cost, hardened.t_cost, hardened.p_cost),
270 (HARDENED_M_COST, HARDENED_T_COST, HARDENED_P_COST)
271 );
272 }
273}