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 "pbkdf2_sha256" => Err(PasswordError::InvalidFormat),
754
755 "argon2" => parse_argon(hash_value),
756 "crypt" => parse_crypt(hash_value),
757 "sha" => {
758 let h = general_purpose::STANDARD.decode(hash_value)?;
759 if h.len() != DS_SHA1_HASH_LEN {
760 return Err(PasswordError::InvalidSaltLength);
761 }
762 Ok(Password {
763 material: Kdf::SHA1(h.to_vec()),
764 })
765 }
766 "ssha" => {
767 let sh = general_purpose::STANDARD.decode(hash_value)?;
768 let (h, s) = sh
769 .split_at_checked(DS_SHA1_HASH_LEN)
770 .ok_or(PasswordError::InvalidLength)?;
771
772 Ok(Password {
773 material: Kdf::SSHA1(s.to_vec(), h.to_vec()),
774 })
775 }
776 "sha256" => {
777 let h = general_purpose::STANDARD.decode(hash_value)?;
778 if h.len() != DS_SHA256_HASH_LEN {
779 return Err(PasswordError::InvalidSaltLength);
780 }
781 Ok(Password {
782 material: Kdf::SHA256(h.to_vec()),
783 })
784 }
785 "ssha256" => {
786 let sh = general_purpose::STANDARD.decode(hash_value)?;
787 let (h, s) = sh
788 .split_at_checked(DS_SHA256_HASH_LEN)
789 .ok_or(PasswordError::InvalidLength)?;
790 Ok(Password {
791 material: Kdf::SSHA256(s.to_vec(), h.to_vec()),
792 })
793 }
794 "sha512" => {
795 let h = general_purpose::STANDARD.decode(hash_value)?;
796 if h.len() != DS_SHA512_HASH_LEN {
797 return Err(PasswordError::InvalidSaltLength);
798 }
799 Ok(Password {
800 material: Kdf::SHA512(h.to_vec()),
801 })
802 }
803 "ssha512" => {
804 let sh = general_purpose::STANDARD.decode(hash_value)?;
805 if sh.len() <= DS_SHA512_HASH_LEN {
806 return Err(PasswordError::InvalidSaltLength);
807 }
808 let (h, s) = sh
809 .split_at_checked(DS_SHA512_HASH_LEN)
810 .ok_or(PasswordError::InvalidLength)?;
811 Ok(Password {
812 material: Kdf::SSHA512(s.to_vec(), h.to_vec()),
813 })
814 }
815 _ => Err(PasswordError::NoDecoderFound(hash_format)),
816 }
817 } else {
818 Err(PasswordError::NoDecoderFound(
819 value.chars().take(5).collect(),
820 ))
821 }
822 }
823}
824
825impl Password {
826 fn bench_argon2id(params: Params) -> Option<Duration> {
827 let mut rng = rand::rng();
828 let salt: Vec<u8> = (0..ARGON2_SALT_LEN).map(|_| rng.random()).collect();
829 let input: Vec<u8> = (0..ARGON2_SALT_LEN).map(|_| rng.random()).collect();
830 let mut key: Vec<u8> = (0..ARGON2_KEY_LEN).map(|_| 0).collect();
831
832 let argon = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
833
834 let start = Instant::now();
835 argon
836 .hash_password_into(input.as_slice(), salt.as_slice(), key.as_mut_slice())
837 .ok()?;
838 let end = Instant::now();
839
840 end.checked_duration_since(start)
841 }
842
843 pub fn new_pbkdf2(policy: &CryptoPolicy, cleartext: &str) -> Result<Self, CryptoError> {
844 let pbkdf2_cost = policy.pbkdf2_cost;
845 let mut rng = rand::rng();
846 let salt: Vec<u8> = (0..PBKDF2_SALT_LEN).map(|_| rng.random()).collect();
847 let mut key: Vec<u8> = (0..PBKDF2_KEY_LEN).map(|_| 0).collect();
848
849 pbkdf2_hmac(
850 cleartext.as_bytes(),
851 salt.as_slice(),
852 pbkdf2_cost,
853 MessageDigest::sha256(),
854 key.as_mut_slice(),
855 )
856 .map(|()| {
857 Kdf::PBKDF2(pbkdf2_cost, salt, key)
859 })
860 .map(|material| Password { material })
861 .map_err(|e| e.into())
862 }
863
864 pub fn new_argon2id(policy: &CryptoPolicy, cleartext: &str) -> Result<Self, CryptoError> {
865 let version = Version::V0x13;
866
867 let argon = Argon2::new(Algorithm::Argon2id, version, policy.argon2id_params.clone());
868
869 let mut rng = rand::rng();
870 let salt: Vec<u8> = (0..ARGON2_SALT_LEN).map(|_| rng.random()).collect();
871 let mut key: Vec<u8> = (0..ARGON2_KEY_LEN).map(|_| 0).collect();
872
873 argon
874 .hash_password_into(cleartext.as_bytes(), salt.as_slice(), key.as_mut_slice())
875 .map(|()| Kdf::ARGON2ID {
876 m_cost: policy.argon2id_params.m_cost(),
877 t_cost: policy.argon2id_params.t_cost(),
878 p_cost: policy.argon2id_params.p_cost(),
879 version: version as u32,
880 salt,
881 key,
882 })
883 .map_err(|_| CryptoError::Argon2)
884 .map(|material| Password { material })
885 }
886
887 pub fn new_argon2id_hsm(
888 policy: &CryptoPolicy,
889 cleartext: &str,
890 hsm: &mut dyn TpmHmacS256,
891 hmac_key: &HmacS256Key,
892 ) -> Result<Self, CryptoError> {
893 let version = Version::V0x13;
894
895 let argon = Argon2::new(Algorithm::Argon2id, version, policy.argon2id_params.clone());
896
897 let mut rng = rand::rng();
898 let salt: Vec<u8> = (0..ARGON2_SALT_LEN).map(|_| rng.random()).collect();
899 let mut check_key: Vec<u8> = (0..ARGON2_KEY_LEN).map(|_| 0).collect();
900
901 argon
902 .hash_password_into(
903 cleartext.as_bytes(),
904 salt.as_slice(),
905 check_key.as_mut_slice(),
906 )
907 .map_err(|_| CryptoError::Argon2)
908 .and_then(|()| {
909 hsm.hmac_s256(hmac_key, &check_key)
910 .map_err(|err| {
911 error!(?err, "hsm error");
912 CryptoError::Hsm
913 })
914 .map(|hmac_output| hmac_output.into_bytes().to_vec())
915 })
916 .map(|key| Kdf::TPM_ARGON2ID {
917 m_cost: policy.argon2id_params.m_cost(),
918 t_cost: policy.argon2id_params.t_cost(),
919 p_cost: policy.argon2id_params.p_cost(),
920 version: version as u32,
921 salt,
922 key,
923 })
924 .map(|material| Password { material })
925 }
926
927 #[inline]
928 pub fn new(policy: &CryptoPolicy, cleartext: &str) -> Result<Self, CryptoError> {
929 Self::new_argon2id(policy, cleartext)
930 }
931
932 pub fn verify(&self, cleartext: &str) -> Result<bool, CryptoError> {
933 self.verify_ctx(cleartext, None)
934 }
935
936 pub fn verify_ctx(
937 &self,
938 cleartext: &str,
939 hsm: Option<(&mut dyn TpmHmacS256, &HmacS256Key)>,
940 ) -> Result<bool, CryptoError> {
941 match (&self.material, hsm) {
942 (
943 Kdf::TPM_ARGON2ID {
944 m_cost,
945 t_cost,
946 p_cost,
947 version,
948 salt,
949 key,
950 },
951 Some((hsm, hmac_key)),
952 ) => {
953 let version: Version = (*version).try_into().map_err(|_| {
954 error!("Failed to convert {} to valid argon2id version", version);
955 CryptoError::Argon2Version
956 })?;
957
958 let key_len = key.len();
959
960 let params =
961 Params::new(*m_cost, *t_cost, *p_cost, Some(key_len)).map_err(|e| {
962 error!(err = ?e, "invalid argon2id parameters");
963 CryptoError::Argon2Parameters
964 })?;
965
966 let argon = Argon2::new(Algorithm::Argon2id, version, params);
967 let mut check_key: Vec<u8> = (0..key_len).map(|_| 0).collect();
968
969 argon
970 .hash_password_into(
971 cleartext.as_bytes(),
972 salt.as_slice(),
973 check_key.as_mut_slice(),
974 )
975 .map_err(|e| {
976 error!(err = ?e, "unable to perform argon2id hash");
977 CryptoError::Argon2
978 })
979 .and_then(|()| {
980 hsm.hmac_s256(hmac_key, &check_key).map_err(|err| {
981 error!(?err, "hsm error");
982 CryptoError::Hsm
983 })
984 })
985 .map(|hmac_key| {
986 hmac_key.into_bytes().as_slice() == key
988 })
989 }
990 (Kdf::TPM_ARGON2ID { .. }, None) => {
991 error!("Unable to validate password - not hsm context available");
992 Err(CryptoError::HsmContextMissing)
993 }
994 (
995 Kdf::ARGON2ID {
996 m_cost,
997 t_cost,
998 p_cost,
999 version,
1000 salt,
1001 key,
1002 },
1003 _,
1004 ) => {
1005 let version: Version = (*version).try_into().map_err(|_| {
1006 error!("Failed to convert {} to valid argon2id version", version);
1007 CryptoError::Argon2Version
1008 })?;
1009
1010 let key_len = key.len();
1011
1012 let params =
1013 Params::new(*m_cost, *t_cost, *p_cost, Some(key_len)).map_err(|e| {
1014 error!(err = ?e, "invalid argon2id parameters");
1015 CryptoError::Argon2Parameters
1016 })?;
1017
1018 let argon = Argon2::new(Algorithm::Argon2id, version, params);
1019 let mut check_key: Vec<u8> = (0..key_len).map(|_| 0).collect();
1020
1021 argon
1022 .hash_password_into(
1023 cleartext.as_bytes(),
1024 salt.as_slice(),
1025 check_key.as_mut_slice(),
1026 )
1027 .map_err(|e| {
1028 error!(err = ?e, "unable to perform argon2id hash");
1029 CryptoError::Argon2
1030 })
1031 .map(|()| {
1032 &check_key == key
1034 })
1035 }
1036 (Kdf::PBKDF2(cost, salt, key), _) => {
1037 let key_len = key.len();
1040 debug_assert!(key_len >= PBKDF2_MIN_NIST_KEY_LEN);
1041 let mut chal_key: Vec<u8> = (0..key_len).map(|_| 0).collect();
1042 pbkdf2_hmac(
1043 cleartext.as_bytes(),
1044 salt.as_slice(),
1045 *cost,
1046 MessageDigest::sha256(),
1047 chal_key.as_mut_slice(),
1048 )
1049 .map(|()| {
1050 &chal_key == key
1052 })
1053 .map_err(|e| e.into())
1054 }
1055 (Kdf::PBKDF2_SHA1(cost, salt, key), _) => {
1056 let key_len = key.len();
1057 debug_assert!(key_len >= PBKDF2_SHA1_MIN_KEY_LEN);
1058 let mut chal_key: Vec<u8> = (0..key_len).map(|_| 0).collect();
1059 pbkdf2_hmac(
1060 cleartext.as_bytes(),
1061 salt.as_slice(),
1062 *cost,
1063 MessageDigest::sha1(),
1064 chal_key.as_mut_slice(),
1065 )
1066 .map(|()| {
1067 &chal_key == key
1069 })
1070 .map_err(|e| e.into())
1071 }
1072 (Kdf::PBKDF2_SHA512(cost, salt, key), _) => {
1073 let key_len = key.len();
1074 debug_assert!(key_len >= PBKDF2_MIN_NIST_KEY_LEN);
1075 let mut chal_key: Vec<u8> = (0..key_len).map(|_| 0).collect();
1076 pbkdf2_hmac(
1077 cleartext.as_bytes(),
1078 salt.as_slice(),
1079 *cost,
1080 MessageDigest::sha512(),
1081 chal_key.as_mut_slice(),
1082 )
1083 .map(|()| {
1084 &chal_key == key
1086 })
1087 .map_err(|e| e.into())
1088 }
1089 (Kdf::SHA1(key), _) => {
1090 let mut hasher = Sha1::new();
1091 hasher.update(cleartext.as_bytes());
1092 let r = hasher.finish();
1093 Ok(key == &(r.to_vec()))
1094 }
1095 (Kdf::SSHA1(salt, key), _) => {
1096 let mut hasher = Sha1::new();
1097 hasher.update(cleartext.as_bytes());
1098 hasher.update(salt);
1099 let r = hasher.finish();
1100 Ok(key == &(r.to_vec()))
1101 }
1102 (Kdf::SHA256(key), _) => {
1103 let mut hasher = Sha256::new();
1104 hasher.update(cleartext.as_bytes());
1105 let r = hasher.finish();
1106 Ok(key == &(r.to_vec()))
1107 }
1108 (Kdf::SSHA256(salt, key), _) => {
1109 let mut hasher = Sha256::new();
1110 hasher.update(cleartext.as_bytes());
1111 hasher.update(salt);
1112 let r = hasher.finish();
1113 Ok(key == &(r.to_vec()))
1114 }
1115 (Kdf::SHA512(key), _) => {
1116 let mut hasher = Sha512::new();
1117 hasher.update(cleartext.as_bytes());
1118 let r = hasher.finish();
1119 Ok(key == &(r.to_vec()))
1120 }
1121 (Kdf::SSHA512(salt, key), _) => {
1122 let mut hasher = Sha512::new();
1123 hasher.update(cleartext.as_bytes());
1124 hasher.update(salt);
1125 let r = hasher.finish();
1126 Ok(key == &(r.to_vec()))
1127 }
1128 (Kdf::NT_MD4(key), _) => {
1129 let clear_utf16le: Vec<u8> = cleartext
1131 .encode_utf16()
1132 .map(|c| c.to_le_bytes())
1133 .flat_map(|i| i.into_iter())
1134 .collect();
1135
1136 let mut hasher = Md4::new();
1137 hasher.update(&clear_utf16le);
1138 let chal_key = hasher.finalize();
1139
1140 Ok(chal_key.as_slice() == key)
1141 }
1142 (Kdf::CRYPT_MD5 { s, h }, _) => {
1143 let chal_key = crypt_md5::do_md5_crypt(cleartext.as_bytes(), s);
1144 Ok(chal_key == *h)
1145 }
1146 (Kdf::CRYPT_SHA256 { h }, _) => {
1147 let is_valid = sha_crypt::sha256_check(cleartext, h.as_str()).is_ok();
1148
1149 Ok(is_valid)
1150 }
1151 (Kdf::CRYPT_SHA512 { h }, _) => {
1152 let is_valid = sha_crypt::sha512_check(cleartext, h.as_str()).is_ok();
1153
1154 Ok(is_valid)
1155 }
1156 }
1157 }
1158
1159 pub fn to_dbpasswordv1(&self) -> DbPasswordV1 {
1160 match &self.material {
1161 Kdf::TPM_ARGON2ID {
1162 m_cost,
1163 t_cost,
1164 p_cost,
1165 version,
1166 salt,
1167 key,
1168 } => DbPasswordV1::TPM_ARGON2ID {
1169 m: *m_cost,
1170 t: *t_cost,
1171 p: *p_cost,
1172 v: *version,
1173 s: salt.clone().into(),
1174 k: key.clone().into(),
1175 },
1176 Kdf::ARGON2ID {
1177 m_cost,
1178 t_cost,
1179 p_cost,
1180 version,
1181 salt,
1182 key,
1183 } => DbPasswordV1::ARGON2ID {
1184 m: *m_cost,
1185 t: *t_cost,
1186 p: *p_cost,
1187 v: *version,
1188 s: salt.clone().into(),
1189 k: key.clone().into(),
1190 },
1191 Kdf::PBKDF2(cost, salt, hash) => {
1192 DbPasswordV1::PBKDF2(*cost, salt.clone(), hash.clone())
1193 }
1194 Kdf::PBKDF2_SHA1(cost, salt, hash) => {
1195 DbPasswordV1::PBKDF2_SHA1(*cost, salt.clone(), hash.clone())
1196 }
1197 Kdf::PBKDF2_SHA512(cost, salt, hash) => {
1198 DbPasswordV1::PBKDF2_SHA512(*cost, salt.clone(), hash.clone())
1199 }
1200 Kdf::SHA1(hash) => DbPasswordV1::SHA1(hash.clone()),
1201 Kdf::SSHA1(salt, hash) => DbPasswordV1::SSHA1(salt.clone(), hash.clone()),
1202 Kdf::SHA256(hash) => DbPasswordV1::SHA256(hash.clone()),
1203 Kdf::SSHA256(salt, hash) => DbPasswordV1::SSHA256(salt.clone(), hash.clone()),
1204 Kdf::SHA512(hash) => DbPasswordV1::SHA512(hash.clone()),
1205 Kdf::SSHA512(salt, hash) => DbPasswordV1::SSHA512(salt.clone(), hash.clone()),
1206 Kdf::NT_MD4(hash) => DbPasswordV1::NT_MD4(hash.clone()),
1207 Kdf::CRYPT_MD5 { s, h } => DbPasswordV1::CRYPT_MD5 {
1208 s: s.clone().into(),
1209 h: h.clone().into(),
1210 },
1211 Kdf::CRYPT_SHA256 { h } => DbPasswordV1::CRYPT_SHA256 { h: h.clone() },
1212 Kdf::CRYPT_SHA512 { h } => DbPasswordV1::CRYPT_SHA512 { h: h.clone() },
1213 }
1214 }
1215
1216 pub fn requires_upgrade(&self) -> bool {
1217 match &self.material {
1218 Kdf::ARGON2ID {
1219 m_cost,
1220 t_cost,
1221 p_cost,
1222 version,
1223 salt,
1224 key,
1225 } => {
1226 *version < ARGON2_VERSION ||
1227 salt.len() < ARGON2_SALT_LEN ||
1228 key.len() < ARGON2_KEY_LEN ||
1229 *p_cost > ARGON2_MAX_P_COST ||
1231 *t_cost > ARGON2_MAX_T_COST ||
1233 *m_cost > ARGON2_MAX_RAM_KIB
1235 }
1236 Kdf::TPM_ARGON2ID { .. } => false,
1238 Kdf::PBKDF2(_, _, _)
1240 | Kdf::PBKDF2_SHA512(_, _, _)
1241 | Kdf::PBKDF2_SHA1(_, _, _)
1242 | Kdf::SHA1(_)
1243 | Kdf::SSHA1(_, _)
1244 | Kdf::SHA256(_)
1245 | Kdf::SSHA256(_, _)
1246 | Kdf::SHA512(_)
1247 | Kdf::SSHA512(_, _)
1248 | Kdf::NT_MD4(_)
1249 | Kdf::CRYPT_MD5 { .. }
1250 | Kdf::CRYPT_SHA256 { .. }
1251 | Kdf::CRYPT_SHA512 { .. } => true,
1252 }
1253 }
1254}
1255
1256#[cfg(test)]
1257mod tests {
1258 use kanidm_hsm_crypto::{
1259 provider::{SoftTpm, TpmHmacS256},
1260 AuthValue,
1261 };
1262 use std::convert::TryFrom;
1263
1264 use crate::*;
1265
1266 #[test]
1267 fn test_credential_simple() {
1268 let p = CryptoPolicy::minimum();
1269 let c = Password::new(&p, "password").unwrap();
1270 assert!(c.verify("password").unwrap());
1271 assert!(!c.verify("password1").unwrap());
1272 assert!(!c.verify("Password1").unwrap());
1273 assert!(!c.verify("It Works!").unwrap());
1274 assert!(!c.verify("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap());
1275 }
1276
1277 #[test]
1278 fn test_password_pbkdf2() {
1279 let p = CryptoPolicy::minimum();
1280 let c = Password::new_pbkdf2(&p, "password").unwrap();
1281 assert!(c.verify("password").unwrap());
1282 assert!(!c.verify("password1").unwrap());
1283 assert!(!c.verify("Password1").unwrap());
1284 }
1285
1286 #[test]
1287 fn test_password_argon2id() {
1288 let p = CryptoPolicy::minimum();
1289 let c = Password::new_argon2id(&p, "password").unwrap();
1290 assert!(c.verify("password").unwrap());
1291 assert!(!c.verify("password1").unwrap());
1292 assert!(!c.verify("Password1").unwrap());
1293 }
1294
1295 #[test]
1296 fn test_password_from_invalid() {
1297 assert!(Password::try_from("password").is_err())
1298 }
1299
1300 #[test]
1301 fn test_password_from_django_pbkdf2_sha256() {
1302 let im_pw = "pbkdf2_sha256$36000$xIEozuZVAoYm$uW1b35DUKyhvQAf1mBqMvoBDcqSD06juzyO/nmyV0+w=";
1303 let password = "eicieY7ahchaoCh0eeTa";
1304 let r = Password::try_from(im_pw).expect("Failed to parse");
1305 assert!(r.verify(password).unwrap_or(false));
1306 }
1307
1308 #[test]
1309 fn test_password_from_ds_sha1() {
1310 let im_pw = "{SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=";
1311 let _r = Password::try_from(im_pw).expect("Failed to parse");
1312
1313 let im_pw = "{sha}W6ph5Mm5Pz8GgiULbPgzG37mj9g=";
1314 let password = "password";
1315 let r = Password::try_from(im_pw).expect("Failed to parse");
1316
1317 assert!(r.requires_upgrade());
1319 assert!(r.verify(password).unwrap_or(false));
1320 }
1321
1322 #[test]
1323 fn test_password_from_ds_ssha1() {
1324 let im_pw = "{SSHA}EyzbBiP4u4zxOrLpKTORI/RX3HC6TCTJtnVOCQ==";
1325 let _r = Password::try_from(im_pw).expect("Failed to parse");
1326
1327 let im_pw = "{ssha}EyzbBiP4u4zxOrLpKTORI/RX3HC6TCTJtnVOCQ==";
1328 let password = "password";
1329 let r = Password::try_from(im_pw).expect("Failed to parse");
1330
1331 assert!(r.requires_upgrade());
1333 assert!(r.verify(password).unwrap_or(false));
1334 }
1335
1336 #[test]
1337 fn test_password_from_ds_sha256() {
1338 let im_pw = "{SHA256}XohImNooBHFR0OVvjcYpJ3NgPQ1qq73WKhHvch0VQtg=";
1339 let _r = Password::try_from(im_pw).expect("Failed to parse");
1340
1341 let im_pw = "{sha256}XohImNooBHFR0OVvjcYpJ3NgPQ1qq73WKhHvch0VQtg=";
1342 let password = "password";
1343 let r = Password::try_from(im_pw).expect("Failed to parse");
1344
1345 assert!(r.requires_upgrade());
1347 assert!(r.verify(password).unwrap_or(false));
1348 }
1349
1350 #[test]
1351 fn test_password_from_ds_ssha256() {
1352 let im_pw = "{SSHA256}luYWfFJOZgxySTsJXHgIaCYww4yMpu6yest69j/wO5n5OycuHFV/GQ==";
1353 let _r = Password::try_from(im_pw).expect("Failed to parse");
1354
1355 let im_pw = "{ssha256}luYWfFJOZgxySTsJXHgIaCYww4yMpu6yest69j/wO5n5OycuHFV/GQ==";
1356 let password = "password";
1357 let r = Password::try_from(im_pw).expect("Failed to parse");
1358
1359 assert!(r.requires_upgrade());
1361 assert!(r.verify(password).unwrap_or(false));
1362 }
1363
1364 #[test]
1365 fn test_password_from_ds_sha512() {
1366 let im_pw = "{SHA512}sQnzu7wkTrgkQZF+0G1hi5AI3Qmzvv0bXgc5THBqi7mAsdd4Xll27ASbRt9fEyavWi6m0QP9B8lThf+rDKy8hg==";
1367 let _r = Password::try_from(im_pw).expect("Failed to parse");
1368
1369 let im_pw = "{sha512}sQnzu7wkTrgkQZF+0G1hi5AI3Qmzvv0bXgc5THBqi7mAsdd4Xll27ASbRt9fEyavWi6m0QP9B8lThf+rDKy8hg==";
1370 let password = "password";
1371 let r = Password::try_from(im_pw).expect("Failed to parse");
1372
1373 assert!(r.requires_upgrade());
1375 assert!(r.verify(password).unwrap_or(false));
1376 }
1377
1378 #[test]
1379 fn test_password_from_ds_ssha512() {
1380 let im_pw = "{SSHA512}JwrSUHkI7FTAfHRVR6KoFlSN0E3dmaQWARjZ+/UsShYlENOqDtFVU77HJLLrY2MuSp0jve52+pwtdVl2QUAHukQ0XUf5LDtM";
1381 let _r = Password::try_from(im_pw).expect("Failed to parse");
1382
1383 let im_pw = "{ssha512}JwrSUHkI7FTAfHRVR6KoFlSN0E3dmaQWARjZ+/UsShYlENOqDtFVU77HJLLrY2MuSp0jve52+pwtdVl2QUAHukQ0XUf5LDtM";
1384 let password = "password";
1385 let r = Password::try_from(im_pw).expect("Failed to parse");
1386
1387 assert!(r.requires_upgrade());
1389 assert!(r.verify(password).unwrap_or(false));
1390 }
1391
1392 #[test]
1396 fn test_password_from_openldap_pkbdf2() {
1397 let im_pw = "{PBKDF2}10000$IlfapjA351LuDSwYC0IQ8Q$saHqQTuYnjJN/tmAndT.8mJt.6w";
1398 let password = "password";
1399 let r = Password::try_from(im_pw).expect("Failed to parse");
1400 assert!(r.requires_upgrade());
1401 assert!(r.verify(password).unwrap_or(false));
1402 }
1403
1404 #[test]
1405 fn test_password_from_openldap_pkbdf2_sha1() {
1406 let im_pw = "{PBKDF2-SHA1}10000$ZBEH6B07rgQpJSikyvMU2w$TAA03a5IYkz1QlPsbJKvUsTqNV";
1407 let password = "password";
1408 let r = Password::try_from(im_pw).expect("Failed to parse");
1409 assert!(r.requires_upgrade());
1410 assert!(r.verify(password).unwrap_or(false));
1411 }
1412
1413 #[test]
1414 fn test_password_from_openldap_pkbdf2_sha256() {
1415 let im_pw = "{PBKDF2-SHA256}10000$henZGfPWw79Cs8ORDeVNrQ$1dTJy73v6n3bnTmTZFghxHXHLsAzKaAy8SksDfZBPIw";
1416 let password = "password";
1417 let r = Password::try_from(im_pw).expect("Failed to parse");
1418 assert!(r.requires_upgrade());
1419 assert!(r.verify(password).unwrap_or(false));
1420 }
1421
1422 #[test]
1423 fn test_password_from_openldap_pkbdf2_sha512() {
1424 let im_pw = "{PBKDF2-SHA512}10000$Je1Uw19Bfv5lArzZ6V3EPw$g4T/1sqBUYWl9o93MVnyQ/8zKGSkPbKaXXsT8WmysXQJhWy8MRP2JFudSL.N9RklQYgDPxPjnfum/F2f/TrppA";
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]
1433 fn test_password_from_openldap_argon2() {
1434 sketching::test_init();
1435 let im_pw = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$IyTQMsvzB2JHDiWx8fq7Ew$VhYOA7AL0kbRXI5g2kOyyp8St1epkNj7WZyUY4pAIQQ";
1436 let password = "password";
1437 let r = Password::try_from(im_pw).expect("Failed to parse");
1438 assert!(!r.requires_upgrade());
1439 assert!(r.verify(password).unwrap_or(false));
1440 }
1441
1442 #[test]
1449 fn test_password_from_ipa_nt_hash() {
1450 sketching::test_init();
1451 let im_pw = "ipaNTHash: iEb36u6PsRetBr3YMLdYbA";
1453 let password = "password";
1454 let r = Password::try_from(im_pw).expect("Failed to parse");
1455 assert!(r.requires_upgrade());
1456
1457 assert!(r.verify(password).expect("Failed to hash"));
1458 let im_pw = "ipaNTHash: pS43DjQLcUYhaNF_cd_Vhw==";
1459 Password::try_from(im_pw).expect("Failed to parse");
1460 }
1461
1462 #[test]
1463 fn test_password_from_samba_nt_hash() {
1464 sketching::test_init();
1465 let im_pw = "sambaNTPassword: 8846F7EAEE8FB117AD06BDD830B7586C";
1467 let password = "password";
1468 let r = Password::try_from(im_pw).expect("Failed to parse");
1469 assert!(r.requires_upgrade());
1470 assert!(r.verify(password).expect("Failed to hash"));
1471 }
1472
1473 #[test]
1474 fn test_password_from_crypt_md5() {
1475 sketching::test_init();
1476 let im_pw = "{crypt}$1$zaRIAsoe$7887GzjDTrst0XbDPpF5m.";
1477 let password = "password";
1478 let r = Password::try_from(im_pw).expect("Failed to parse");
1479
1480 assert!(r.requires_upgrade());
1481 assert!(r.verify(password).unwrap_or(false));
1482 }
1483
1484 #[test]
1485 fn test_password_from_crypt_sha256() {
1486 sketching::test_init();
1487 let im_pw = "{crypt}$5$3UzV7Sut8EHCUxlN$41V.jtMQmFAOucqI4ImFV43r.bRLjPlN.hyfoCdmGE2";
1488 let password = "password";
1489 let r = Password::try_from(im_pw).expect("Failed to parse");
1490
1491 assert!(r.requires_upgrade());
1492 assert!(r.verify(password).unwrap_or(false));
1493 }
1494
1495 #[test]
1496 fn test_password_from_crypt_sha512() {
1497 sketching::test_init();
1498 let im_pw = "{crypt}$6$aXn8azL8DXUyuMvj$9aJJC/KEUwygIpf2MTqjQa.f0MEXNg2cGFc62Fet8XpuDVDedM05CweAlxW6GWxnmHqp14CRf6zU7OQoE/bCu0";
1499 let password = "password";
1500 let r = Password::try_from(im_pw).expect("Failed to parse");
1501
1502 assert!(r.requires_upgrade());
1503 assert!(r.verify(password).unwrap_or(false));
1504 }
1505
1506 #[test]
1507 fn test_password_argon2id_hsm_bind() {
1508 sketching::test_init();
1509
1510 let mut hsm: Box<dyn TpmHmacS256> = Box::new(SoftTpm::default());
1511
1512 let auth_value = AuthValue::ephemeral().unwrap();
1513
1514 let loadable_machine_key = hsm.root_storage_key_create(&auth_value).unwrap();
1515 let machine_key = hsm
1516 .root_storage_key_load(&auth_value, &loadable_machine_key)
1517 .unwrap();
1518
1519 let loadable_hmac_key = hsm.hmac_s256_create(&machine_key).unwrap();
1520 let key = hsm
1521 .hmac_s256_load(&machine_key, &loadable_hmac_key)
1522 .unwrap();
1523
1524 let ctx: &mut dyn TpmHmacS256 = &mut *hsm;
1525
1526 let p = CryptoPolicy::minimum();
1527 let c = Password::new_argon2id_hsm(&p, "password", ctx, &key).unwrap();
1528
1529 assert!(matches!(
1530 c.verify("password"),
1531 Err(CryptoError::HsmContextMissing)
1532 ));
1533
1534 let dup = match &c.material {
1536 Kdf::TPM_ARGON2ID {
1537 m_cost,
1538 t_cost,
1539 p_cost,
1540 version,
1541 salt,
1542 key,
1543 } => Password {
1544 material: Kdf::ARGON2ID {
1545 m_cost: *m_cost,
1546 t_cost: *t_cost,
1547 p_cost: *p_cost,
1548 version: *version,
1549 salt: salt.clone(),
1550 key: key.clone(),
1551 },
1552 },
1553 #[allow(clippy::unreachable)]
1554 _ => unreachable!(),
1555 };
1556
1557 assert!(!dup.verify("password").unwrap());
1558
1559 assert!(c.verify_ctx("password", Some((ctx, &key))).unwrap());
1560 assert!(!c.verify_ctx("password1", Some((ctx, &key))).unwrap());
1561 assert!(!c.verify_ctx("Password1", Some((ctx, &key))).unwrap());
1562 }
1563}