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::{HmacKey, Tpm};
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 '{}' - please report it upstream", hint)
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 Tpm,
938 hmac_key: &HmacKey,
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(hmac_key, &check_key).map_err(|err| {
957 error!(?err, "hsm error");
958 CryptoError::Hsm
959 })
960 })
961 .map(|key| Kdf::TPM_ARGON2ID {
962 m_cost: policy.argon2id_params.m_cost(),
963 t_cost: policy.argon2id_params.t_cost(),
964 p_cost: policy.argon2id_params.p_cost(),
965 version: version as u32,
966 salt,
967 key,
968 })
969 .map(|material| Password { material })
970 }
971
972 #[inline]
973 pub fn new(policy: &CryptoPolicy, cleartext: &str) -> Result<Self, CryptoError> {
974 Self::new_argon2id(policy, cleartext)
975 }
976
977 pub fn verify(&self, cleartext: &str) -> Result<bool, CryptoError> {
978 self.verify_ctx(cleartext, None)
979 }
980
981 pub fn verify_ctx(
982 &self,
983 cleartext: &str,
984 hsm: Option<(&mut dyn Tpm, &HmacKey)>,
985 ) -> Result<bool, CryptoError> {
986 match (&self.material, hsm) {
987 (
988 Kdf::TPM_ARGON2ID {
989 m_cost,
990 t_cost,
991 p_cost,
992 version,
993 salt,
994 key,
995 },
996 Some((hsm, hmac_key)),
997 ) => {
998 let version: Version = (*version).try_into().map_err(|_| {
999 error!("Failed to convert {} to valid argon2id version", version);
1000 CryptoError::Argon2Version
1001 })?;
1002
1003 let key_len = key.len();
1004
1005 let params =
1006 Params::new(*m_cost, *t_cost, *p_cost, Some(key_len)).map_err(|e| {
1007 error!(err = ?e, "invalid argon2id parameters");
1008 CryptoError::Argon2Parameters
1009 })?;
1010
1011 let argon = Argon2::new(Algorithm::Argon2id, version, params);
1012 let mut check_key: Vec<u8> = (0..key_len).map(|_| 0).collect();
1013
1014 argon
1015 .hash_password_into(
1016 cleartext.as_bytes(),
1017 salt.as_slice(),
1018 check_key.as_mut_slice(),
1019 )
1020 .map_err(|e| {
1021 error!(err = ?e, "unable to perform argon2id hash");
1022 CryptoError::Argon2
1023 })
1024 .and_then(|()| {
1025 hsm.hmac(hmac_key, &check_key).map_err(|err| {
1026 error!(?err, "hsm error");
1027 CryptoError::Hsm
1028 })
1029 })
1030 .map(|hmac_key| {
1031 &hmac_key == key
1033 })
1034 }
1035 (Kdf::TPM_ARGON2ID { .. }, None) => {
1036 error!("Unable to validate password - not hsm context available");
1037 Err(CryptoError::HsmContextMissing)
1038 }
1039 (
1040 Kdf::ARGON2ID {
1041 m_cost,
1042 t_cost,
1043 p_cost,
1044 version,
1045 salt,
1046 key,
1047 },
1048 _,
1049 ) => {
1050 let version: Version = (*version).try_into().map_err(|_| {
1051 error!("Failed to convert {} to valid argon2id version", version);
1052 CryptoError::Argon2Version
1053 })?;
1054
1055 let key_len = key.len();
1056
1057 let params =
1058 Params::new(*m_cost, *t_cost, *p_cost, Some(key_len)).map_err(|e| {
1059 error!(err = ?e, "invalid argon2id parameters");
1060 CryptoError::Argon2Parameters
1061 })?;
1062
1063 let argon = Argon2::new(Algorithm::Argon2id, version, params);
1064 let mut check_key: Vec<u8> = (0..key_len).map(|_| 0).collect();
1065
1066 argon
1067 .hash_password_into(
1068 cleartext.as_bytes(),
1069 salt.as_slice(),
1070 check_key.as_mut_slice(),
1071 )
1072 .map_err(|e| {
1073 error!(err = ?e, "unable to perform argon2id hash");
1074 CryptoError::Argon2
1075 })
1076 .map(|()| {
1077 &check_key == key
1079 })
1080 }
1081 (Kdf::PBKDF2(cost, salt, key), _) => {
1082 let key_len = key.len();
1085 debug_assert!(key_len >= PBKDF2_MIN_NIST_KEY_LEN);
1086 let mut chal_key: Vec<u8> = (0..key_len).map(|_| 0).collect();
1087 pbkdf2_hmac(
1088 cleartext.as_bytes(),
1089 salt.as_slice(),
1090 *cost,
1091 MessageDigest::sha256(),
1092 chal_key.as_mut_slice(),
1093 )
1094 .map(|()| {
1095 &chal_key == key
1097 })
1098 .map_err(|e| e.into())
1099 }
1100 (Kdf::PBKDF2_SHA1(cost, salt, key), _) => {
1101 let key_len = key.len();
1102 debug_assert!(key_len >= PBKDF2_SHA1_MIN_KEY_LEN);
1103 let mut chal_key: Vec<u8> = (0..key_len).map(|_| 0).collect();
1104 pbkdf2_hmac(
1105 cleartext.as_bytes(),
1106 salt.as_slice(),
1107 *cost,
1108 MessageDigest::sha1(),
1109 chal_key.as_mut_slice(),
1110 )
1111 .map(|()| {
1112 &chal_key == key
1114 })
1115 .map_err(|e| e.into())
1116 }
1117 (Kdf::PBKDF2_SHA512(cost, salt, key), _) => {
1118 let key_len = key.len();
1119 debug_assert!(key_len >= PBKDF2_MIN_NIST_KEY_LEN);
1120 let mut chal_key: Vec<u8> = (0..key_len).map(|_| 0).collect();
1121 pbkdf2_hmac(
1122 cleartext.as_bytes(),
1123 salt.as_slice(),
1124 *cost,
1125 MessageDigest::sha512(),
1126 chal_key.as_mut_slice(),
1127 )
1128 .map(|()| {
1129 &chal_key == key
1131 })
1132 .map_err(|e| e.into())
1133 }
1134 (Kdf::SHA1(key), _) => {
1135 let mut hasher = Sha1::new();
1136 hasher.update(cleartext.as_bytes());
1137 let r = hasher.finish();
1138 Ok(key == &(r.to_vec()))
1139 }
1140 (Kdf::SSHA1(salt, key), _) => {
1141 let mut hasher = Sha1::new();
1142 hasher.update(cleartext.as_bytes());
1143 hasher.update(salt);
1144 let r = hasher.finish();
1145 Ok(key == &(r.to_vec()))
1146 }
1147 (Kdf::SHA256(key), _) => {
1148 let mut hasher = Sha256::new();
1149 hasher.update(cleartext.as_bytes());
1150 let r = hasher.finish();
1151 Ok(key == &(r.to_vec()))
1152 }
1153 (Kdf::SSHA256(salt, key), _) => {
1154 let mut hasher = Sha256::new();
1155 hasher.update(cleartext.as_bytes());
1156 hasher.update(salt);
1157 let r = hasher.finish();
1158 Ok(key == &(r.to_vec()))
1159 }
1160 (Kdf::SHA512(key), _) => {
1161 let mut hasher = Sha512::new();
1162 hasher.update(cleartext.as_bytes());
1163 let r = hasher.finish();
1164 Ok(key == &(r.to_vec()))
1165 }
1166 (Kdf::SSHA512(salt, key), _) => {
1167 let mut hasher = Sha512::new();
1168 hasher.update(cleartext.as_bytes());
1169 hasher.update(salt);
1170 let r = hasher.finish();
1171 Ok(key == &(r.to_vec()))
1172 }
1173 (Kdf::NT_MD4(key), _) => {
1174 let clear_utf16le: Vec<u8> = cleartext
1176 .encode_utf16()
1177 .map(|c| c.to_le_bytes())
1178 .flat_map(|i| i.into_iter())
1179 .collect();
1180
1181 let mut hasher = Md4::new();
1182 hasher.update(&clear_utf16le);
1183 let chal_key = hasher.finalize();
1184
1185 Ok(chal_key.as_slice() == key)
1186 }
1187 (Kdf::CRYPT_MD5 { s, h }, _) => {
1188 let chal_key = crypt_md5::do_md5_crypt(cleartext.as_bytes(), s);
1189 Ok(chal_key == *h)
1190 }
1191 (Kdf::CRYPT_SHA256 { h }, _) => {
1192 let is_valid = sha_crypt::sha256_check(cleartext, h.as_str()).is_ok();
1193
1194 Ok(is_valid)
1195 }
1196 (Kdf::CRYPT_SHA512 { h }, _) => {
1197 let is_valid = sha_crypt::sha512_check(cleartext, h.as_str()).is_ok();
1198
1199 Ok(is_valid)
1200 }
1201 }
1202 }
1203
1204 pub fn to_dbpasswordv1(&self) -> DbPasswordV1 {
1205 match &self.material {
1206 Kdf::TPM_ARGON2ID {
1207 m_cost,
1208 t_cost,
1209 p_cost,
1210 version,
1211 salt,
1212 key,
1213 } => DbPasswordV1::TPM_ARGON2ID {
1214 m: *m_cost,
1215 t: *t_cost,
1216 p: *p_cost,
1217 v: *version,
1218 s: salt.clone().into(),
1219 k: key.clone().into(),
1220 },
1221 Kdf::ARGON2ID {
1222 m_cost,
1223 t_cost,
1224 p_cost,
1225 version,
1226 salt,
1227 key,
1228 } => DbPasswordV1::ARGON2ID {
1229 m: *m_cost,
1230 t: *t_cost,
1231 p: *p_cost,
1232 v: *version,
1233 s: salt.clone().into(),
1234 k: key.clone().into(),
1235 },
1236 Kdf::PBKDF2(cost, salt, hash) => {
1237 DbPasswordV1::PBKDF2(*cost, salt.clone(), hash.clone())
1238 }
1239 Kdf::PBKDF2_SHA1(cost, salt, hash) => {
1240 DbPasswordV1::PBKDF2_SHA1(*cost, salt.clone(), hash.clone())
1241 }
1242 Kdf::PBKDF2_SHA512(cost, salt, hash) => {
1243 DbPasswordV1::PBKDF2_SHA512(*cost, salt.clone(), hash.clone())
1244 }
1245 Kdf::SHA1(hash) => DbPasswordV1::SHA1(hash.clone()),
1246 Kdf::SSHA1(salt, hash) => DbPasswordV1::SSHA1(salt.clone(), hash.clone()),
1247 Kdf::SHA256(hash) => DbPasswordV1::SHA256(hash.clone()),
1248 Kdf::SSHA256(salt, hash) => DbPasswordV1::SSHA256(salt.clone(), hash.clone()),
1249 Kdf::SHA512(hash) => DbPasswordV1::SHA512(hash.clone()),
1250 Kdf::SSHA512(salt, hash) => DbPasswordV1::SSHA512(salt.clone(), hash.clone()),
1251 Kdf::NT_MD4(hash) => DbPasswordV1::NT_MD4(hash.clone()),
1252 Kdf::CRYPT_MD5 { s, h } => DbPasswordV1::CRYPT_MD5 {
1253 s: s.clone().into(),
1254 h: h.clone().into(),
1255 },
1256 Kdf::CRYPT_SHA256 { h } => DbPasswordV1::CRYPT_SHA256 { h: h.clone() },
1257 Kdf::CRYPT_SHA512 { h } => DbPasswordV1::CRYPT_SHA512 { h: h.clone() },
1258 }
1259 }
1260
1261 pub fn requires_upgrade(&self) -> bool {
1262 match &self.material {
1263 Kdf::ARGON2ID {
1264 m_cost,
1265 t_cost,
1266 p_cost,
1267 version,
1268 salt,
1269 key,
1270 } => {
1271 *version < ARGON2_VERSION ||
1272 salt.len() < ARGON2_SALT_LEN ||
1273 key.len() < ARGON2_KEY_LEN ||
1274 *p_cost > ARGON2_MAX_P_COST ||
1276 *t_cost > ARGON2_MAX_T_COST ||
1278 *m_cost > ARGON2_MAX_RAM_KIB
1280 }
1281 Kdf::TPM_ARGON2ID { .. } => false,
1283 Kdf::PBKDF2(_, _, _)
1285 | Kdf::PBKDF2_SHA512(_, _, _)
1286 | Kdf::PBKDF2_SHA1(_, _, _)
1287 | Kdf::SHA1(_)
1288 | Kdf::SSHA1(_, _)
1289 | Kdf::SHA256(_)
1290 | Kdf::SSHA256(_, _)
1291 | Kdf::SHA512(_)
1292 | Kdf::SSHA512(_, _)
1293 | Kdf::NT_MD4(_)
1294 | Kdf::CRYPT_MD5 { .. }
1295 | Kdf::CRYPT_SHA256 { .. }
1296 | Kdf::CRYPT_SHA512 { .. } => true,
1297 }
1298 }
1299}
1300
1301#[cfg(test)]
1302mod tests {
1303 use kanidm_hsm_crypto::soft::SoftTpm;
1304 use kanidm_hsm_crypto::AuthValue;
1305 use std::convert::TryFrom;
1306
1307 use crate::*;
1308
1309 #[test]
1310 fn test_credential_simple() {
1311 let p = CryptoPolicy::minimum();
1312 let c = Password::new(&p, "password").unwrap();
1313 assert!(c.verify("password").unwrap());
1314 assert!(!c.verify("password1").unwrap());
1315 assert!(!c.verify("Password1").unwrap());
1316 assert!(!c.verify("It Works!").unwrap());
1317 assert!(!c.verify("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap());
1318 }
1319
1320 #[test]
1321 fn test_password_pbkdf2() {
1322 let p = CryptoPolicy::minimum();
1323 let c = Password::new_pbkdf2(&p, "password").unwrap();
1324 assert!(c.verify("password").unwrap());
1325 assert!(!c.verify("password1").unwrap());
1326 assert!(!c.verify("Password1").unwrap());
1327 }
1328
1329 #[test]
1330 fn test_password_argon2id() {
1331 let p = CryptoPolicy::minimum();
1332 let c = Password::new_argon2id(&p, "password").unwrap();
1333 assert!(c.verify("password").unwrap());
1334 assert!(!c.verify("password1").unwrap());
1335 assert!(!c.verify("Password1").unwrap());
1336 }
1337
1338 #[test]
1339 fn test_password_from_invalid() {
1340 assert!(Password::try_from("password").is_err())
1341 }
1342
1343 #[test]
1344 fn test_password_from_django_pbkdf2_sha256() {
1345 let im_pw = "pbkdf2_sha256$36000$xIEozuZVAoYm$uW1b35DUKyhvQAf1mBqMvoBDcqSD06juzyO/nmyV0+w=";
1346 let password = "eicieY7ahchaoCh0eeTa";
1347 let r = Password::try_from(im_pw).expect("Failed to parse");
1348 assert!(r.verify(password).unwrap_or(false));
1349 }
1350
1351 #[test]
1352 fn test_password_from_ds_sha1() {
1353 let im_pw = "{SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=";
1354 let _r = Password::try_from(im_pw).expect("Failed to parse");
1355
1356 let im_pw = "{sha}W6ph5Mm5Pz8GgiULbPgzG37mj9g=";
1357 let password = "password";
1358 let r = Password::try_from(im_pw).expect("Failed to parse");
1359
1360 assert!(r.requires_upgrade());
1362 assert!(r.verify(password).unwrap_or(false));
1363 }
1364
1365 #[test]
1366 fn test_password_from_ds_ssha1() {
1367 let im_pw = "{SSHA}EyzbBiP4u4zxOrLpKTORI/RX3HC6TCTJtnVOCQ==";
1368 let _r = Password::try_from(im_pw).expect("Failed to parse");
1369
1370 let im_pw = "{ssha}EyzbBiP4u4zxOrLpKTORI/RX3HC6TCTJtnVOCQ==";
1371 let password = "password";
1372 let r = Password::try_from(im_pw).expect("Failed to parse");
1373
1374 assert!(r.requires_upgrade());
1376 assert!(r.verify(password).unwrap_or(false));
1377 }
1378
1379 #[test]
1380 fn test_password_from_ds_sha256() {
1381 let im_pw = "{SHA256}XohImNooBHFR0OVvjcYpJ3NgPQ1qq73WKhHvch0VQtg=";
1382 let _r = Password::try_from(im_pw).expect("Failed to parse");
1383
1384 let im_pw = "{sha256}XohImNooBHFR0OVvjcYpJ3NgPQ1qq73WKhHvch0VQtg=";
1385 let password = "password";
1386 let r = Password::try_from(im_pw).expect("Failed to parse");
1387
1388 assert!(r.requires_upgrade());
1390 assert!(r.verify(password).unwrap_or(false));
1391 }
1392
1393 #[test]
1394 fn test_password_from_ds_ssha256() {
1395 let im_pw = "{SSHA256}luYWfFJOZgxySTsJXHgIaCYww4yMpu6yest69j/wO5n5OycuHFV/GQ==";
1396 let _r = Password::try_from(im_pw).expect("Failed to parse");
1397
1398 let im_pw = "{ssha256}luYWfFJOZgxySTsJXHgIaCYww4yMpu6yest69j/wO5n5OycuHFV/GQ==";
1399 let password = "password";
1400 let r = Password::try_from(im_pw).expect("Failed to parse");
1401
1402 assert!(r.requires_upgrade());
1404 assert!(r.verify(password).unwrap_or(false));
1405 }
1406
1407 #[test]
1408 fn test_password_from_ds_sha512() {
1409 let im_pw = "{SHA512}sQnzu7wkTrgkQZF+0G1hi5AI3Qmzvv0bXgc5THBqi7mAsdd4Xll27ASbRt9fEyavWi6m0QP9B8lThf+rDKy8hg==";
1410 let _r = Password::try_from(im_pw).expect("Failed to parse");
1411
1412 let im_pw = "{sha512}sQnzu7wkTrgkQZF+0G1hi5AI3Qmzvv0bXgc5THBqi7mAsdd4Xll27ASbRt9fEyavWi6m0QP9B8lThf+rDKy8hg==";
1413 let password = "password";
1414 let r = Password::try_from(im_pw).expect("Failed to parse");
1415
1416 assert!(r.requires_upgrade());
1418 assert!(r.verify(password).unwrap_or(false));
1419 }
1420
1421 #[test]
1422 fn test_password_from_ds_ssha512() {
1423 let im_pw = "{SSHA512}JwrSUHkI7FTAfHRVR6KoFlSN0E3dmaQWARjZ+/UsShYlENOqDtFVU77HJLLrY2MuSp0jve52+pwtdVl2QUAHukQ0XUf5LDtM";
1424 let _r = Password::try_from(im_pw).expect("Failed to parse");
1425
1426 let im_pw = "{ssha512}JwrSUHkI7FTAfHRVR6KoFlSN0E3dmaQWARjZ+/UsShYlENOqDtFVU77HJLLrY2MuSp0jve52+pwtdVl2QUAHukQ0XUf5LDtM";
1427 let password = "password";
1428 let r = Password::try_from(im_pw).expect("Failed to parse");
1429
1430 assert!(r.requires_upgrade());
1432 assert!(r.verify(password).unwrap_or(false));
1433 }
1434
1435 #[test]
1439 fn test_password_from_openldap_pkbdf2() {
1440 let im_pw = "{PBKDF2}10000$IlfapjA351LuDSwYC0IQ8Q$saHqQTuYnjJN/tmAndT.8mJt.6w";
1441 let password = "password";
1442 let r = Password::try_from(im_pw).expect("Failed to parse");
1443 assert!(r.requires_upgrade());
1444 assert!(r.verify(password).unwrap_or(false));
1445 }
1446
1447 #[test]
1448 fn test_password_from_openldap_pkbdf2_sha1() {
1449 let im_pw = "{PBKDF2-SHA1}10000$ZBEH6B07rgQpJSikyvMU2w$TAA03a5IYkz1QlPsbJKvUsTqNV";
1450 let password = "password";
1451 let r = Password::try_from(im_pw).expect("Failed to parse");
1452 assert!(r.requires_upgrade());
1453 assert!(r.verify(password).unwrap_or(false));
1454 }
1455
1456 #[test]
1457 fn test_password_from_openldap_pkbdf2_sha256() {
1458 let im_pw = "{PBKDF2-SHA256}10000$henZGfPWw79Cs8ORDeVNrQ$1dTJy73v6n3bnTmTZFghxHXHLsAzKaAy8SksDfZBPIw";
1459 let password = "password";
1460 let r = Password::try_from(im_pw).expect("Failed to parse");
1461 assert!(r.requires_upgrade());
1462 assert!(r.verify(password).unwrap_or(false));
1463 }
1464
1465 #[test]
1466 fn test_password_from_openldap_pkbdf2_sha512() {
1467 let im_pw = "{PBKDF2-SHA512}10000$Je1Uw19Bfv5lArzZ6V3EPw$g4T/1sqBUYWl9o93MVnyQ/8zKGSkPbKaXXsT8WmysXQJhWy8MRP2JFudSL.N9RklQYgDPxPjnfum/F2f/TrppA";
1468 let password = "password";
1469 let r = Password::try_from(im_pw).expect("Failed to parse");
1470 assert!(r.requires_upgrade());
1471 assert!(r.verify(password).unwrap_or(false));
1472 }
1473
1474 #[test]
1476 fn test_password_from_openldap_argon2() {
1477 sketching::test_init();
1478 let im_pw = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$IyTQMsvzB2JHDiWx8fq7Ew$VhYOA7AL0kbRXI5g2kOyyp8St1epkNj7WZyUY4pAIQQ";
1479 let password = "password";
1480 let r = Password::try_from(im_pw).expect("Failed to parse");
1481 assert!(!r.requires_upgrade());
1482 assert!(r.verify(password).unwrap_or(false));
1483 }
1484
1485 #[test]
1492 fn test_password_from_ipa_nt_hash() {
1493 sketching::test_init();
1494 let im_pw = "ipaNTHash: iEb36u6PsRetBr3YMLdYbA";
1496 let password = "password";
1497 let r = Password::try_from(im_pw).expect("Failed to parse");
1498 assert!(r.requires_upgrade());
1499
1500 assert!(r.verify(password).expect("Failed to hash"));
1501 let im_pw = "ipaNTHash: pS43DjQLcUYhaNF_cd_Vhw==";
1502 Password::try_from(im_pw).expect("Failed to parse");
1503 }
1504
1505 #[test]
1506 fn test_password_from_samba_nt_hash() {
1507 sketching::test_init();
1508 let im_pw = "sambaNTPassword: 8846F7EAEE8FB117AD06BDD830B7586C";
1510 let password = "password";
1511 let r = Password::try_from(im_pw).expect("Failed to parse");
1512 assert!(r.requires_upgrade());
1513 assert!(r.verify(password).expect("Failed to hash"));
1514 }
1515
1516 #[test]
1517 fn test_password_from_crypt_md5() {
1518 sketching::test_init();
1519 let im_pw = "{crypt}$1$zaRIAsoe$7887GzjDTrst0XbDPpF5m.";
1520 let password = "password";
1521 let r = Password::try_from(im_pw).expect("Failed to parse");
1522
1523 assert!(r.requires_upgrade());
1524 assert!(r.verify(password).unwrap_or(false));
1525 }
1526
1527 #[test]
1528 fn test_password_from_crypt_sha256() {
1529 sketching::test_init();
1530 let im_pw = "{crypt}$5$3UzV7Sut8EHCUxlN$41V.jtMQmFAOucqI4ImFV43r.bRLjPlN.hyfoCdmGE2";
1531 let password = "password";
1532 let r = Password::try_from(im_pw).expect("Failed to parse");
1533
1534 assert!(r.requires_upgrade());
1535 assert!(r.verify(password).unwrap_or(false));
1536 }
1537
1538 #[test]
1539 fn test_password_from_crypt_sha512() {
1540 sketching::test_init();
1541 let im_pw = "{crypt}$6$aXn8azL8DXUyuMvj$9aJJC/KEUwygIpf2MTqjQa.f0MEXNg2cGFc62Fet8XpuDVDedM05CweAlxW6GWxnmHqp14CRf6zU7OQoE/bCu0";
1542 let password = "password";
1543 let r = Password::try_from(im_pw).expect("Failed to parse");
1544
1545 assert!(r.requires_upgrade());
1546 assert!(r.verify(password).unwrap_or(false));
1547 }
1548
1549 #[test]
1550 fn test_password_argon2id_hsm_bind() {
1551 sketching::test_init();
1552
1553 let mut hsm: Box<dyn Tpm> = Box::new(SoftTpm::new());
1554
1555 let auth_value = AuthValue::ephemeral().unwrap();
1556
1557 let loadable_machine_key = hsm.machine_key_create(&auth_value).unwrap();
1558 let machine_key = hsm
1559 .machine_key_load(&auth_value, &loadable_machine_key)
1560 .unwrap();
1561
1562 let loadable_hmac_key = hsm.hmac_key_create(&machine_key).unwrap();
1563 let key = hsm.hmac_key_load(&machine_key, &loadable_hmac_key).unwrap();
1564
1565 let ctx: &mut dyn Tpm = &mut *hsm;
1566
1567 let p = CryptoPolicy::minimum();
1568 let c = Password::new_argon2id_hsm(&p, "password", ctx, &key).unwrap();
1569
1570 assert!(matches!(
1571 c.verify("password"),
1572 Err(CryptoError::HsmContextMissing)
1573 ));
1574
1575 let dup = match &c.material {
1577 Kdf::TPM_ARGON2ID {
1578 m_cost,
1579 t_cost,
1580 p_cost,
1581 version,
1582 salt,
1583 key,
1584 } => Password {
1585 material: Kdf::ARGON2ID {
1586 m_cost: *m_cost,
1587 t_cost: *t_cost,
1588 p_cost: *p_cost,
1589 version: *version,
1590 salt: salt.clone(),
1591 key: key.clone(),
1592 },
1593 },
1594 #[allow(clippy::unreachable)]
1595 _ => unreachable!(),
1596 };
1597
1598 assert!(!dup.verify("password").unwrap());
1599
1600 assert!(c.verify_ctx("password", Some((ctx, &key))).unwrap());
1601 assert!(!c.verify_ctx("password1", Some((ctx, &key))).unwrap());
1602 assert!(!c.verify_ctx("Password1", Some((ctx, &key))).unwrap());
1603 }
1604}