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