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