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