1#![allow(missing_copy_implementations, reason = "unnecessary")]
4#![allow(clippy::doc_markdown, reason = "false positive")]
5
6use crate::{Algorithm, Argon2, Error, Result, SYNC_POINTS, Version};
7use base64ct::{Base64Unpadded as B64, Encoding};
8use core::str::FromStr;
9
10#[cfg(feature = "password-hash")]
11use {
12 core::fmt::{self, Display},
13 password_hash::phc::{ParamsString, PasswordHash},
14};
15
16#[derive(Clone, Debug, Eq, PartialEq)]
20pub struct Params {
21 m_cost: u32,
25
26 t_cost: u32,
30
31 p_cost: u32,
35
36 keyid: KeyId,
38
39 data: AssociatedData,
41
42 output_len: Option<usize>,
44}
45
46impl Params {
47 pub const DEFAULT_M_COST: u32 = 19 * 1024;
49
50 #[allow(clippy::cast_possible_truncation)]
52 pub const MIN_M_COST: u32 = 2 * SYNC_POINTS as u32; pub const MAX_M_COST: u32 = u32::MAX;
56
57 pub const DEFAULT_T_COST: u32 = 2;
59
60 pub const MIN_T_COST: u32 = 1;
62
63 pub const MAX_T_COST: u32 = u32::MAX;
65
66 pub const DEFAULT_P_COST: u32 = 1;
68
69 pub const MIN_P_COST: u32 = 1;
71
72 pub const MAX_P_COST: u32 = 0xFFFFFF;
74
75 pub const MAX_KEYID_LEN: usize = 8;
77
78 pub const MAX_DATA_LEN: usize = 32;
80
81 pub const DEFAULT_OUTPUT_LEN: usize = 32;
83
84 pub const MIN_OUTPUT_LEN: usize = 4;
86
87 pub const MAX_OUTPUT_LEN: usize = 0xFFFFFFFF;
89
90 pub const DEFAULT: Self = Params {
92 m_cost: Self::DEFAULT_M_COST,
93 t_cost: Self::DEFAULT_T_COST,
94 p_cost: Self::DEFAULT_P_COST,
95 keyid: KeyId {
96 bytes: [0u8; Self::MAX_KEYID_LEN],
97 len: 0,
98 },
99 data: AssociatedData {
100 bytes: [0u8; Self::MAX_DATA_LEN],
101 len: 0,
102 },
103 output_len: None,
104 };
105
106 pub const fn new(
124 m_cost: u32,
125 t_cost: u32,
126 p_cost: u32,
127 output_len: Option<usize>,
128 ) -> Result<Self> {
129 if m_cost < Params::MIN_M_COST {
130 return Err(Error::MemoryTooLittle);
131 }
132
133 if p_cost < Params::MIN_P_COST {
136 return Err(Error::ThreadsTooFew);
137 }
138
139 if p_cost > Params::MAX_P_COST {
140 return Err(Error::ThreadsTooMany);
141 }
142
143 if m_cost < p_cost * 8 {
146 return Err(Error::MemoryTooLittle);
147 }
148
149 if t_cost < Params::MIN_T_COST {
150 return Err(Error::TimeTooSmall);
151 }
152
153 if let Some(len) = output_len {
154 if len < Params::MIN_OUTPUT_LEN {
155 return Err(Error::OutputTooShort);
156 }
157
158 if len > Params::MAX_OUTPUT_LEN {
159 return Err(Error::OutputTooLong);
160 }
161 }
162
163 Ok(Params {
164 m_cost,
165 t_cost,
166 p_cost,
167 keyid: KeyId::EMPTY,
168 data: AssociatedData::EMPTY,
169 output_len,
170 })
171 }
172
173 #[must_use]
177 pub const fn m_cost(&self) -> u32 {
178 self.m_cost
179 }
180
181 #[must_use]
185 pub const fn t_cost(&self) -> u32 {
186 self.t_cost
187 }
188
189 #[must_use]
193 pub const fn p_cost(&self) -> u32 {
194 self.p_cost
195 }
196
197 #[must_use]
209 pub fn keyid(&self) -> &[u8] {
210 self.keyid.as_bytes()
211 }
212
213 #[must_use]
221 pub fn data(&self) -> &[u8] {
222 self.data.as_bytes()
223 }
224
225 #[must_use]
227 pub const fn output_len(&self) -> Option<usize> {
228 self.output_len
229 }
230
231 #[allow(clippy::cast_possible_truncation)]
233 pub(crate) const fn lanes(&self) -> usize {
234 self.p_cost as usize
235 }
236
237 pub(crate) const fn lane_length(&self) -> usize {
239 self.segment_length() * SYNC_POINTS
240 }
241
242 pub(crate) const fn segment_length(&self) -> usize {
246 let m_cost = self.m_cost as usize;
247
248 let memory_blocks = if m_cost < 2 * SYNC_POINTS * self.lanes() {
249 2 * SYNC_POINTS * self.lanes()
250 } else {
251 m_cost
252 };
253
254 memory_blocks / (self.lanes() * SYNC_POINTS)
255 }
256
257 #[must_use]
259 pub const fn block_count(&self) -> usize {
260 self.segment_length() * self.lanes() * SYNC_POINTS
261 }
262}
263
264impl Default for Params {
265 fn default() -> Params {
266 Params::DEFAULT
267 }
268}
269
270macro_rules! param_buf {
271 ($ty:ident, $name:expr, $max_len:expr, $error:expr, $doc:expr) => {
272 #[doc = $doc]
273 #[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)]
274 pub struct $ty {
275 bytes: [u8; Self::MAX_LEN],
277
278 len: usize,
280 }
281
282 impl $ty {
283 pub const MAX_LEN: usize = $max_len;
285
286 #[doc = "Create a new"]
287 #[doc = $name]
288 #[doc = "from a slice.\n"]
289 #[doc = "# Errors"]
290 #[doc = concat!("Returns [`", stringify!($error), "`] in event the provided slice is too long.")]
291 pub fn new(slice: &[u8]) -> Result<Self> {
292 let mut bytes = [0u8; Self::MAX_LEN];
293 let len = slice.len();
294 bytes.get_mut(..len).ok_or($error)?.copy_from_slice(slice);
295 Ok(Self { bytes, len })
296 }
297
298 pub const EMPTY: Self = Self {
300 bytes: [0u8; Self::MAX_LEN],
301 len: 0,
302 };
303
304 #[doc = "Decode"]
305 #[doc = $name]
306 #[doc = " from a B64 string.\n"]
307 #[doc = "# Errors"]
308 #[doc = "Returns [`Error::B64Encoding`] if the providing string couldn't be decoded as B64." ]
309 pub fn from_b64(s: &str) -> Result<Self> {
310 let mut bytes = [0u8; Self::MAX_LEN];
311 let len = B64::decode(s, &mut bytes)?.len();
312 Ok(Self { bytes, len })
313 }
314
315 pub fn as_bytes(&self) -> &[u8] {
317 &self.bytes[..self.len]
318 }
319
320 pub const fn len(&self) -> usize {
322 self.len
323 }
324
325 pub const fn is_empty(&self) -> bool {
327 self.len() == 0
328 }
329 }
330
331 impl AsRef<[u8]> for $ty {
332 fn as_ref(&self) -> &[u8] {
333 self.as_bytes()
334 }
335 }
336
337 impl FromStr for $ty {
338 type Err = Error;
339
340 fn from_str(s: &str) -> Result<Self> {
341 Self::from_b64(s)
342 }
343 }
344
345 impl TryFrom<&[u8]> for $ty {
346 type Error = Error;
347
348 fn try_from(bytes: &[u8]) -> Result<Self> {
349 Self::new(bytes)
350 }
351 }
352 };
353}
354
355param_buf!(
357 KeyId,
358 "KeyId",
359 Params::MAX_KEYID_LEN,
360 Error::KeyIdTooLong,
361 "Key identifier"
362);
363
364param_buf!(
366 AssociatedData,
367 "AssociatedData",
368 Params::MAX_DATA_LEN,
369 Error::AdTooLong,
370 "Associated data"
371);
372
373#[cfg(feature = "password-hash")]
374impl Display for Params {
375 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
376 ParamsString::try_from(self).map_err(|_| fmt::Error)?.fmt(f)
377 }
378}
379
380#[cfg(feature = "password-hash")]
381impl FromStr for Params {
382 type Err = password_hash::Error;
383
384 fn from_str(s: &str) -> password_hash::Result<Self> {
385 let params_string =
386 ParamsString::from_str(s).map_err(|_| password_hash::Error::ParamsInvalid)?;
387 Self::try_from(¶ms_string)
388 }
389}
390
391#[cfg(feature = "password-hash")]
392impl TryFrom<&ParamsString> for Params {
393 type Error = password_hash::Error;
394
395 fn try_from(params: &ParamsString) -> password_hash::Result<Self> {
396 let mut builder = ParamsBuilder::new();
397
398 for (ident, value) in params.iter() {
399 match ident.as_str() {
400 "m" => {
401 builder.m_cost(
402 value
403 .decimal()
404 .map_err(|_| password_hash::Error::ParamInvalid { name: "m" })?,
405 );
406 }
407 "t" => {
408 builder.t_cost(
409 value
410 .decimal()
411 .map_err(|_| password_hash::Error::ParamInvalid { name: "t" })?,
412 );
413 }
414 "p" => {
415 builder.p_cost(
416 value
417 .decimal()
418 .map_err(|_| password_hash::Error::ParamInvalid { name: "p" })?,
419 );
420 }
421 "keyid" => {
422 builder.keyid(
423 value
424 .as_str()
425 .parse()
426 .map_err(|_| password_hash::Error::ParamInvalid { name: "keyid" })?,
427 );
428 }
429 "data" => {
430 builder.data(
431 value
432 .as_str()
433 .parse()
434 .map_err(|_| password_hash::Error::ParamInvalid { name: "data" })?,
435 );
436 }
437 _ => return Err(password_hash::Error::ParamsInvalid),
438 }
439 }
440
441 Ok(builder.build()?)
442 }
443}
444
445#[cfg(feature = "password-hash")]
446impl TryFrom<&PasswordHash> for Params {
447 type Error = password_hash::Error;
448
449 fn try_from(hash: &PasswordHash) -> password_hash::Result<Self> {
450 let mut params = Self::try_from(&hash.params)?;
451
452 if let Some(output) = &hash.hash {
453 params.output_len = Some(output.len());
454 }
455
456 Ok(params)
457 }
458}
459
460#[cfg(feature = "password-hash")]
461impl TryFrom<Params> for ParamsString {
462 type Error = password_hash::Error;
463
464 fn try_from(params: Params) -> password_hash::Result<ParamsString> {
465 ParamsString::try_from(¶ms)
466 }
467}
468
469#[cfg(feature = "password-hash")]
470impl TryFrom<&Params> for ParamsString {
471 type Error = password_hash::Error;
472
473 fn try_from(params: &Params) -> password_hash::Result<ParamsString> {
474 let mut output = ParamsString::new();
475
476 for (name, value) in [
477 ("m", params.m_cost),
478 ("t", params.t_cost),
479 ("p", params.p_cost),
480 ] {
481 output
482 .add_decimal(name, value)
483 .map_err(|_| password_hash::Error::ParamInvalid { name })?;
484 }
485
486 if !params.keyid.is_empty() {
487 output
488 .add_b64_bytes("keyid", params.keyid.as_bytes())
489 .map_err(|_| password_hash::Error::ParamInvalid { name: "keyid" })?;
490 }
491
492 if !params.data.is_empty() {
493 output
494 .add_b64_bytes("data", params.data.as_bytes())
495 .map_err(|_| password_hash::Error::ParamInvalid { name: "keyid" })?;
496 }
497
498 Ok(output)
499 }
500}
501
502#[derive(Clone, Debug, Eq, PartialEq)]
504pub struct ParamsBuilder {
505 m_cost: u32,
506 t_cost: u32,
507 p_cost: u32,
508 keyid: Option<KeyId>,
509 data: Option<AssociatedData>,
510 output_len: Option<usize>,
511}
512
513impl ParamsBuilder {
514 #[must_use]
516 pub const fn new() -> Self {
517 Self::DEFAULT
518 }
519
520 pub fn m_cost(&mut self, m_cost: u32) -> &mut Self {
522 self.m_cost = m_cost;
523 self
524 }
525
526 pub fn t_cost(&mut self, t_cost: u32) -> &mut Self {
528 self.t_cost = t_cost;
529 self
530 }
531
532 pub fn p_cost(&mut self, p_cost: u32) -> &mut Self {
534 self.p_cost = p_cost;
535 self
536 }
537
538 pub fn keyid(&mut self, keyid: KeyId) -> &mut Self {
540 self.keyid = Some(keyid);
541 self
542 }
543
544 pub fn data(&mut self, data: AssociatedData) -> &mut Self {
546 self.data = Some(data);
547 self
548 }
549
550 pub fn output_len(&mut self, len: usize) -> &mut Self {
552 self.output_len = Some(len);
553 self
554 }
555
556 pub const fn build(&self) -> Result<Params> {
565 let mut params = match Params::new(self.m_cost, self.t_cost, self.p_cost, self.output_len) {
566 Ok(params) => params,
567 Err(err) => return Err(err),
568 };
569
570 if let Some(keyid) = self.keyid {
571 params.keyid = keyid;
572 }
573
574 if let Some(data) = self.data {
575 params.data = data;
576 };
577
578 Ok(params)
579 }
580
581 pub fn context(&self, algorithm: Algorithm, version: Version) -> Result<Argon2<'_>> {
587 Ok(Argon2::new(algorithm, version, self.build()?))
588 }
589
590 pub const DEFAULT: ParamsBuilder = {
592 let params = Params::DEFAULT;
593 Self {
594 m_cost: params.m_cost,
595 t_cost: params.t_cost,
596 p_cost: params.p_cost,
597 keyid: None,
598 data: None,
599 output_len: params.output_len,
600 }
601 };
602}
603
604impl Default for ParamsBuilder {
605 fn default() -> Self {
606 Self::DEFAULT
607 }
608}
609
610impl TryFrom<ParamsBuilder> for Params {
611 type Error = Error;
612
613 fn try_from(builder: ParamsBuilder) -> Result<Params> {
614 builder.build()
615 }
616}
617
618#[cfg(all(test, feature = "alloc", feature = "password-hash"))]
619mod tests {
620
621 use super::*;
622
623 #[test]
624 fn params_builder_bad_values() {
625 assert_eq!(
626 ParamsBuilder::new().m_cost(Params::MIN_M_COST - 1).build(),
627 Err(Error::MemoryTooLittle)
628 );
629 assert_eq!(
630 ParamsBuilder::new().t_cost(Params::MIN_T_COST - 1).build(),
631 Err(Error::TimeTooSmall)
632 );
633 assert_eq!(
634 ParamsBuilder::new().p_cost(Params::MIN_P_COST - 1).build(),
635 Err(Error::ThreadsTooFew)
636 );
637 assert_eq!(
638 ParamsBuilder::new()
639 .m_cost(Params::DEFAULT_P_COST * 8 - 1)
640 .build(),
641 Err(Error::MemoryTooLittle)
642 );
643 assert_eq!(
644 ParamsBuilder::new()
645 .m_cost((Params::MAX_P_COST + 1) * 8)
646 .p_cost(Params::MAX_P_COST + 1)
647 .build(),
648 Err(Error::ThreadsTooMany)
649 );
650 }
651
652 #[test]
653 fn associated_data_too_long() {
654 let ret = AssociatedData::new(&[0u8; Params::MAX_DATA_LEN + 1]);
655 assert_eq!(ret, Err(Error::AdTooLong));
656 }
657
658 #[test]
659 fn keyid_too_long() {
660 let ret = KeyId::new(&[0u8; Params::MAX_KEYID_LEN + 1]);
661 assert_eq!(ret, Err(Error::KeyIdTooLong));
662 }
663}