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