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