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