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