1#![deny(warnings)]
2#![warn(unused_extern_crates)]
3#![deny(clippy::todo)]
4#![deny(clippy::unimplemented)]
5#![deny(clippy::unwrap_used)]
6#![deny(clippy::expect_used)]
7#![deny(clippy::panic)]
8#![deny(clippy::await_holding_lock)]
9#![deny(clippy::needless_pass_by_value)]
10#![deny(clippy::trivially_copy_pass_by_ref)]
11#![deny(clippy::unreachable)]
12
13use base64::engine::general_purpose;
14use base64::engine::GeneralPurpose;
15use base64::{alphabet, Engine};
16use base64urlsafedata::Base64UrlSafeData;
17use crypto_glue::{
18 argon2::{Algorithm, Argon2, Params, PasswordHash, Version},
19 pbkdf2::pbkdf2_hmac,
20 s256::Sha256,
21 s512::Sha512,
22 sha1::Sha1,
23 traits::Digest,
24};
25use kanidm_hsm_crypto::{provider::TpmHmacS256, structures::HmacS256Key};
26use md4::Md4;
27use rand::RngExt;
28use serde::{Deserialize, Serialize};
29use std::fmt;
30use std::fmt::Display;
31use std::num::ParseIntError;
32use std::time::{Duration, Instant};
33use tracing::{debug, error, warn};
34
35mod crypt_md5;
36
37pub use sha2;
38
39const PBKDF2_SALT_LEN: usize = 24;
41
42pub const PBKDF2_MIN_NIST_SALT_LEN: usize = 14;
43
44pub const PBKDF2_MIN_NIST_COST: u32 = 10_000;
46pub const PBKDF2_DEFAULT_COST: u32 = 600_000;
48
49const PBKDF2_KEY_LEN: usize = 32;
51const PBKDF2_MIN_NIST_KEY_LEN: usize = 32;
52const PBKDF2_SHA1_MIN_KEY_LEN: usize = 19;
53
54const DS_SHA1_HASH_LEN: usize = 20;
55const DS_SHA256_HASH_LEN: usize = 32;
56const DS_SHA512_HASH_LEN: usize = 64;
57
58const ARGON2_VERSION: u32 = 19;
60const ARGON2_SALT_LEN: usize = 16;
61const ARGON2_KEY_LEN: usize = 32;
63const ARGON2_MIN_RAM_KIB: u32 = 8 * 1024;
65const ARGON2_MAX_RAM_KIB: u32 = 64 * 1024;
66const ARGON2_TCOST_RAM_ITER_KIB: u32 = 12 * 1024;
70const ARGON2_MIN_T_COST: u32 = 2;
71const ARGON2_MAX_T_COST: u32 = 16;
72const ARGON2_MAX_P_COST: u32 = 1;
73
74#[derive(Clone, Debug)]
75pub enum CryptoError {
76 Hsm,
77 HsmContextMissing,
78 OpenSSL(u64),
79 Md4Disabled,
80 Argon2,
81 Argon2Version,
82 Argon2Parameters,
83 Crypt,
84 InvalidServerName,
85}
86
87#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)]
88#[allow(non_camel_case_types)]
89pub enum DbPasswordV1 {
90 TPM_ARGON2ID {
91 m: u32,
92 t: u32,
93 p: u32,
94 v: u32,
95 s: Base64UrlSafeData,
96 k: Base64UrlSafeData,
97 },
98 ARGON2ID {
99 m: u32,
100 t: u32,
101 p: u32,
102 v: u32,
103 s: Base64UrlSafeData,
104 k: Base64UrlSafeData,
105 },
106 PBKDF2(u32, Vec<u8>, Vec<u8>),
107 PBKDF2_SHA1(u32, Vec<u8>, Vec<u8>),
108 PBKDF2_SHA512(u32, Vec<u8>, Vec<u8>),
109 SHA1(Vec<u8>),
110 SSHA1(Vec<u8>, Vec<u8>),
111 SHA256(Vec<u8>),
112 SSHA256(Vec<u8>, Vec<u8>),
113 SHA512(Vec<u8>),
114 SSHA512(Vec<u8>, Vec<u8>),
115 NT_MD4(Vec<u8>),
116 CRYPT_MD5 {
117 s: Base64UrlSafeData,
118 h: Base64UrlSafeData,
119 },
120 CRYPT_SHA256 {
121 h: String,
122 },
123 CRYPT_SHA512 {
124 h: String,
125 },
126}
127
128impl fmt::Debug for DbPasswordV1 {
129 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
130 match self {
131 DbPasswordV1::TPM_ARGON2ID { .. } => write!(f, "TPM_ARGON2ID"),
132 DbPasswordV1::ARGON2ID { .. } => write!(f, "ARGON2ID"),
133 DbPasswordV1::PBKDF2(_, _, _) => write!(f, "PBKDF2"),
134 DbPasswordV1::PBKDF2_SHA1(_, _, _) => write!(f, "PBKDF2_SHA1"),
135 DbPasswordV1::PBKDF2_SHA512(_, _, _) => write!(f, "PBKDF2_SHA512"),
136 DbPasswordV1::SHA1(_) => write!(f, "SHA1"),
137 DbPasswordV1::SSHA1(_, _) => write!(f, "SSHA1"),
138 DbPasswordV1::SHA256(_) => write!(f, "SHA256"),
139 DbPasswordV1::SSHA256(_, _) => write!(f, "SSHA256"),
140 DbPasswordV1::SHA512(_) => write!(f, "SHA512"),
141 DbPasswordV1::SSHA512(_, _) => write!(f, "SSHA512"),
142 DbPasswordV1::NT_MD4(_) => write!(f, "NT_MD4"),
143 DbPasswordV1::CRYPT_MD5 { .. } => write!(f, "CRYPT_MD5"),
144 DbPasswordV1::CRYPT_SHA256 { .. } => write!(f, "CRYPT_SHA256"),
145 DbPasswordV1::CRYPT_SHA512 { .. } => write!(f, "CRYPT_SHA512"),
146 }
147 }
148}
149
150#[derive(Debug)]
151pub struct CryptoPolicy {
152 pub(crate) pbkdf2_cost: u32,
153 pub(crate) argon2id_params: Params,
156}
157
158impl CryptoPolicy {
159 pub fn minimum() -> Self {
160 CryptoPolicy {
161 pbkdf2_cost: PBKDF2_MIN_NIST_COST,
162 argon2id_params: Params::default(),
163 }
164 }
165
166 pub fn danger_test_minimum() -> Self {
167 CryptoPolicy {
168 pbkdf2_cost: 1000,
169 argon2id_params: Params::new(
170 Params::MIN_M_COST,
171 Params::MIN_T_COST,
172 Params::MIN_P_COST,
173 None,
174 )
175 .unwrap_or_default(),
176 }
177 }
178
179 pub fn time_target(target_time: Duration) -> Self {
180 let mut m_cost = ARGON2_MIN_RAM_KIB;
215 let mut t_cost = ARGON2_MIN_T_COST;
216 let p_cost = ARGON2_MAX_P_COST;
217
218 loop {
220 let params = if let Ok(p) = Params::new(m_cost, t_cost, p_cost, None) {
221 p
222 } else {
223 error!(
225 ?m_cost,
226 ?t_cost,
227 ?p_cost,
228 "Parameters were not valid for argon2"
229 );
230 break;
231 };
232
233 if let Some(ubt) = Password::bench_argon2id(params) {
234 debug!("{}ns - t_cost {} m_cost {}", ubt.as_nanos(), t_cost, m_cost);
235 if ubt < target_time {
237 let m_mult = target_time
238 .as_nanos()
239 .checked_div(ubt.as_nanos())
240 .unwrap_or(1);
241 if m_cost < ARGON2_MAX_RAM_KIB {
242 let m_adjust = if m_mult >= 2 {
244 m_cost * u32::try_from(m_mult).unwrap_or(2)
246 } else {
247 m_cost + 1024
249 };
250
251 m_cost = if m_adjust > ARGON2_MAX_RAM_KIB {
252 ARGON2_MAX_RAM_KIB
253 } else {
254 m_adjust
255 };
256 continue;
257 } else if t_cost < ARGON2_MAX_T_COST {
258 if m_mult >= 2 {
260 let t_adjust = t_cost * u32::try_from(m_mult).unwrap_or(2);
262 t_cost = t_adjust.clamp(ARGON2_MIN_T_COST, ARGON2_MAX_T_COST);
263 } else {
264 t_cost += 1;
270 let m_adjust = m_cost
272 .checked_sub(ARGON2_TCOST_RAM_ITER_KIB)
273 .unwrap_or(ARGON2_MIN_RAM_KIB);
274
275 m_cost = m_adjust.clamp(ARGON2_MIN_RAM_KIB, ARGON2_MAX_RAM_KIB);
277 }
278 continue;
279 } else {
280 warn!("Argon2 parameters have hit their maximums - this may be a bug!");
282 break;
283 }
284 } else {
285 break;
287 }
288 } else {
289 error!("Unable to perform bench of argon2id, stopping benchmark");
290 break;
291 }
292 }
293
294 let argon2id_params = Params::new(m_cost, t_cost, p_cost, None)
295 .unwrap_or_default();
297
298 let p = CryptoPolicy {
299 pbkdf2_cost: PBKDF2_DEFAULT_COST,
300 argon2id_params,
301 };
302 debug!(argon2id_m = %p.argon2id_params.m_cost(), argon2id_p = %p.argon2id_params.p_cost(), argon2id_t = %p.argon2id_params.t_cost(), );
303 p
304 }
305}
306
307#[derive(Clone, Debug, PartialEq)]
311#[allow(non_camel_case_types)]
312enum Kdf {
313 TPM_ARGON2ID {
314 m_cost: u32,
315 t_cost: u32,
316 p_cost: u32,
317 version: u32,
318 salt: Vec<u8>,
319 key: Vec<u8>,
320 },
321 ARGON2ID {
323 m_cost: u32,
324 t_cost: u32,
325 p_cost: u32,
326 version: u32,
327 salt: Vec<u8>,
328 key: Vec<u8>,
329 },
330 PBKDF2(u32, Vec<u8>, Vec<u8>),
332
333 PBKDF2_SHA1(u32, Vec<u8>, Vec<u8>),
336 PBKDF2_SHA512(u32, Vec<u8>, Vec<u8>),
338 SHA1(Vec<u8>),
340 SSHA1(Vec<u8>, Vec<u8>),
341 SHA256(Vec<u8>),
342 SSHA256(Vec<u8>, Vec<u8>),
343 SHA512(Vec<u8>),
344 SSHA512(Vec<u8>, Vec<u8>),
345 NT_MD4(Vec<u8>),
347 CRYPT_MD5 {
348 s: Vec<u8>,
349 h: Vec<u8>,
350 },
351 CRYPT_SHA256 {
352 h: String,
353 },
354 CRYPT_SHA512 {
355 h: String,
356 },
357}
358
359#[derive(Debug, Clone, PartialEq)]
360pub enum PasswordError {
361 Base64Decoding,
362 InvalidFormat,
363 InvalidKeyLength,
364 InvalidLength,
365 InvalidSaltLength,
366 UnsupportedAlgorithm(String),
368 NoDecoderFound(String),
370 ParsingFailed,
371}
372
373impl Display for PasswordError {
374 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
375 match self {
376 PasswordError::Base64Decoding => write!(f, "Base64 decoding failed"),
377 PasswordError::InvalidFormat => write!(f, "Invalid password format"),
378 PasswordError::InvalidKeyLength => write!(f, "Invalid key length for password"),
379 PasswordError::InvalidLength => write!(f, "Invalid length for password"),
380 PasswordError::InvalidSaltLength => write!(f, "Invalid salt length for password"),
381 PasswordError::UnsupportedAlgorithm(alg) => {
382 write!(f, "Unsupported algorithm: {alg}")
383 }
384 PasswordError::NoDecoderFound(hint) => {
385 write!(f, "No decoder found for password in this format - input started with '{hint}' - please report it upstream")
386 }
387 PasswordError::ParsingFailed => write!(f, "Parsing of password failed"),
388 }
389 }
390}
391
392impl From<ParseIntError> for PasswordError {
393 fn from(_err: ParseIntError) -> Self {
394 PasswordError::ParsingFailed
395 }
396}
397
398impl From<base64::DecodeError> for PasswordError {
399 fn from(_err: base64::DecodeError) -> Self {
400 PasswordError::Base64Decoding
401 }
402}
403
404#[derive(Clone, Debug, PartialEq)]
405pub struct Password {
406 material: Kdf,
407}
408
409impl TryFrom<DbPasswordV1> for Password {
410 type Error = ();
411
412 fn try_from(value: DbPasswordV1) -> Result<Self, Self::Error> {
413 match value {
414 DbPasswordV1::TPM_ARGON2ID { m, t, p, v, s, k } => Ok(Password {
415 material: Kdf::TPM_ARGON2ID {
416 m_cost: m,
417 t_cost: t,
418 p_cost: p,
419 version: v,
420 salt: s.into(),
421 key: k.into(),
422 },
423 }),
424 DbPasswordV1::ARGON2ID { m, t, p, v, s, k } => Ok(Password {
425 material: Kdf::ARGON2ID {
426 m_cost: m,
427 t_cost: t,
428 p_cost: p,
429 version: v,
430 salt: s.into(),
431 key: k.into(),
432 },
433 }),
434 DbPasswordV1::PBKDF2(c, s, h) => Ok(Password {
435 material: Kdf::PBKDF2(c, s, h),
436 }),
437 DbPasswordV1::PBKDF2_SHA1(c, s, h) => Ok(Password {
438 material: Kdf::PBKDF2_SHA1(c, s, h),
439 }),
440 DbPasswordV1::PBKDF2_SHA512(c, s, h) => Ok(Password {
441 material: Kdf::PBKDF2_SHA512(c, s, h),
442 }),
443 DbPasswordV1::SHA1(h) => Ok(Password {
444 material: Kdf::SHA1(h),
445 }),
446 DbPasswordV1::SSHA1(s, h) => Ok(Password {
447 material: Kdf::SSHA1(s, h),
448 }),
449 DbPasswordV1::SHA256(h) => Ok(Password {
450 material: Kdf::SHA256(h),
451 }),
452 DbPasswordV1::SSHA256(s, h) => Ok(Password {
453 material: Kdf::SSHA256(s, h),
454 }),
455 DbPasswordV1::SHA512(h) => Ok(Password {
456 material: Kdf::SHA512(h),
457 }),
458 DbPasswordV1::SSHA512(s, h) => Ok(Password {
459 material: Kdf::SSHA512(s, h),
460 }),
461 DbPasswordV1::NT_MD4(h) => Ok(Password {
462 material: Kdf::NT_MD4(h),
463 }),
464 DbPasswordV1::CRYPT_MD5 { s, h } => Ok(Password {
465 material: Kdf::CRYPT_MD5 {
466 s: s.into(),
467 h: h.into(),
468 },
469 }),
470 DbPasswordV1::CRYPT_SHA256 { h } => Ok(Password {
471 material: Kdf::CRYPT_SHA256 { h },
472 }),
473 DbPasswordV1::CRYPT_SHA512 { h } => Ok(Password {
474 material: Kdf::CRYPT_SHA256 { h },
475 }),
476 }
477 }
478}
479
480macro_rules! ab64_to_b64 {
486 ($ab64:expr) => {{
487 let mut s = $ab64.replace(".", "+");
488 match s.len() & 3 {
489 0 => {
490 }
492 1 => {
493 }
495 2 => s.push_str("=="),
496 3 => s.push_str("="),
497 _ => unreachable!(),
498 }
499 s
500 }};
501}
502
503fn parse_django_password(value: &str) -> Result<Password, PasswordError> {
505 let django_pbkdf: Vec<&str> = value.split('$').collect();
506 if django_pbkdf.len() != 4 {
507 return Err(PasswordError::InvalidLength);
508 }
509 let cost = django_pbkdf[1];
511 let salt = django_pbkdf[2];
512 let hash = django_pbkdf[3];
513 let c = cost.parse::<u32>()?;
514 let s: Vec<_> = salt.as_bytes().to_vec();
515 let h = general_purpose::STANDARD.decode(hash)?;
516 if h.len() < PBKDF2_MIN_NIST_KEY_LEN {
517 Err(PasswordError::InvalidLength)
518 } else {
519 Ok(Password {
520 material: Kdf::PBKDF2(c, s, h),
521 })
522 }
523}
524
525fn parse_ipanthash(hash_value: &str) -> Result<Password, PasswordError> {
526 let h = base64::engine::general_purpose::URL_SAFE_NO_PAD
528 .decode(hash_value)
529 .or_else(|_| base64::engine::general_purpose::URL_SAFE.decode(hash_value))?;
530
531 Ok(Password {
532 material: Kdf::NT_MD4(h),
533 })
534}
535
536fn parse_sambantpassword(hash_value: &str) -> Result<Password, PasswordError> {
537 let h = hex::decode(hash_value).map_err(|_| PasswordError::ParsingFailed)?;
538 Ok(Password {
539 material: Kdf::NT_MD4(h),
540 })
541}
542
543fn parse_crypt(hash_value: &str) -> Result<Password, PasswordError> {
544 if let Some(crypt_md5_phc) = hash_value.strip_prefix("$1$") {
545 let (salt, hash) = crypt_md5_phc
546 .split_once('$')
547 .ok_or(PasswordError::ParsingFailed)?;
548
549 let s = salt.as_bytes().to_vec();
552 let h = hash.as_bytes().to_vec();
553
554 Ok(Password {
555 material: Kdf::CRYPT_MD5 { s, h },
556 })
557 } else if hash_value.starts_with("$5$") {
558 Ok(Password {
559 material: Kdf::CRYPT_SHA256 {
560 h: hash_value.to_string(),
561 },
562 })
563 } else if hash_value.starts_with("$6$") {
564 Ok(Password {
565 material: Kdf::CRYPT_SHA512 {
566 h: hash_value.to_string(),
567 },
568 })
569 } else {
570 Err(PasswordError::UnsupportedAlgorithm("crypt".to_string()))
571 }
572}
573
574fn parse_pbkdf2(hash_format: &str, hash_value: &str) -> Result<Password, PasswordError> {
575 let ol_pbkdf: Vec<&str> = hash_value.split('$').collect();
576 if ol_pbkdf.len() != 3 {
577 warn!("oldap pbkdf2 found but invalid number of elements?");
578 return Err(PasswordError::InvalidLength);
579 }
580
581 let cost = ol_pbkdf[0];
582 let salt = ol_pbkdf[1];
583 let hash = ol_pbkdf[2];
584
585 let c: u32 = cost.parse()?;
586
587 let s = ab64_to_b64!(salt);
588 let base64_decoder_config =
589 general_purpose::GeneralPurposeConfig::new().with_decode_allow_trailing_bits(true);
590 let base64_decoder = GeneralPurpose::new(&alphabet::STANDARD, base64_decoder_config);
591 let s = base64_decoder.decode(s).inspect_err(|e| {
592 error!(?e, "Invalid base64 in oldap pbkdf2-sha1");
593 })?;
594
595 let h = ab64_to_b64!(hash);
596 let h = base64_decoder.decode(h).inspect_err(|e| {
597 error!(?e, "Invalid base64 in oldap pbkdf2-sha1");
598 })?;
599
600 match hash_format {
601 "pbkdf2" | "pbkdf2-sha1" => {
603 if h.len() < PBKDF2_SHA1_MIN_KEY_LEN {
604 Err(PasswordError::InvalidKeyLength)
605 } else {
606 Ok(Password {
607 material: Kdf::PBKDF2_SHA1(c, s, h),
608 })
609 }
610 }
611 "pbkdf2-sha256" => {
612 if h.len() < PBKDF2_MIN_NIST_KEY_LEN {
613 Err(PasswordError::InvalidKeyLength)
614 } else {
615 Ok(Password {
616 material: Kdf::PBKDF2(c, s, h),
617 })
618 }
619 }
620 "pbkdf2-sha512" => {
621 if h.len() < PBKDF2_MIN_NIST_KEY_LEN {
622 Err(PasswordError::InvalidKeyLength)
623 } else {
624 Ok(Password {
625 material: Kdf::PBKDF2_SHA512(c, s, h),
626 })
627 }
628 }
629 _ => Err(PasswordError::UnsupportedAlgorithm(hash_format.to_string())),
630 }
631}
632
633fn parse_argon(hash_value: &str) -> Result<Password, PasswordError> {
634 match PasswordHash::try_from(hash_value) {
635 Ok(PasswordHash {
636 algorithm,
637 version,
638 params,
639 salt,
640 hash,
641 }) => {
642 if algorithm.as_str() != "argon2id" {
643 error!(alg = %algorithm.as_str(), "Only argon2id is supported");
644 return Err(PasswordError::UnsupportedAlgorithm(algorithm.to_string()));
645 }
646
647 let version = version.unwrap_or(ARGON2_VERSION);
648 let version: Version = version.try_into().map_err(|_| {
649 error!("Failed to convert {} to valid argon2id version", version);
650 PasswordError::ParsingFailed
651 })?;
652
653 let m_cost = params.get_decimal("m").ok_or_else(|| {
654 error!("Failed to access m_cost parameter");
655 PasswordError::ParsingFailed
656 })?;
657
658 let t_cost = params.get_decimal("t").ok_or_else(|| {
659 error!("Failed to access t_cost parameter");
660 PasswordError::ParsingFailed
661 })?;
662
663 let p_cost = params.get_decimal("p").ok_or_else(|| {
664 error!("Failed to access p_cost parameter");
665 PasswordError::ParsingFailed
666 })?;
667
668 let salt = salt
669 .and_then(|s| {
670 let mut salt_arr = [0u8; 64];
671 s.decode_b64(&mut salt_arr)
672 .ok()
673 .map(|salt_bytes| salt_bytes.to_owned())
674 })
675 .ok_or_else(|| {
676 error!("Failed to access salt");
677 PasswordError::ParsingFailed
678 })?;
679
680 let key = hash.map(|h| h.as_bytes().into()).ok_or_else(|| {
681 error!("Failed to access key");
682 PasswordError::ParsingFailed
683 })?;
684
685 Ok(Password {
686 material: Kdf::ARGON2ID {
687 m_cost,
688 t_cost,
689 p_cost,
690 version: version as u32,
691 salt,
692 key,
693 },
694 })
695 }
696 Err(e) => {
697 error!(?e, "Invalid argon2 PHC string");
698 Err(PasswordError::ParsingFailed)
699 }
700 }
701}
702
703impl TryFrom<&str> for Password {
704 type Error = PasswordError;
705
706 fn try_from(value: &str) -> Result<Self, Self::Error> {
709 if value.starts_with("pbkdf2_sha256$") {
710 parse_django_password(value)
711 } else if let Some(hash_value) = value.strip_prefix("ipaNTHash: ") {
712 parse_ipanthash(hash_value)
713 } else if let Some(hash_value) = value.strip_prefix("sambaNTPassword: ") {
714 parse_sambantpassword(hash_value)
715 } else if value.starts_with("{") {
716 let (hash_format, hash_value) = match value.split_once('}') {
721 Some((format, value)) => (
722 format.strip_prefix('{').unwrap_or(format).to_lowercase(),
723 value,
724 ),
725 None => {
726 return Err(PasswordError::InvalidFormat);
727 }
728 };
729
730 match hash_format.as_str() {
731 "pbkdf2" | "pbkdf2-sha1" | "pbkdf2-sha256" | "pbkdf2-sha512" => {
733 parse_pbkdf2(&hash_format, hash_value)
734 }
735
736 "pbkdf2_sha256" => Err(PasswordError::InvalidFormat),
738
739 "argon2" => parse_argon(hash_value),
740 "crypt" => parse_crypt(hash_value),
741 "sha" => {
742 let h = general_purpose::STANDARD.decode(hash_value)?;
743 if h.len() != DS_SHA1_HASH_LEN {
744 return Err(PasswordError::InvalidSaltLength);
745 }
746 Ok(Password {
747 material: Kdf::SHA1(h.to_vec()),
748 })
749 }
750 "ssha" => {
751 let sh = general_purpose::STANDARD.decode(hash_value)?;
752 let (h, s) = sh
753 .split_at_checked(DS_SHA1_HASH_LEN)
754 .ok_or(PasswordError::InvalidLength)?;
755
756 Ok(Password {
757 material: Kdf::SSHA1(s.to_vec(), h.to_vec()),
758 })
759 }
760 "sha256" => {
761 let h = general_purpose::STANDARD.decode(hash_value)?;
762 if h.len() != DS_SHA256_HASH_LEN {
763 return Err(PasswordError::InvalidSaltLength);
764 }
765 Ok(Password {
766 material: Kdf::SHA256(h.to_vec()),
767 })
768 }
769 "ssha256" => {
770 let sh = general_purpose::STANDARD.decode(hash_value)?;
771 let (h, s) = sh
772 .split_at_checked(DS_SHA256_HASH_LEN)
773 .ok_or(PasswordError::InvalidLength)?;
774 Ok(Password {
775 material: Kdf::SSHA256(s.to_vec(), h.to_vec()),
776 })
777 }
778 "sha512" => {
779 let h = general_purpose::STANDARD.decode(hash_value)?;
780 if h.len() != DS_SHA512_HASH_LEN {
781 return Err(PasswordError::InvalidSaltLength);
782 }
783 Ok(Password {
784 material: Kdf::SHA512(h.to_vec()),
785 })
786 }
787 "ssha512" => {
788 let sh = general_purpose::STANDARD.decode(hash_value)?;
789 if sh.len() <= DS_SHA512_HASH_LEN {
790 return Err(PasswordError::InvalidSaltLength);
791 }
792 let (h, s) = sh
793 .split_at_checked(DS_SHA512_HASH_LEN)
794 .ok_or(PasswordError::InvalidLength)?;
795 Ok(Password {
796 material: Kdf::SSHA512(s.to_vec(), h.to_vec()),
797 })
798 }
799 _ => Err(PasswordError::NoDecoderFound(hash_format)),
800 }
801 } else {
802 Err(PasswordError::NoDecoderFound(
803 value.chars().take(5).collect(),
804 ))
805 }
806 }
807}
808
809impl Password {
810 fn bench_argon2id(params: Params) -> Option<Duration> {
811 let mut rng = rand::rng();
812 let salt: Vec<u8> = (0..ARGON2_SALT_LEN).map(|_| rng.random()).collect();
813 let input: Vec<u8> = (0..ARGON2_SALT_LEN).map(|_| rng.random()).collect();
814 let mut key: Vec<u8> = (0..ARGON2_KEY_LEN).map(|_| 0).collect();
815
816 let argon = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
817
818 let start = Instant::now();
819 argon
820 .hash_password_into(input.as_slice(), salt.as_slice(), key.as_mut_slice())
821 .ok()?;
822 let end = Instant::now();
823
824 end.checked_duration_since(start)
825 }
826
827 pub fn new_pbkdf2(policy: &CryptoPolicy, cleartext: &str) -> Result<Self, CryptoError> {
828 let pbkdf2_cost = policy.pbkdf2_cost;
829 let mut rng = rand::rng();
830 let salt: Vec<u8> = (0..PBKDF2_SALT_LEN).map(|_| rng.random()).collect();
831 let mut key: Vec<u8> = (0..PBKDF2_KEY_LEN).map(|_| 0).collect();
832
833 pbkdf2_hmac::<Sha256>(
834 cleartext.as_bytes(),
835 salt.as_slice(),
836 pbkdf2_cost,
837 key.as_mut_slice(),
838 );
839
840 Ok(Password {
842 material: Kdf::PBKDF2(pbkdf2_cost, salt, key),
843 })
844 }
845
846 pub fn new_argon2id(policy: &CryptoPolicy, cleartext: &str) -> Result<Self, CryptoError> {
847 let version = Version::V0x13;
848
849 let argon = Argon2::new(Algorithm::Argon2id, version, policy.argon2id_params.clone());
850
851 let mut rng = rand::rng();
852 let salt: Vec<u8> = (0..ARGON2_SALT_LEN).map(|_| rng.random()).collect();
853 let mut key: Vec<u8> = (0..ARGON2_KEY_LEN).map(|_| 0).collect();
854
855 argon
856 .hash_password_into(cleartext.as_bytes(), salt.as_slice(), key.as_mut_slice())
857 .map(|()| Kdf::ARGON2ID {
858 m_cost: policy.argon2id_params.m_cost(),
859 t_cost: policy.argon2id_params.t_cost(),
860 p_cost: policy.argon2id_params.p_cost(),
861 version: version as u32,
862 salt,
863 key,
864 })
865 .map_err(|_| CryptoError::Argon2)
866 .map(|material| Password { material })
867 }
868
869 pub fn new_argon2id_hsm(
870 policy: &CryptoPolicy,
871 cleartext: &str,
872 hsm: &mut dyn TpmHmacS256,
873 hmac_key: &HmacS256Key,
874 ) -> Result<Self, CryptoError> {
875 let version = Version::V0x13;
876
877 let argon = Argon2::new(Algorithm::Argon2id, version, policy.argon2id_params.clone());
878
879 let mut rng = rand::rng();
880 let salt: Vec<u8> = (0..ARGON2_SALT_LEN).map(|_| rng.random()).collect();
881 let mut check_key: Vec<u8> = (0..ARGON2_KEY_LEN).map(|_| 0).collect();
882
883 argon
884 .hash_password_into(
885 cleartext.as_bytes(),
886 salt.as_slice(),
887 check_key.as_mut_slice(),
888 )
889 .map_err(|_| CryptoError::Argon2)
890 .and_then(|()| {
891 hsm.hmac_s256(hmac_key, &check_key)
892 .map_err(|err| {
893 error!(?err, "hsm error");
894 CryptoError::Hsm
895 })
896 .map(|hmac_output| hmac_output.into_bytes().to_vec())
897 })
898 .map(|key| Kdf::TPM_ARGON2ID {
899 m_cost: policy.argon2id_params.m_cost(),
900 t_cost: policy.argon2id_params.t_cost(),
901 p_cost: policy.argon2id_params.p_cost(),
902 version: version as u32,
903 salt,
904 key,
905 })
906 .map(|material| Password { material })
907 }
908
909 #[inline]
910 pub fn new(policy: &CryptoPolicy, cleartext: &str) -> Result<Self, CryptoError> {
911 Self::new_argon2id(policy, cleartext)
912 }
913
914 pub fn verify(&self, cleartext: &str) -> Result<bool, CryptoError> {
915 self.verify_ctx(cleartext, None)
916 }
917
918 pub fn verify_ctx(
919 &self,
920 cleartext: &str,
921 hsm: Option<(&mut dyn TpmHmacS256, &HmacS256Key)>,
922 ) -> Result<bool, CryptoError> {
923 match (&self.material, hsm) {
924 (
925 Kdf::TPM_ARGON2ID {
926 m_cost,
927 t_cost,
928 p_cost,
929 version,
930 salt,
931 key,
932 },
933 Some((hsm, hmac_key)),
934 ) => {
935 let version: Version = (*version).try_into().map_err(|_| {
936 error!("Failed to convert {} to valid argon2id version", version);
937 CryptoError::Argon2Version
938 })?;
939
940 let key_len = key.len();
941
942 let params =
943 Params::new(*m_cost, *t_cost, *p_cost, Some(key_len)).map_err(|e| {
944 error!(err = ?e, "invalid argon2id parameters");
945 CryptoError::Argon2Parameters
946 })?;
947
948 let argon = Argon2::new(Algorithm::Argon2id, version, params);
949 let mut check_key: Vec<u8> = (0..key_len).map(|_| 0).collect();
950
951 argon
952 .hash_password_into(
953 cleartext.as_bytes(),
954 salt.as_slice(),
955 check_key.as_mut_slice(),
956 )
957 .map_err(|e| {
958 error!(err = ?e, "unable to perform argon2id hash");
959 CryptoError::Argon2
960 })
961 .and_then(|()| {
962 hsm.hmac_s256(hmac_key, &check_key).map_err(|err| {
963 error!(?err, "hsm error");
964 CryptoError::Hsm
965 })
966 })
967 .map(|hmac_key| {
968 hmac_key.into_bytes().as_slice() == key
970 })
971 }
972 (Kdf::TPM_ARGON2ID { .. }, None) => {
973 error!("Unable to validate password - not hsm context available");
974 Err(CryptoError::HsmContextMissing)
975 }
976 (
977 Kdf::ARGON2ID {
978 m_cost,
979 t_cost,
980 p_cost,
981 version,
982 salt,
983 key,
984 },
985 _,
986 ) => {
987 let version: Version = (*version).try_into().map_err(|_| {
988 error!("Failed to convert {} to valid argon2id version", version);
989 CryptoError::Argon2Version
990 })?;
991
992 let key_len = key.len();
993
994 let params =
995 Params::new(*m_cost, *t_cost, *p_cost, Some(key_len)).map_err(|e| {
996 error!(err = ?e, "invalid argon2id parameters");
997 CryptoError::Argon2Parameters
998 })?;
999
1000 let argon = Argon2::new(Algorithm::Argon2id, version, params);
1001 let mut check_key: Vec<u8> = (0..key_len).map(|_| 0).collect();
1002
1003 argon
1004 .hash_password_into(
1005 cleartext.as_bytes(),
1006 salt.as_slice(),
1007 check_key.as_mut_slice(),
1008 )
1009 .map_err(|e| {
1010 error!(err = ?e, "unable to perform argon2id hash");
1011 CryptoError::Argon2
1012 })
1013 .map(|()| {
1014 &check_key == key
1016 })
1017 }
1018 (Kdf::PBKDF2(cost, salt, key), _) => {
1019 let key_len = key.len();
1022 debug_assert!(key_len >= PBKDF2_MIN_NIST_KEY_LEN);
1023 let mut chal_key: Vec<u8> = (0..key_len).map(|_| 0).collect();
1024
1025 pbkdf2_hmac::<Sha256>(
1026 cleartext.as_bytes(),
1027 salt.as_slice(),
1028 *cost,
1029 chal_key.as_mut_slice(),
1030 );
1031
1032 Ok(&chal_key == key)
1034 }
1035 (Kdf::PBKDF2_SHA1(cost, salt, key), _) => {
1036 let key_len = key.len();
1037 debug_assert!(key_len >= PBKDF2_SHA1_MIN_KEY_LEN);
1038 let mut chal_key: Vec<u8> = (0..key_len).map(|_| 0).collect();
1039
1040 pbkdf2_hmac::<Sha1>(
1041 cleartext.as_bytes(),
1042 salt.as_slice(),
1043 *cost,
1044 chal_key.as_mut_slice(),
1045 );
1046
1047 Ok(&chal_key == key)
1049 }
1050 (Kdf::PBKDF2_SHA512(cost, salt, key), _) => {
1051 let key_len = key.len();
1052 debug_assert!(key_len >= PBKDF2_MIN_NIST_KEY_LEN);
1053 let mut chal_key: Vec<u8> = (0..key_len).map(|_| 0).collect();
1054
1055 pbkdf2_hmac::<Sha512>(
1056 cleartext.as_bytes(),
1057 salt.as_slice(),
1058 *cost,
1059 chal_key.as_mut_slice(),
1060 );
1061
1062 Ok(&chal_key == key)
1064 }
1065 (Kdf::SHA1(key), _) => {
1066 let mut hasher = Sha1::new();
1067 hasher.update(cleartext.as_bytes());
1068 let r = hasher.finalize();
1069 Ok(key == &(r.to_vec()))
1070 }
1071 (Kdf::SSHA1(salt, key), _) => {
1072 let mut hasher = Sha1::new();
1073 hasher.update(cleartext.as_bytes());
1074 hasher.update(salt);
1075 let r = hasher.finalize();
1076 Ok(key == &(r.to_vec()))
1077 }
1078 (Kdf::SHA256(key), _) => {
1079 let mut hasher = Sha256::new();
1080 hasher.update(cleartext.as_bytes());
1081 let r = hasher.finalize();
1082 Ok(key == &(r.to_vec()))
1083 }
1084 (Kdf::SSHA256(salt, key), _) => {
1085 let mut hasher = Sha256::new();
1086 hasher.update(cleartext.as_bytes());
1087 hasher.update(salt);
1088 let r = hasher.finalize();
1089 Ok(key == &(r.to_vec()))
1090 }
1091 (Kdf::SHA512(key), _) => {
1092 let mut hasher = Sha512::new();
1093 hasher.update(cleartext.as_bytes());
1094 let r = hasher.finalize();
1095 Ok(key == &(r.to_vec()))
1096 }
1097 (Kdf::SSHA512(salt, key), _) => {
1098 let mut hasher = Sha512::new();
1099 hasher.update(cleartext.as_bytes());
1100 hasher.update(salt);
1101 let r = hasher.finalize();
1102 Ok(key == &(r.to_vec()))
1103 }
1104 (Kdf::NT_MD4(key), _) => {
1105 let clear_utf16le: Vec<u8> = cleartext
1107 .encode_utf16()
1108 .map(|c| c.to_le_bytes())
1109 .flat_map(|i| i.into_iter())
1110 .collect();
1111
1112 let mut hasher = Md4::new();
1113 hasher.update(&clear_utf16le);
1114 let chal_key = hasher.finalize();
1115
1116 Ok(chal_key.as_slice() == key)
1117 }
1118 (Kdf::CRYPT_MD5 { s, h }, _) => {
1119 let chal_key = crypt_md5::do_md5_crypt(cleartext.as_bytes(), s);
1120 Ok(chal_key == *h)
1121 }
1122 (Kdf::CRYPT_SHA256 { h }, _) => {
1123 let is_valid = sha_crypt::sha256_check(cleartext, h.as_str()).is_ok();
1124
1125 Ok(is_valid)
1126 }
1127 (Kdf::CRYPT_SHA512 { h }, _) => {
1128 let is_valid = sha_crypt::sha512_check(cleartext, h.as_str()).is_ok();
1129
1130 Ok(is_valid)
1131 }
1132 }
1133 }
1134
1135 pub fn to_dbpasswordv1(&self) -> DbPasswordV1 {
1136 match &self.material {
1137 Kdf::TPM_ARGON2ID {
1138 m_cost,
1139 t_cost,
1140 p_cost,
1141 version,
1142 salt,
1143 key,
1144 } => DbPasswordV1::TPM_ARGON2ID {
1145 m: *m_cost,
1146 t: *t_cost,
1147 p: *p_cost,
1148 v: *version,
1149 s: salt.clone().into(),
1150 k: key.clone().into(),
1151 },
1152 Kdf::ARGON2ID {
1153 m_cost,
1154 t_cost,
1155 p_cost,
1156 version,
1157 salt,
1158 key,
1159 } => DbPasswordV1::ARGON2ID {
1160 m: *m_cost,
1161 t: *t_cost,
1162 p: *p_cost,
1163 v: *version,
1164 s: salt.clone().into(),
1165 k: key.clone().into(),
1166 },
1167 Kdf::PBKDF2(cost, salt, hash) => {
1168 DbPasswordV1::PBKDF2(*cost, salt.clone(), hash.clone())
1169 }
1170 Kdf::PBKDF2_SHA1(cost, salt, hash) => {
1171 DbPasswordV1::PBKDF2_SHA1(*cost, salt.clone(), hash.clone())
1172 }
1173 Kdf::PBKDF2_SHA512(cost, salt, hash) => {
1174 DbPasswordV1::PBKDF2_SHA512(*cost, salt.clone(), hash.clone())
1175 }
1176 Kdf::SHA1(hash) => DbPasswordV1::SHA1(hash.clone()),
1177 Kdf::SSHA1(salt, hash) => DbPasswordV1::SSHA1(salt.clone(), hash.clone()),
1178 Kdf::SHA256(hash) => DbPasswordV1::SHA256(hash.clone()),
1179 Kdf::SSHA256(salt, hash) => DbPasswordV1::SSHA256(salt.clone(), hash.clone()),
1180 Kdf::SHA512(hash) => DbPasswordV1::SHA512(hash.clone()),
1181 Kdf::SSHA512(salt, hash) => DbPasswordV1::SSHA512(salt.clone(), hash.clone()),
1182 Kdf::NT_MD4(hash) => DbPasswordV1::NT_MD4(hash.clone()),
1183 Kdf::CRYPT_MD5 { s, h } => DbPasswordV1::CRYPT_MD5 {
1184 s: s.clone().into(),
1185 h: h.clone().into(),
1186 },
1187 Kdf::CRYPT_SHA256 { h } => DbPasswordV1::CRYPT_SHA256 { h: h.clone() },
1188 Kdf::CRYPT_SHA512 { h } => DbPasswordV1::CRYPT_SHA512 { h: h.clone() },
1189 }
1190 }
1191
1192 pub fn requires_upgrade(&self) -> bool {
1193 match &self.material {
1194 Kdf::ARGON2ID {
1195 m_cost,
1196 t_cost,
1197 p_cost,
1198 version,
1199 salt,
1200 key,
1201 } => {
1202 *version < ARGON2_VERSION ||
1203 salt.len() < ARGON2_SALT_LEN ||
1204 key.len() < ARGON2_KEY_LEN ||
1205 *p_cost > ARGON2_MAX_P_COST ||
1207 *t_cost > ARGON2_MAX_T_COST ||
1209 *m_cost > ARGON2_MAX_RAM_KIB
1211 }
1212 Kdf::TPM_ARGON2ID { .. } => false,
1214 Kdf::PBKDF2(_, _, _)
1216 | Kdf::PBKDF2_SHA512(_, _, _)
1217 | Kdf::PBKDF2_SHA1(_, _, _)
1218 | Kdf::SHA1(_)
1219 | Kdf::SSHA1(_, _)
1220 | Kdf::SHA256(_)
1221 | Kdf::SSHA256(_, _)
1222 | Kdf::SHA512(_)
1223 | Kdf::SSHA512(_, _)
1224 | Kdf::NT_MD4(_)
1225 | Kdf::CRYPT_MD5 { .. }
1226 | Kdf::CRYPT_SHA256 { .. }
1227 | Kdf::CRYPT_SHA512 { .. } => true,
1228 }
1229 }
1230}
1231
1232#[cfg(test)]
1233mod tests {
1234 use kanidm_hsm_crypto::{
1235 provider::{SoftTpm, TpmHmacS256},
1236 AuthValue,
1237 };
1238 use std::convert::TryFrom;
1239
1240 use crate::*;
1241
1242 #[test]
1243 fn test_credential_simple() {
1244 let p = CryptoPolicy::minimum();
1245 let c = Password::new(&p, "password").unwrap();
1246 assert!(c.verify("password").unwrap());
1247 assert!(!c.verify("password1").unwrap());
1248 assert!(!c.verify("Password1").unwrap());
1249 assert!(!c.verify("It Works!").unwrap());
1250 assert!(!c.verify("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap());
1251 }
1252
1253 #[test]
1254 fn test_password_pbkdf2() {
1255 let p = CryptoPolicy::minimum();
1256 let c = Password::new_pbkdf2(&p, "password").unwrap();
1257 assert!(c.verify("password").unwrap());
1258 assert!(!c.verify("password1").unwrap());
1259 assert!(!c.verify("Password1").unwrap());
1260 }
1261
1262 #[test]
1263 fn test_password_argon2id() {
1264 let p = CryptoPolicy::minimum();
1265 let c = Password::new_argon2id(&p, "password").unwrap();
1266 assert!(c.verify("password").unwrap());
1267 assert!(!c.verify("password1").unwrap());
1268 assert!(!c.verify("Password1").unwrap());
1269 }
1270
1271 #[test]
1272 fn test_password_from_invalid() {
1273 assert!(Password::try_from("password").is_err())
1274 }
1275
1276 #[test]
1277 fn test_password_from_django_pbkdf2_sha256() {
1278 let im_pw = "pbkdf2_sha256$36000$xIEozuZVAoYm$uW1b35DUKyhvQAf1mBqMvoBDcqSD06juzyO/nmyV0+w=";
1279 let password = "eicieY7ahchaoCh0eeTa";
1280 let r = Password::try_from(im_pw).expect("Failed to parse");
1281 assert!(r.verify(password).unwrap_or(false));
1282 }
1283
1284 #[test]
1285 fn test_password_from_ds_sha1() {
1286 let im_pw = "{SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=";
1287 let _r = Password::try_from(im_pw).expect("Failed to parse");
1288
1289 let im_pw = "{sha}W6ph5Mm5Pz8GgiULbPgzG37mj9g=";
1290 let password = "password";
1291 let r = Password::try_from(im_pw).expect("Failed to parse");
1292
1293 assert!(r.requires_upgrade());
1295 assert!(r.verify(password).unwrap_or(false));
1296 }
1297
1298 #[test]
1299 fn test_password_from_ds_ssha1() {
1300 let im_pw = "{SSHA}EyzbBiP4u4zxOrLpKTORI/RX3HC6TCTJtnVOCQ==";
1301 let _r = Password::try_from(im_pw).expect("Failed to parse");
1302
1303 let im_pw = "{ssha}EyzbBiP4u4zxOrLpKTORI/RX3HC6TCTJtnVOCQ==";
1304 let password = "password";
1305 let r = Password::try_from(im_pw).expect("Failed to parse");
1306
1307 assert!(r.requires_upgrade());
1309 assert!(r.verify(password).unwrap_or(false));
1310 }
1311
1312 #[test]
1313 fn test_password_from_ds_sha256() {
1314 let im_pw = "{SHA256}XohImNooBHFR0OVvjcYpJ3NgPQ1qq73WKhHvch0VQtg=";
1315 let _r = Password::try_from(im_pw).expect("Failed to parse");
1316
1317 let im_pw = "{sha256}XohImNooBHFR0OVvjcYpJ3NgPQ1qq73WKhHvch0VQtg=";
1318 let password = "password";
1319 let r = Password::try_from(im_pw).expect("Failed to parse");
1320
1321 assert!(r.requires_upgrade());
1323 assert!(r.verify(password).unwrap_or(false));
1324 }
1325
1326 #[test]
1327 fn test_password_from_ds_ssha256() {
1328 let im_pw = "{SSHA256}luYWfFJOZgxySTsJXHgIaCYww4yMpu6yest69j/wO5n5OycuHFV/GQ==";
1329 let _r = Password::try_from(im_pw).expect("Failed to parse");
1330
1331 let im_pw = "{ssha256}luYWfFJOZgxySTsJXHgIaCYww4yMpu6yest69j/wO5n5OycuHFV/GQ==";
1332 let password = "password";
1333 let r = Password::try_from(im_pw).expect("Failed to parse");
1334
1335 assert!(r.requires_upgrade());
1337 assert!(r.verify(password).unwrap_or(false));
1338 }
1339
1340 #[test]
1341 fn test_password_from_ds_sha512() {
1342 let im_pw = "{SHA512}sQnzu7wkTrgkQZF+0G1hi5AI3Qmzvv0bXgc5THBqi7mAsdd4Xll27ASbRt9fEyavWi6m0QP9B8lThf+rDKy8hg==";
1343 let _r = Password::try_from(im_pw).expect("Failed to parse");
1344
1345 let im_pw = "{sha512}sQnzu7wkTrgkQZF+0G1hi5AI3Qmzvv0bXgc5THBqi7mAsdd4Xll27ASbRt9fEyavWi6m0QP9B8lThf+rDKy8hg==";
1346 let password = "password";
1347 let r = Password::try_from(im_pw).expect("Failed to parse");
1348
1349 assert!(r.requires_upgrade());
1351 assert!(r.verify(password).unwrap_or(false));
1352 }
1353
1354 #[test]
1355 fn test_password_from_ds_ssha512() {
1356 let im_pw = "{SSHA512}JwrSUHkI7FTAfHRVR6KoFlSN0E3dmaQWARjZ+/UsShYlENOqDtFVU77HJLLrY2MuSp0jve52+pwtdVl2QUAHukQ0XUf5LDtM";
1357 let _r = Password::try_from(im_pw).expect("Failed to parse");
1358
1359 let im_pw = "{ssha512}JwrSUHkI7FTAfHRVR6KoFlSN0E3dmaQWARjZ+/UsShYlENOqDtFVU77HJLLrY2MuSp0jve52+pwtdVl2QUAHukQ0XUf5LDtM";
1360 let password = "password";
1361 let r = Password::try_from(im_pw).expect("Failed to parse");
1362
1363 assert!(r.requires_upgrade());
1365 assert!(r.verify(password).unwrap_or(false));
1366 }
1367
1368 #[test]
1372 fn test_password_from_openldap_pkbdf2() {
1373 let im_pw = "{PBKDF2}10000$IlfapjA351LuDSwYC0IQ8Q$saHqQTuYnjJN/tmAndT.8mJt.6w";
1374 let password = "password";
1375 let r = Password::try_from(im_pw).expect("Failed to parse");
1376 assert!(r.requires_upgrade());
1377 assert!(r.verify(password).unwrap_or(false));
1378 }
1379
1380 #[test]
1381 fn test_password_from_openldap_pkbdf2_sha1() {
1382 let im_pw = "{PBKDF2-SHA1}10000$ZBEH6B07rgQpJSikyvMU2w$TAA03a5IYkz1QlPsbJKvUsTqNV";
1383 let password = "password";
1384 let r = Password::try_from(im_pw).expect("Failed to parse");
1385 assert!(r.requires_upgrade());
1386 assert!(r.verify(password).unwrap_or(false));
1387 }
1388
1389 #[test]
1390 fn test_password_from_openldap_pkbdf2_sha256() {
1391 let im_pw = "{PBKDF2-SHA256}10000$henZGfPWw79Cs8ORDeVNrQ$1dTJy73v6n3bnTmTZFghxHXHLsAzKaAy8SksDfZBPIw";
1392 let password = "password";
1393 let r = Password::try_from(im_pw).expect("Failed to parse");
1394 assert!(r.requires_upgrade());
1395 assert!(r.verify(password).unwrap_or(false));
1396 }
1397
1398 #[test]
1399 fn test_password_from_openldap_pkbdf2_sha512() {
1400 let im_pw = "{PBKDF2-SHA512}10000$Je1Uw19Bfv5lArzZ6V3EPw$g4T/1sqBUYWl9o93MVnyQ/8zKGSkPbKaXXsT8WmysXQJhWy8MRP2JFudSL.N9RklQYgDPxPjnfum/F2f/TrppA";
1401 let password = "password";
1402 let r = Password::try_from(im_pw).expect("Failed to parse");
1403 assert!(r.requires_upgrade());
1404 assert!(r.verify(password).unwrap_or(false));
1405 }
1406
1407 #[test]
1409 fn test_password_from_openldap_argon2() {
1410 sketching::test_init();
1411 let im_pw = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$IyTQMsvzB2JHDiWx8fq7Ew$VhYOA7AL0kbRXI5g2kOyyp8St1epkNj7WZyUY4pAIQQ";
1412 let password = "password";
1413 let r = Password::try_from(im_pw).expect("Failed to parse");
1414 assert!(!r.requires_upgrade());
1415 assert!(r.verify(password).unwrap_or(false));
1416 }
1417
1418 #[test]
1425 fn test_password_from_ipa_nt_hash() {
1426 sketching::test_init();
1427 let im_pw = "ipaNTHash: iEb36u6PsRetBr3YMLdYbA";
1429 let password = "password";
1430 let r = Password::try_from(im_pw).expect("Failed to parse");
1431 assert!(r.requires_upgrade());
1432
1433 assert!(r.verify(password).expect("Failed to hash"));
1434 let im_pw = "ipaNTHash: pS43DjQLcUYhaNF_cd_Vhw==";
1435 Password::try_from(im_pw).expect("Failed to parse");
1436 }
1437
1438 #[test]
1439 fn test_password_from_samba_nt_hash() {
1440 sketching::test_init();
1441 let im_pw = "sambaNTPassword: 8846F7EAEE8FB117AD06BDD830B7586C";
1443 let password = "password";
1444 let r = Password::try_from(im_pw).expect("Failed to parse");
1445 assert!(r.requires_upgrade());
1446 assert!(r.verify(password).expect("Failed to hash"));
1447 }
1448
1449 #[test]
1450 fn test_password_from_crypt_md5() {
1451 sketching::test_init();
1452 let im_pw = "{crypt}$1$zaRIAsoe$7887GzjDTrst0XbDPpF5m.";
1453 let password = "password";
1454 let r = Password::try_from(im_pw).expect("Failed to parse");
1455
1456 assert!(r.requires_upgrade());
1457 assert!(r.verify(password).unwrap_or(false));
1458 }
1459
1460 #[test]
1461 fn test_password_from_crypt_sha256() {
1462 sketching::test_init();
1463 let im_pw = "{crypt}$5$3UzV7Sut8EHCUxlN$41V.jtMQmFAOucqI4ImFV43r.bRLjPlN.hyfoCdmGE2";
1464 let password = "password";
1465 let r = Password::try_from(im_pw).expect("Failed to parse");
1466
1467 assert!(r.requires_upgrade());
1468 assert!(r.verify(password).unwrap_or(false));
1469 }
1470
1471 #[test]
1472 fn test_password_from_crypt_sha512() {
1473 sketching::test_init();
1474 let im_pw = "{crypt}$6$aXn8azL8DXUyuMvj$9aJJC/KEUwygIpf2MTqjQa.f0MEXNg2cGFc62Fet8XpuDVDedM05CweAlxW6GWxnmHqp14CRf6zU7OQoE/bCu0";
1475 let password = "password";
1476 let r = Password::try_from(im_pw).expect("Failed to parse");
1477
1478 assert!(r.requires_upgrade());
1479 assert!(r.verify(password).unwrap_or(false));
1480 }
1481
1482 #[test]
1483 fn test_password_argon2id_hsm_bind() {
1484 sketching::test_init();
1485
1486 let mut hsm: Box<dyn TpmHmacS256> = Box::new(SoftTpm::default());
1487
1488 let auth_value = AuthValue::ephemeral().unwrap();
1489
1490 let loadable_machine_key = hsm.root_storage_key_create(&auth_value).unwrap();
1491 let machine_key = hsm
1492 .root_storage_key_load(&auth_value, &loadable_machine_key)
1493 .unwrap();
1494
1495 let loadable_hmac_key = hsm.hmac_s256_create(&machine_key).unwrap();
1496 let key = hsm
1497 .hmac_s256_load(&machine_key, &loadable_hmac_key)
1498 .unwrap();
1499
1500 let ctx: &mut dyn TpmHmacS256 = &mut *hsm;
1501
1502 let p = CryptoPolicy::minimum();
1503 let c = Password::new_argon2id_hsm(&p, "password", ctx, &key).unwrap();
1504
1505 assert!(matches!(
1506 c.verify("password"),
1507 Err(CryptoError::HsmContextMissing)
1508 ));
1509
1510 let dup = match &c.material {
1512 Kdf::TPM_ARGON2ID {
1513 m_cost,
1514 t_cost,
1515 p_cost,
1516 version,
1517 salt,
1518 key,
1519 } => Password {
1520 material: Kdf::ARGON2ID {
1521 m_cost: *m_cost,
1522 t_cost: *t_cost,
1523 p_cost: *p_cost,
1524 version: *version,
1525 salt: salt.clone(),
1526 key: key.clone(),
1527 },
1528 },
1529 #[allow(clippy::unreachable)]
1530 _ => unreachable!(),
1531 };
1532
1533 assert!(!dup.verify("password").unwrap());
1534
1535 assert!(c.verify_ctx("password", Some((ctx, &key))).unwrap());
1536 assert!(!c.verify_ctx("password1", Some((ctx, &key))).unwrap());
1537 assert!(!c.verify_ctx("Password1", Some((ctx, &key))).unwrap());
1538 }
1539}