kanidm_lib_crypto/
lib.rs

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
41// NIST 800-63.b salt should be 112 bits -> 14  8u8.
42const PBKDF2_SALT_LEN: usize = 24;
43
44pub const PBKDF2_MIN_NIST_SALT_LEN: usize = 14;
45
46// Min number of rounds for a pbkdf2
47pub const PBKDF2_MIN_NIST_COST: usize = 10_000;
48// Default rounds - owasp recommend 600_000 rounds.
49pub const PBKDF2_DEFAULT_COST: usize = 600_000;
50
51// 32 * u8 -> 256 bits of out.
52const PBKDF2_KEY_LEN: usize = 32;
53const PBKDF2_MIN_NIST_KEY_LEN: usize = 32;
54const PBKDF2_SHA1_MIN_KEY_LEN: usize = 19;
55
56const DS_SHA1_HASH_LEN: usize = 20;
57const DS_SHA256_HASH_LEN: usize = 32;
58const DS_SHA512_HASH_LEN: usize = 64;
59
60// Taken from the argon2 library and rfc 9106
61const ARGON2_VERSION: u32 = 19;
62const ARGON2_SALT_LEN: usize = 16;
63// 32 * u8 -> 256 bits of out.
64const ARGON2_KEY_LEN: usize = 32;
65// Default amount of ram we sacrifice per thread
66const ARGON2_MIN_RAM_KIB: u32 = 8 * 1024;
67const ARGON2_MAX_RAM_KIB: u32 = 64 * 1024;
68// Amount of ram to subtract when we do a T cost iter. This
69// is because t=2 m=32 == t=3 m=20. So we just step down a little
70// to keep the value about the same.
71const ARGON2_TCOST_RAM_ITER_KIB: u32 = 12 * 1024;
72const ARGON2_MIN_T_COST: u32 = 2;
73const ARGON2_MAX_T_COST: u32 = 16;
74const ARGON2_MAX_P_COST: u32 = 1;
75
76#[derive(Clone, Debug)]
77pub enum CryptoError {
78    Hsm,
79    HsmContextMissing,
80    OpenSSL(u64),
81    Md4Disabled,
82    Argon2,
83    Argon2Version,
84    Argon2Parameters,
85    Crypt,
86    InvalidServerName,
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        // this is an .into() because on windows it's a u32 not a u64
97        #[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    // https://docs.rs/argon2/0.5.0/argon2/struct.Params.html
178    // defaults to 19mb memory, 2 iterations and 1 thread, with a 32byte output.
179    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        // Argon2id has multiple parameters. These all are about *exchanges* that you can
205        // request in how the computation is performed.
206        //
207        // rfc9106 explains that there are two algorithms stacked here. Argon2i has defences
208        // against side-channel timing. Argon2d provides defences for time-memory tradeoffs.
209        //
210        // We can see how this impacts timings from sources like:
211        // https://www.twelve21.io/how-to-choose-the-right-parameters-for-argon2/
212        //
213        // M =  256 MB, T =    2, d = 8, Time = 0.732 s
214        // M =  128 MB, T =    6, d = 8, Time = 0.99 s
215        // M =   64 MB, T =   12, d = 8, Time = 0.968 s
216        // M =   32 MB, T =   24, d = 8, Time = 0.896 s
217        // M =   16 MB, T =   49, d = 8, Time = 0.973 s
218        // M =    8 MB, T =   96, d = 8, Time = 0.991 s
219        // M =    4 MB, T =  190, d = 8, Time = 0.977 s
220        // M =    2 MB, T =  271, d = 8, Time = 0.973 s
221        // M =    1 MB, T =  639, d = 8, Time = 0.991 s
222        //
223        // As we can see, the time taken stays constant, but as ram decreases the amount of
224        // CPU work required goes up. In our case, our primary threat is from GPU hashcat
225        // cracking. GPU's tend to have many fast cores but very little amounts of fast ram
226        // for those cores. So we want to have as much ram as *possible* up to a limit, and
227        // then we want to increase iterations.
228        //
229        // This way a GPU has to expend further GPU time to compensate for the less ram.
230        //
231        // We also need to balance this against the fact we are a database, and we do have
232        // caches. We also don't want to over-use RAM, especially because in the worst case
233        // every thread will be operating in argon2id at the same time. That means
234        // thread x ram will be used. If we had 8 threads at 64mb of ram, that would require
235        // 512mb of ram alone just for hashing. This becomes worse as core counts scale, with
236        // 24 core xeons easily reaching 1.5GB in these cases.
237
238        let mut m_cost = ARGON2_MIN_RAM_KIB;
239        let mut t_cost = ARGON2_MIN_T_COST;
240        let p_cost = ARGON2_MAX_P_COST;
241
242        // Raise memory usage until an acceptable ram amount is reached.
243        loop {
244            let params = if let Ok(p) = Params::new(m_cost, t_cost, p_cost, None) {
245                p
246            } else {
247                // Unable to proceed.
248                error!(
249                    ?m_cost,
250                    ?t_cost,
251                    ?p_cost,
252                    "Parameters were not valid for argon2"
253                );
254                break;
255            };
256
257            if let Some(ubt) = Password::bench_argon2id(params) {
258                debug!("{}ns - t_cost {} m_cost {}", ubt.as_nanos(), t_cost, m_cost);
259                // Parameter adjustment
260                if ubt < target_time {
261                    let m_mult = target_time
262                        .as_nanos()
263                        .checked_div(ubt.as_nanos())
264                        .unwrap_or(1);
265                    if m_cost < ARGON2_MAX_RAM_KIB {
266                        // Help narrow in quicker.
267                        let m_adjust = if m_mult >= 2 {
268                            // Far away, multiply up
269                            m_cost * u32::try_from(m_mult).unwrap_or(2)
270                        } else {
271                            // Close! Increase in a small step
272                            m_cost + 1024
273                        };
274
275                        m_cost = if m_adjust > ARGON2_MAX_RAM_KIB {
276                            ARGON2_MAX_RAM_KIB
277                        } else {
278                            m_adjust
279                        };
280                        continue;
281                    } else if t_cost < ARGON2_MAX_T_COST {
282                        // Help narrow in quicker.
283                        if m_mult >= 2 {
284                            // Far away, multiply T next
285                            let t_adjust = t_cost * u32::try_from(m_mult).unwrap_or(2);
286                            t_cost = t_adjust.clamp(ARGON2_MIN_T_COST, ARGON2_MAX_T_COST);
287                        } else {
288                            // t=2 with m = 32MB is about the same as t=3 m=20MB, so we want to start with ram
289                            // higher on these iterations. About 12MB appears to be one iteration. We use 8MB
290                            // here though, just to give a little window under that for adjustment.
291                            //
292                            // Similar, once we hit t=4 we just need to have max ram.
293                            t_cost += 1;
294                            // Halve the ram cost.
295                            let m_adjust = m_cost
296                                .checked_sub(ARGON2_TCOST_RAM_ITER_KIB)
297                                .unwrap_or(ARGON2_MIN_RAM_KIB);
298
299                            // Clamp the value
300                            m_cost = m_adjust.clamp(ARGON2_MIN_RAM_KIB, ARGON2_MAX_RAM_KIB);
301                        }
302                        continue;
303                    } else {
304                        // Unable to proceed, parameters are maxed out.
305                        warn!("Argon2 parameters have hit their maximums - this may be a bug!");
306                        break;
307                    }
308                } else {
309                    // Found the target time.
310                    break;
311                }
312            } else {
313                error!("Unable to perform bench of argon2id, stopping benchmark");
314                break;
315            }
316        }
317
318        let argon2id_params = Params::new(m_cost, t_cost, p_cost, None)
319            // fallback
320            .unwrap_or_default();
321
322        let p = CryptoPolicy {
323            pbkdf2_cost: PBKDF2_DEFAULT_COST,
324            argon2id_params,
325        };
326        debug!(argon2id_m = %p.argon2id_params.m_cost(), argon2id_p = %p.argon2id_params.p_cost(), argon2id_t = %p.argon2id_params.t_cost(), );
327        p
328    }
329}
330
331// Why PBKDF2? Rust's bcrypt has a number of hardcodings like max pw len of 72
332// I don't really feel like adding in so many restrictions, so I'll use
333// pbkdf2 in openssl because it doesn't have the same limits.
334#[derive(Clone, Debug, PartialEq)]
335#[allow(non_camel_case_types)]
336enum Kdf {
337    TPM_ARGON2ID {
338        m_cost: u32,
339        t_cost: u32,
340        p_cost: u32,
341        version: u32,
342        salt: Vec<u8>,
343        key: Vec<u8>,
344    },
345    //
346    ARGON2ID {
347        m_cost: u32,
348        t_cost: u32,
349        p_cost: u32,
350        version: u32,
351        salt: Vec<u8>,
352        key: Vec<u8>,
353    },
354    //     cost, salt,   hash
355    PBKDF2(usize, Vec<u8>, Vec<u8>),
356
357    // Imported types, will upgrade to the above.
358    //         cost,   salt,    hash
359    PBKDF2_SHA1(usize, Vec<u8>, Vec<u8>),
360    //           cost,   salt,    hash
361    PBKDF2_SHA512(usize, Vec<u8>, Vec<u8>),
362    //      salt     hash
363    SHA1(Vec<u8>),
364    SSHA1(Vec<u8>, Vec<u8>),
365    SHA256(Vec<u8>),
366    SSHA256(Vec<u8>, Vec<u8>),
367    SHA512(Vec<u8>),
368    SSHA512(Vec<u8>, Vec<u8>),
369    //     hash
370    NT_MD4(Vec<u8>),
371    CRYPT_MD5 {
372        s: Vec<u8>,
373        h: Vec<u8>,
374    },
375    CRYPT_SHA256 {
376        h: String,
377    },
378    CRYPT_SHA512 {
379        h: String,
380    },
381}
382
383#[derive(Debug, Clone, PartialEq)]
384pub enum PasswordError {
385    Base64Decoding,
386    InvalidFormat,
387    InvalidKeyLength,
388    InvalidLength,
389    InvalidSaltLength,
390    // We guess what it is, but don't know how to handle it
391    UnsupportedAlgorithm(String),
392    // No idea how to decode this password
393    NoDecoderFound(String),
394    ParsingFailed,
395}
396
397impl Display for PasswordError {
398    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
399        match self {
400            PasswordError::Base64Decoding => write!(f, "Base64 decoding failed"),
401            PasswordError::InvalidFormat => write!(f, "Invalid password format"),
402            PasswordError::InvalidKeyLength => write!(f, "Invalid key length for password"),
403            PasswordError::InvalidLength => write!(f, "Invalid length for password"),
404            PasswordError::InvalidSaltLength => write!(f, "Invalid salt length for password"),
405            PasswordError::UnsupportedAlgorithm(alg) => {
406                write!(f, "Unsupported algorithm: {alg}")
407            }
408            PasswordError::NoDecoderFound(hint) => {
409                write!(f, "No decoder found for password in this format - input started with '{hint}' - please report it upstream")
410            }
411            PasswordError::ParsingFailed => write!(f, "Parsing of password failed"),
412        }
413    }
414}
415
416impl From<ParseIntError> for PasswordError {
417    fn from(_err: ParseIntError) -> Self {
418        PasswordError::ParsingFailed
419    }
420}
421
422impl From<base64::DecodeError> for PasswordError {
423    fn from(_err: base64::DecodeError) -> Self {
424        PasswordError::Base64Decoding
425    }
426}
427
428#[derive(Clone, Debug, PartialEq)]
429pub struct Password {
430    material: Kdf,
431}
432
433impl TryFrom<DbPasswordV1> for Password {
434    type Error = ();
435
436    fn try_from(value: DbPasswordV1) -> Result<Self, Self::Error> {
437        match value {
438            DbPasswordV1::TPM_ARGON2ID { m, t, p, v, s, k } => Ok(Password {
439                material: Kdf::TPM_ARGON2ID {
440                    m_cost: m,
441                    t_cost: t,
442                    p_cost: p,
443                    version: v,
444                    salt: s.into(),
445                    key: k.into(),
446                },
447            }),
448            DbPasswordV1::ARGON2ID { m, t, p, v, s, k } => Ok(Password {
449                material: Kdf::ARGON2ID {
450                    m_cost: m,
451                    t_cost: t,
452                    p_cost: p,
453                    version: v,
454                    salt: s.into(),
455                    key: k.into(),
456                },
457            }),
458            DbPasswordV1::PBKDF2(c, s, h) => Ok(Password {
459                material: Kdf::PBKDF2(c, s, h),
460            }),
461            DbPasswordV1::PBKDF2_SHA1(c, s, h) => Ok(Password {
462                material: Kdf::PBKDF2_SHA1(c, s, h),
463            }),
464            DbPasswordV1::PBKDF2_SHA512(c, s, h) => Ok(Password {
465                material: Kdf::PBKDF2_SHA512(c, s, h),
466            }),
467            DbPasswordV1::SHA1(h) => Ok(Password {
468                material: Kdf::SHA1(h),
469            }),
470            DbPasswordV1::SSHA1(s, h) => Ok(Password {
471                material: Kdf::SSHA1(s, h),
472            }),
473            DbPasswordV1::SHA256(h) => Ok(Password {
474                material: Kdf::SHA256(h),
475            }),
476            DbPasswordV1::SSHA256(s, h) => Ok(Password {
477                material: Kdf::SSHA256(s, h),
478            }),
479            DbPasswordV1::SHA512(h) => Ok(Password {
480                material: Kdf::SHA512(h),
481            }),
482            DbPasswordV1::SSHA512(s, h) => Ok(Password {
483                material: Kdf::SSHA512(s, h),
484            }),
485            DbPasswordV1::NT_MD4(h) => Ok(Password {
486                material: Kdf::NT_MD4(h),
487            }),
488            DbPasswordV1::CRYPT_MD5 { s, h } => Ok(Password {
489                material: Kdf::CRYPT_MD5 {
490                    s: s.into(),
491                    h: h.into(),
492                },
493            }),
494            DbPasswordV1::CRYPT_SHA256 { h } => Ok(Password {
495                material: Kdf::CRYPT_SHA256 { h },
496            }),
497            DbPasswordV1::CRYPT_SHA512 { h } => Ok(Password {
498                material: Kdf::CRYPT_SHA256 { h },
499            }),
500        }
501    }
502}
503
504// OpenLDAP based their PBKDF2 implementation on passlib from python, that uses a
505// non-standard base64 altchar set and padding that is not supported by
506// anything else in the world. To manage this, we only ever encode to base64 with
507// no pad but we have to remap ab64 to b64. This function allows b64 standard with
508// padding to pass, and remaps ab64 to b64 standard with padding.
509macro_rules! ab64_to_b64 {
510    ($ab64:expr) => {{
511        let mut s = $ab64.replace(".", "+");
512        match s.len() & 3 {
513            0 => {
514                // Do nothing
515            }
516            1 => {
517                // One is invalid, do nothing, we'll error in base64
518            }
519            2 => s.push_str("=="),
520            3 => s.push_str("="),
521            _ => unreachable!(),
522        }
523        s
524    }};
525}
526
527/// Django passwords look like `algo$salt$hash`
528fn parse_django_password(value: &str) -> Result<Password, PasswordError> {
529    let django_pbkdf: Vec<&str> = value.split('$').collect();
530    if django_pbkdf.len() != 4 {
531        return Err(PasswordError::InvalidLength);
532    }
533    // let _algo = django_pbkdf[0];
534    let cost = django_pbkdf[1];
535    let salt = django_pbkdf[2];
536    let hash = django_pbkdf[3];
537    let c = cost.parse::<usize>()?;
538    let s: Vec<_> = salt.as_bytes().to_vec();
539    let h = general_purpose::STANDARD.decode(hash)?;
540    if h.len() < PBKDF2_MIN_NIST_KEY_LEN {
541        Err(PasswordError::InvalidLength)
542    } else {
543        Ok(Password {
544            material: Kdf::PBKDF2(c, s, h),
545        })
546    }
547}
548
549fn parse_ipanthash(hash_value: &str) -> Result<Password, PasswordError> {
550    // Great work.
551    let h = base64::engine::general_purpose::URL_SAFE_NO_PAD
552        .decode(hash_value)
553        .or_else(|_| base64::engine::general_purpose::URL_SAFE.decode(hash_value))?;
554
555    Ok(Password {
556        material: Kdf::NT_MD4(h),
557    })
558}
559
560fn parse_sambantpassword(hash_value: &str) -> Result<Password, PasswordError> {
561    let h = hex::decode(hash_value).map_err(|_| PasswordError::ParsingFailed)?;
562    Ok(Password {
563        material: Kdf::NT_MD4(h),
564    })
565}
566
567fn parse_crypt(hash_value: &str) -> Result<Password, PasswordError> {
568    if let Some(crypt_md5_phc) = hash_value.strip_prefix("$1$") {
569        let (salt, hash) = crypt_md5_phc
570            .split_once('$')
571            .ok_or(PasswordError::ParsingFailed)?;
572
573        // These are a hash64 format, so leave them as bytes, don't try
574        // to decode.
575        let s = salt.as_bytes().to_vec();
576        let h = hash.as_bytes().to_vec();
577
578        Ok(Password {
579            material: Kdf::CRYPT_MD5 { s, h },
580        })
581    } else if hash_value.starts_with("$5$") {
582        Ok(Password {
583            material: Kdf::CRYPT_SHA256 {
584                h: hash_value.to_string(),
585            },
586        })
587    } else if hash_value.starts_with("$6$") {
588        Ok(Password {
589            material: Kdf::CRYPT_SHA512 {
590                h: hash_value.to_string(),
591            },
592        })
593    } else {
594        Err(PasswordError::UnsupportedAlgorithm("crypt".to_string()))
595    }
596}
597
598fn parse_pbkdf2(hash_format: &str, hash_value: &str) -> Result<Password, PasswordError> {
599    let ol_pbkdf: Vec<&str> = hash_value.split('$').collect();
600    if ol_pbkdf.len() != 3 {
601        warn!("oldap pbkdf2 found but invalid number of elements?");
602        return Err(PasswordError::InvalidLength);
603    }
604
605    let cost = ol_pbkdf[0];
606    let salt = ol_pbkdf[1];
607    let hash = ol_pbkdf[2];
608
609    let c: usize = cost.parse()?;
610
611    let s = ab64_to_b64!(salt);
612    let base64_decoder_config =
613        general_purpose::GeneralPurposeConfig::new().with_decode_allow_trailing_bits(true);
614    let base64_decoder = GeneralPurpose::new(&alphabet::STANDARD, base64_decoder_config);
615    let s = base64_decoder.decode(s).inspect_err(|e| {
616        error!(?e, "Invalid base64 in oldap pbkdf2-sha1");
617    })?;
618
619    let h = ab64_to_b64!(hash);
620    let h = base64_decoder.decode(h).inspect_err(|e| {
621        error!(?e, "Invalid base64 in oldap pbkdf2-sha1");
622    })?;
623
624    match hash_format {
625        // This is just sha1 in a trenchcoat.
626        "pbkdf2" | "pbkdf2-sha1" => {
627            if h.len() < PBKDF2_SHA1_MIN_KEY_LEN {
628                Err(PasswordError::InvalidKeyLength)
629            } else {
630                Ok(Password {
631                    material: Kdf::PBKDF2_SHA1(c, s, h),
632                })
633            }
634        }
635        "pbkdf2-sha256" => {
636            if h.len() < PBKDF2_MIN_NIST_KEY_LEN {
637                Err(PasswordError::InvalidKeyLength)
638            } else {
639                Ok(Password {
640                    material: Kdf::PBKDF2(c, s, h),
641                })
642            }
643        }
644        "pbkdf2-sha512" => {
645            if h.len() < PBKDF2_MIN_NIST_KEY_LEN {
646                Err(PasswordError::InvalidKeyLength)
647            } else {
648                Ok(Password {
649                    material: Kdf::PBKDF2_SHA512(c, s, h),
650                })
651            }
652        }
653        _ => Err(PasswordError::UnsupportedAlgorithm(hash_format.to_string())),
654    }
655}
656
657fn parse_argon(hash_value: &str) -> Result<Password, PasswordError> {
658    match PasswordHash::try_from(hash_value) {
659        Ok(PasswordHash {
660            algorithm,
661            version,
662            params,
663            salt,
664            hash,
665        }) => {
666            if algorithm.as_str() != "argon2id" {
667                error!(alg = %algorithm.as_str(), "Only argon2id is supported");
668                return Err(PasswordError::UnsupportedAlgorithm(algorithm.to_string()));
669            }
670
671            let version = version.unwrap_or(ARGON2_VERSION);
672            let version: Version = version.try_into().map_err(|_| {
673                error!("Failed to convert {} to valid argon2id version", version);
674                PasswordError::ParsingFailed
675            })?;
676
677            let m_cost = params.get_decimal("m").ok_or_else(|| {
678                error!("Failed to access m_cost parameter");
679                PasswordError::ParsingFailed
680            })?;
681
682            let t_cost = params.get_decimal("t").ok_or_else(|| {
683                error!("Failed to access t_cost parameter");
684                PasswordError::ParsingFailed
685            })?;
686
687            let p_cost = params.get_decimal("p").ok_or_else(|| {
688                error!("Failed to access p_cost parameter");
689                PasswordError::ParsingFailed
690            })?;
691
692            let salt = salt
693                .and_then(|s| {
694                    let mut salt_arr = [0u8; 64];
695                    s.decode_b64(&mut salt_arr)
696                        .ok()
697                        .map(|salt_bytes| salt_bytes.to_owned())
698                })
699                .ok_or_else(|| {
700                    error!("Failed to access salt");
701                    PasswordError::ParsingFailed
702                })?;
703
704            let key = hash.map(|h| h.as_bytes().into()).ok_or_else(|| {
705                error!("Failed to access key");
706                PasswordError::ParsingFailed
707            })?;
708
709            Ok(Password {
710                material: Kdf::ARGON2ID {
711                    m_cost,
712                    t_cost,
713                    p_cost,
714                    version: version as u32,
715                    salt,
716                    key,
717                },
718            })
719        }
720        Err(e) => {
721            error!(?e, "Invalid argon2 PHC string");
722            Err(PasswordError::ParsingFailed)
723        }
724    }
725}
726
727impl TryFrom<&str> for Password {
728    type Error = PasswordError;
729
730    // As we may add more algos, we keep the match algo single for later.
731
732    fn try_from(value: &str) -> Result<Self, Self::Error> {
733        if value.starts_with("pbkdf2_sha256$") {
734            parse_django_password(value)
735        } else if let Some(hash_value) = value.strip_prefix("ipaNTHash: ") {
736            parse_ipanthash(hash_value)
737        } else if let Some(hash_value) = value.strip_prefix("sambaNTPassword: ") {
738            parse_sambantpassword(hash_value)
739        } else if value.starts_with("{") {
740            // Test 389ds/openldap formats. Shout outs openldap which sometimes makes these
741            // lowercase, so we're making them all lowercase!
742
743            // turn {hash_format}hash_value into hash_format and hash_value (and lowercase hash_format)
744            let (hash_format, hash_value) = match value.split_once('}') {
745                Some((format, value)) => (
746                    format.strip_prefix('{').unwrap_or(format).to_lowercase(),
747                    value,
748                ),
749                None => {
750                    return Err(PasswordError::InvalidFormat);
751                }
752            };
753
754            match hash_format.as_str() {
755                // Test for OpenLDAP formats
756                "pbkdf2" | "pbkdf2-sha1" | "pbkdf2-sha256" | "pbkdf2-sha512" => {
757                    parse_pbkdf2(&hash_format, hash_value)
758                }
759
760                "argon2" => parse_argon(hash_value),
761                "crypt" => parse_crypt(hash_value),
762                "sha" => {
763                    let h = general_purpose::STANDARD.decode(hash_value)?;
764                    if h.len() != DS_SHA1_HASH_LEN {
765                        return Err(PasswordError::InvalidSaltLength);
766                    }
767                    Ok(Password {
768                        material: Kdf::SHA1(h.to_vec()),
769                    })
770                }
771                "ssha" => {
772                    let sh = general_purpose::STANDARD.decode(hash_value)?;
773                    let (h, s) = sh
774                        .split_at_checked(DS_SHA1_HASH_LEN)
775                        .ok_or(PasswordError::InvalidLength)?;
776
777                    Ok(Password {
778                        material: Kdf::SSHA1(s.to_vec(), h.to_vec()),
779                    })
780                }
781                "sha256" => {
782                    let h = general_purpose::STANDARD.decode(hash_value)?;
783                    if h.len() != DS_SHA256_HASH_LEN {
784                        return Err(PasswordError::InvalidSaltLength);
785                    }
786                    Ok(Password {
787                        material: Kdf::SHA256(h.to_vec()),
788                    })
789                }
790                "ssha256" => {
791                    let sh = general_purpose::STANDARD.decode(hash_value)?;
792                    let (h, s) = sh
793                        .split_at_checked(DS_SHA256_HASH_LEN)
794                        .ok_or(PasswordError::InvalidLength)?;
795                    Ok(Password {
796                        material: Kdf::SSHA256(s.to_vec(), h.to_vec()),
797                    })
798                }
799                "sha512" => {
800                    let h = general_purpose::STANDARD.decode(hash_value)?;
801                    if h.len() != DS_SHA512_HASH_LEN {
802                        return Err(PasswordError::InvalidSaltLength);
803                    }
804                    Ok(Password {
805                        material: Kdf::SHA512(h.to_vec()),
806                    })
807                }
808                "ssha512" => {
809                    let sh = general_purpose::STANDARD.decode(hash_value)?;
810                    if sh.len() <= DS_SHA512_HASH_LEN {
811                        return Err(PasswordError::InvalidSaltLength);
812                    }
813                    let (h, s) = sh
814                        .split_at_checked(DS_SHA512_HASH_LEN)
815                        .ok_or(PasswordError::InvalidLength)?;
816                    Ok(Password {
817                        material: Kdf::SSHA512(s.to_vec(), h.to_vec()),
818                    })
819                }
820                _ => Err(PasswordError::NoDecoderFound(hash_format)),
821            }
822        } else {
823            Err(PasswordError::NoDecoderFound(
824                value.chars().take(5).collect(),
825            ))
826        }
827    }
828}
829
830impl Password {
831    fn bench_argon2id(params: Params) -> Option<Duration> {
832        let mut rng = rand::rng();
833        let salt: Vec<u8> = (0..ARGON2_SALT_LEN).map(|_| rng.random()).collect();
834        let input: Vec<u8> = (0..ARGON2_SALT_LEN).map(|_| rng.random()).collect();
835        let mut key: Vec<u8> = (0..ARGON2_KEY_LEN).map(|_| 0).collect();
836
837        let argon = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
838
839        let start = Instant::now();
840        argon
841            .hash_password_into(input.as_slice(), salt.as_slice(), key.as_mut_slice())
842            .ok()?;
843        let end = Instant::now();
844
845        end.checked_duration_since(start)
846    }
847
848    pub fn new_pbkdf2(policy: &CryptoPolicy, cleartext: &str) -> Result<Self, CryptoError> {
849        let pbkdf2_cost = policy.pbkdf2_cost;
850        let mut rng = rand::rng();
851        let salt: Vec<u8> = (0..PBKDF2_SALT_LEN).map(|_| rng.random()).collect();
852        let mut key: Vec<u8> = (0..PBKDF2_KEY_LEN).map(|_| 0).collect();
853
854        pbkdf2_hmac(
855            cleartext.as_bytes(),
856            salt.as_slice(),
857            pbkdf2_cost,
858            MessageDigest::sha256(),
859            key.as_mut_slice(),
860        )
861        .map(|()| {
862            // Turn key to a vec.
863            Kdf::PBKDF2(pbkdf2_cost, salt, key)
864        })
865        .map(|material| Password { material })
866        .map_err(|e| e.into())
867    }
868
869    pub fn new_argon2id(policy: &CryptoPolicy, cleartext: &str) -> Result<Self, CryptoError> {
870        let version = Version::V0x13;
871
872        let argon = Argon2::new(Algorithm::Argon2id, version, policy.argon2id_params.clone());
873
874        let mut rng = rand::rng();
875        let salt: Vec<u8> = (0..ARGON2_SALT_LEN).map(|_| rng.random()).collect();
876        let mut key: Vec<u8> = (0..ARGON2_KEY_LEN).map(|_| 0).collect();
877
878        argon
879            .hash_password_into(cleartext.as_bytes(), salt.as_slice(), key.as_mut_slice())
880            .map(|()| Kdf::ARGON2ID {
881                m_cost: policy.argon2id_params.m_cost(),
882                t_cost: policy.argon2id_params.t_cost(),
883                p_cost: policy.argon2id_params.p_cost(),
884                version: version as u32,
885                salt,
886                key,
887            })
888            .map_err(|_| CryptoError::Argon2)
889            .map(|material| Password { material })
890    }
891
892    pub fn new_argon2id_hsm(
893        policy: &CryptoPolicy,
894        cleartext: &str,
895        hsm: &mut dyn TpmHmacS256,
896        hmac_key: &HmacS256Key,
897    ) -> Result<Self, CryptoError> {
898        let version = Version::V0x13;
899
900        let argon = Argon2::new(Algorithm::Argon2id, version, policy.argon2id_params.clone());
901
902        let mut rng = rand::rng();
903        let salt: Vec<u8> = (0..ARGON2_SALT_LEN).map(|_| rng.random()).collect();
904        let mut check_key: Vec<u8> = (0..ARGON2_KEY_LEN).map(|_| 0).collect();
905
906        argon
907            .hash_password_into(
908                cleartext.as_bytes(),
909                salt.as_slice(),
910                check_key.as_mut_slice(),
911            )
912            .map_err(|_| CryptoError::Argon2)
913            .and_then(|()| {
914                hsm.hmac_s256(hmac_key, &check_key)
915                    .map_err(|err| {
916                        error!(?err, "hsm error");
917                        CryptoError::Hsm
918                    })
919                    .map(|hmac_output| hmac_output.into_bytes().to_vec())
920            })
921            .map(|key| Kdf::TPM_ARGON2ID {
922                m_cost: policy.argon2id_params.m_cost(),
923                t_cost: policy.argon2id_params.t_cost(),
924                p_cost: policy.argon2id_params.p_cost(),
925                version: version as u32,
926                salt,
927                key,
928            })
929            .map(|material| Password { material })
930    }
931
932    #[inline]
933    pub fn new(policy: &CryptoPolicy, cleartext: &str) -> Result<Self, CryptoError> {
934        Self::new_argon2id(policy, cleartext)
935    }
936
937    pub fn verify(&self, cleartext: &str) -> Result<bool, CryptoError> {
938        self.verify_ctx(cleartext, None)
939    }
940
941    pub fn verify_ctx(
942        &self,
943        cleartext: &str,
944        hsm: Option<(&mut dyn TpmHmacS256, &HmacS256Key)>,
945    ) -> Result<bool, CryptoError> {
946        match (&self.material, hsm) {
947            (
948                Kdf::TPM_ARGON2ID {
949                    m_cost,
950                    t_cost,
951                    p_cost,
952                    version,
953                    salt,
954                    key,
955                },
956                Some((hsm, hmac_key)),
957            ) => {
958                let version: Version = (*version).try_into().map_err(|_| {
959                    error!("Failed to convert {} to valid argon2id version", version);
960                    CryptoError::Argon2Version
961                })?;
962
963                let key_len = key.len();
964
965                let params =
966                    Params::new(*m_cost, *t_cost, *p_cost, Some(key_len)).map_err(|e| {
967                        error!(err = ?e, "invalid argon2id parameters");
968                        CryptoError::Argon2Parameters
969                    })?;
970
971                let argon = Argon2::new(Algorithm::Argon2id, version, params);
972                let mut check_key: Vec<u8> = (0..key_len).map(|_| 0).collect();
973
974                argon
975                    .hash_password_into(
976                        cleartext.as_bytes(),
977                        salt.as_slice(),
978                        check_key.as_mut_slice(),
979                    )
980                    .map_err(|e| {
981                        error!(err = ?e, "unable to perform argon2id hash");
982                        CryptoError::Argon2
983                    })
984                    .and_then(|()| {
985                        hsm.hmac_s256(hmac_key, &check_key).map_err(|err| {
986                            error!(?err, "hsm error");
987                            CryptoError::Hsm
988                        })
989                    })
990                    .map(|hmac_key| {
991                        // Actually compare the outputs.
992                        hmac_key.into_bytes().as_slice() == key
993                    })
994            }
995            (Kdf::TPM_ARGON2ID { .. }, None) => {
996                error!("Unable to validate password - not hsm context available");
997                Err(CryptoError::HsmContextMissing)
998            }
999            (
1000                Kdf::ARGON2ID {
1001                    m_cost,
1002                    t_cost,
1003                    p_cost,
1004                    version,
1005                    salt,
1006                    key,
1007                },
1008                _,
1009            ) => {
1010                let version: Version = (*version).try_into().map_err(|_| {
1011                    error!("Failed to convert {} to valid argon2id version", version);
1012                    CryptoError::Argon2Version
1013                })?;
1014
1015                let key_len = key.len();
1016
1017                let params =
1018                    Params::new(*m_cost, *t_cost, *p_cost, Some(key_len)).map_err(|e| {
1019                        error!(err = ?e, "invalid argon2id parameters");
1020                        CryptoError::Argon2Parameters
1021                    })?;
1022
1023                let argon = Argon2::new(Algorithm::Argon2id, version, params);
1024                let mut check_key: Vec<u8> = (0..key_len).map(|_| 0).collect();
1025
1026                argon
1027                    .hash_password_into(
1028                        cleartext.as_bytes(),
1029                        salt.as_slice(),
1030                        check_key.as_mut_slice(),
1031                    )
1032                    .map_err(|e| {
1033                        error!(err = ?e, "unable to perform argon2id hash");
1034                        CryptoError::Argon2
1035                    })
1036                    .map(|()| {
1037                        // Actually compare the outputs.
1038                        &check_key == key
1039                    })
1040            }
1041            (Kdf::PBKDF2(cost, salt, key), _) => {
1042                // We have to get the number of bits to derive from our stored hash
1043                // as some imported hash types may have variable lengths
1044                let key_len = key.len();
1045                debug_assert!(key_len >= PBKDF2_MIN_NIST_KEY_LEN);
1046                let mut chal_key: Vec<u8> = (0..key_len).map(|_| 0).collect();
1047                pbkdf2_hmac(
1048                    cleartext.as_bytes(),
1049                    salt.as_slice(),
1050                    *cost,
1051                    MessageDigest::sha256(),
1052                    chal_key.as_mut_slice(),
1053                )
1054                .map(|()| {
1055                    // Actually compare the outputs.
1056                    &chal_key == key
1057                })
1058                .map_err(|e| e.into())
1059            }
1060            (Kdf::PBKDF2_SHA1(cost, salt, key), _) => {
1061                let key_len = key.len();
1062                debug_assert!(key_len >= PBKDF2_SHA1_MIN_KEY_LEN);
1063                let mut chal_key: Vec<u8> = (0..key_len).map(|_| 0).collect();
1064                pbkdf2_hmac(
1065                    cleartext.as_bytes(),
1066                    salt.as_slice(),
1067                    *cost,
1068                    MessageDigest::sha1(),
1069                    chal_key.as_mut_slice(),
1070                )
1071                .map(|()| {
1072                    // Actually compare the outputs.
1073                    &chal_key == key
1074                })
1075                .map_err(|e| e.into())
1076            }
1077            (Kdf::PBKDF2_SHA512(cost, salt, key), _) => {
1078                let key_len = key.len();
1079                debug_assert!(key_len >= PBKDF2_MIN_NIST_KEY_LEN);
1080                let mut chal_key: Vec<u8> = (0..key_len).map(|_| 0).collect();
1081                pbkdf2_hmac(
1082                    cleartext.as_bytes(),
1083                    salt.as_slice(),
1084                    *cost,
1085                    MessageDigest::sha512(),
1086                    chal_key.as_mut_slice(),
1087                )
1088                .map(|()| {
1089                    // Actually compare the outputs.
1090                    &chal_key == key
1091                })
1092                .map_err(|e| e.into())
1093            }
1094            (Kdf::SHA1(key), _) => {
1095                let mut hasher = Sha1::new();
1096                hasher.update(cleartext.as_bytes());
1097                let r = hasher.finish();
1098                Ok(key == &(r.to_vec()))
1099            }
1100            (Kdf::SSHA1(salt, key), _) => {
1101                let mut hasher = Sha1::new();
1102                hasher.update(cleartext.as_bytes());
1103                hasher.update(salt);
1104                let r = hasher.finish();
1105                Ok(key == &(r.to_vec()))
1106            }
1107            (Kdf::SHA256(key), _) => {
1108                let mut hasher = Sha256::new();
1109                hasher.update(cleartext.as_bytes());
1110                let r = hasher.finish();
1111                Ok(key == &(r.to_vec()))
1112            }
1113            (Kdf::SSHA256(salt, key), _) => {
1114                let mut hasher = Sha256::new();
1115                hasher.update(cleartext.as_bytes());
1116                hasher.update(salt);
1117                let r = hasher.finish();
1118                Ok(key == &(r.to_vec()))
1119            }
1120            (Kdf::SHA512(key), _) => {
1121                let mut hasher = Sha512::new();
1122                hasher.update(cleartext.as_bytes());
1123                let r = hasher.finish();
1124                Ok(key == &(r.to_vec()))
1125            }
1126            (Kdf::SSHA512(salt, key), _) => {
1127                let mut hasher = Sha512::new();
1128                hasher.update(cleartext.as_bytes());
1129                hasher.update(salt);
1130                let r = hasher.finish();
1131                Ok(key == &(r.to_vec()))
1132            }
1133            (Kdf::NT_MD4(key), _) => {
1134                // We need to get the cleartext to utf16le for reasons.
1135                let clear_utf16le: Vec<u8> = cleartext
1136                    .encode_utf16()
1137                    .map(|c| c.to_le_bytes())
1138                    .flat_map(|i| i.into_iter())
1139                    .collect();
1140
1141                let mut hasher = Md4::new();
1142                hasher.update(&clear_utf16le);
1143                let chal_key = hasher.finalize();
1144
1145                Ok(chal_key.as_slice() == key)
1146            }
1147            (Kdf::CRYPT_MD5 { s, h }, _) => {
1148                let chal_key = crypt_md5::do_md5_crypt(cleartext.as_bytes(), s);
1149                Ok(chal_key == *h)
1150            }
1151            (Kdf::CRYPT_SHA256 { h }, _) => {
1152                let is_valid = sha_crypt::sha256_check(cleartext, h.as_str()).is_ok();
1153
1154                Ok(is_valid)
1155            }
1156            (Kdf::CRYPT_SHA512 { h }, _) => {
1157                let is_valid = sha_crypt::sha512_check(cleartext, h.as_str()).is_ok();
1158
1159                Ok(is_valid)
1160            }
1161        }
1162    }
1163
1164    pub fn to_dbpasswordv1(&self) -> DbPasswordV1 {
1165        match &self.material {
1166            Kdf::TPM_ARGON2ID {
1167                m_cost,
1168                t_cost,
1169                p_cost,
1170                version,
1171                salt,
1172                key,
1173            } => DbPasswordV1::TPM_ARGON2ID {
1174                m: *m_cost,
1175                t: *t_cost,
1176                p: *p_cost,
1177                v: *version,
1178                s: salt.clone().into(),
1179                k: key.clone().into(),
1180            },
1181            Kdf::ARGON2ID {
1182                m_cost,
1183                t_cost,
1184                p_cost,
1185                version,
1186                salt,
1187                key,
1188            } => DbPasswordV1::ARGON2ID {
1189                m: *m_cost,
1190                t: *t_cost,
1191                p: *p_cost,
1192                v: *version,
1193                s: salt.clone().into(),
1194                k: key.clone().into(),
1195            },
1196            Kdf::PBKDF2(cost, salt, hash) => {
1197                DbPasswordV1::PBKDF2(*cost, salt.clone(), hash.clone())
1198            }
1199            Kdf::PBKDF2_SHA1(cost, salt, hash) => {
1200                DbPasswordV1::PBKDF2_SHA1(*cost, salt.clone(), hash.clone())
1201            }
1202            Kdf::PBKDF2_SHA512(cost, salt, hash) => {
1203                DbPasswordV1::PBKDF2_SHA512(*cost, salt.clone(), hash.clone())
1204            }
1205            Kdf::SHA1(hash) => DbPasswordV1::SHA1(hash.clone()),
1206            Kdf::SSHA1(salt, hash) => DbPasswordV1::SSHA1(salt.clone(), hash.clone()),
1207            Kdf::SHA256(hash) => DbPasswordV1::SHA256(hash.clone()),
1208            Kdf::SSHA256(salt, hash) => DbPasswordV1::SSHA256(salt.clone(), hash.clone()),
1209            Kdf::SHA512(hash) => DbPasswordV1::SHA512(hash.clone()),
1210            Kdf::SSHA512(salt, hash) => DbPasswordV1::SSHA512(salt.clone(), hash.clone()),
1211            Kdf::NT_MD4(hash) => DbPasswordV1::NT_MD4(hash.clone()),
1212            Kdf::CRYPT_MD5 { s, h } => DbPasswordV1::CRYPT_MD5 {
1213                s: s.clone().into(),
1214                h: h.clone().into(),
1215            },
1216            Kdf::CRYPT_SHA256 { h } => DbPasswordV1::CRYPT_SHA256 { h: h.clone() },
1217            Kdf::CRYPT_SHA512 { h } => DbPasswordV1::CRYPT_SHA512 { h: h.clone() },
1218        }
1219    }
1220
1221    pub fn requires_upgrade(&self) -> bool {
1222        match &self.material {
1223            Kdf::ARGON2ID {
1224                m_cost,
1225                t_cost,
1226                p_cost,
1227                version,
1228                salt,
1229                key,
1230            } => {
1231                *version < ARGON2_VERSION ||
1232                salt.len() < ARGON2_SALT_LEN ||
1233                key.len() < ARGON2_KEY_LEN ||
1234                // Can't multi-thread
1235                *p_cost > ARGON2_MAX_P_COST ||
1236                // Likely too long on cpu time.
1237                *t_cost > ARGON2_MAX_T_COST ||
1238                // Too much ram
1239                *m_cost > ARGON2_MAX_RAM_KIB
1240            }
1241            // Only used in unixd today
1242            Kdf::TPM_ARGON2ID { .. } => false,
1243            // All now upgraded to argon2id
1244            Kdf::PBKDF2(_, _, _)
1245            | Kdf::PBKDF2_SHA512(_, _, _)
1246            | Kdf::PBKDF2_SHA1(_, _, _)
1247            | Kdf::SHA1(_)
1248            | Kdf::SSHA1(_, _)
1249            | Kdf::SHA256(_)
1250            | Kdf::SSHA256(_, _)
1251            | Kdf::SHA512(_)
1252            | Kdf::SSHA512(_, _)
1253            | Kdf::NT_MD4(_)
1254            | Kdf::CRYPT_MD5 { .. }
1255            | Kdf::CRYPT_SHA256 { .. }
1256            | Kdf::CRYPT_SHA512 { .. } => true,
1257        }
1258    }
1259}
1260
1261#[cfg(test)]
1262mod tests {
1263    use kanidm_hsm_crypto::{
1264        provider::{SoftTpm, TpmHmacS256},
1265        AuthValue,
1266    };
1267    use std::convert::TryFrom;
1268
1269    use crate::*;
1270
1271    #[test]
1272    fn test_credential_simple() {
1273        let p = CryptoPolicy::minimum();
1274        let c = Password::new(&p, "password").unwrap();
1275        assert!(c.verify("password").unwrap());
1276        assert!(!c.verify("password1").unwrap());
1277        assert!(!c.verify("Password1").unwrap());
1278        assert!(!c.verify("It Works!").unwrap());
1279        assert!(!c.verify("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap());
1280    }
1281
1282    #[test]
1283    fn test_password_pbkdf2() {
1284        let p = CryptoPolicy::minimum();
1285        let c = Password::new_pbkdf2(&p, "password").unwrap();
1286        assert!(c.verify("password").unwrap());
1287        assert!(!c.verify("password1").unwrap());
1288        assert!(!c.verify("Password1").unwrap());
1289    }
1290
1291    #[test]
1292    fn test_password_argon2id() {
1293        let p = CryptoPolicy::minimum();
1294        let c = Password::new_argon2id(&p, "password").unwrap();
1295        assert!(c.verify("password").unwrap());
1296        assert!(!c.verify("password1").unwrap());
1297        assert!(!c.verify("Password1").unwrap());
1298    }
1299
1300    #[test]
1301    fn test_password_from_invalid() {
1302        assert!(Password::try_from("password").is_err())
1303    }
1304
1305    #[test]
1306    fn test_password_from_django_pbkdf2_sha256() {
1307        let im_pw = "pbkdf2_sha256$36000$xIEozuZVAoYm$uW1b35DUKyhvQAf1mBqMvoBDcqSD06juzyO/nmyV0+w=";
1308        let password = "eicieY7ahchaoCh0eeTa";
1309        let r = Password::try_from(im_pw).expect("Failed to parse");
1310        assert!(r.verify(password).unwrap_or(false));
1311    }
1312
1313    #[test]
1314    fn test_password_from_ds_sha1() {
1315        let im_pw = "{SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=";
1316        let _r = Password::try_from(im_pw).expect("Failed to parse");
1317
1318        let im_pw = "{sha}W6ph5Mm5Pz8GgiULbPgzG37mj9g=";
1319        let password = "password";
1320        let r = Password::try_from(im_pw).expect("Failed to parse");
1321
1322        // Known weak, require upgrade.
1323        assert!(r.requires_upgrade());
1324        assert!(r.verify(password).unwrap_or(false));
1325    }
1326
1327    #[test]
1328    fn test_password_from_ds_ssha1() {
1329        let im_pw = "{SSHA}EyzbBiP4u4zxOrLpKTORI/RX3HC6TCTJtnVOCQ==";
1330        let _r = Password::try_from(im_pw).expect("Failed to parse");
1331
1332        let im_pw = "{ssha}EyzbBiP4u4zxOrLpKTORI/RX3HC6TCTJtnVOCQ==";
1333        let password = "password";
1334        let r = Password::try_from(im_pw).expect("Failed to parse");
1335
1336        // Known weak, require upgrade.
1337        assert!(r.requires_upgrade());
1338        assert!(r.verify(password).unwrap_or(false));
1339    }
1340
1341    #[test]
1342    fn test_password_from_ds_sha256() {
1343        let im_pw = "{SHA256}XohImNooBHFR0OVvjcYpJ3NgPQ1qq73WKhHvch0VQtg=";
1344        let _r = Password::try_from(im_pw).expect("Failed to parse");
1345
1346        let im_pw = "{sha256}XohImNooBHFR0OVvjcYpJ3NgPQ1qq73WKhHvch0VQtg=";
1347        let password = "password";
1348        let r = Password::try_from(im_pw).expect("Failed to parse");
1349
1350        // Known weak, require upgrade.
1351        assert!(r.requires_upgrade());
1352        assert!(r.verify(password).unwrap_or(false));
1353    }
1354
1355    #[test]
1356    fn test_password_from_ds_ssha256() {
1357        let im_pw = "{SSHA256}luYWfFJOZgxySTsJXHgIaCYww4yMpu6yest69j/wO5n5OycuHFV/GQ==";
1358        let _r = Password::try_from(im_pw).expect("Failed to parse");
1359
1360        let im_pw = "{ssha256}luYWfFJOZgxySTsJXHgIaCYww4yMpu6yest69j/wO5n5OycuHFV/GQ==";
1361        let password = "password";
1362        let r = Password::try_from(im_pw).expect("Failed to parse");
1363
1364        // Known weak, require upgrade.
1365        assert!(r.requires_upgrade());
1366        assert!(r.verify(password).unwrap_or(false));
1367    }
1368
1369    #[test]
1370    fn test_password_from_ds_sha512() {
1371        let im_pw = "{SHA512}sQnzu7wkTrgkQZF+0G1hi5AI3Qmzvv0bXgc5THBqi7mAsdd4Xll27ASbRt9fEyavWi6m0QP9B8lThf+rDKy8hg==";
1372        let _r = Password::try_from(im_pw).expect("Failed to parse");
1373
1374        let im_pw = "{sha512}sQnzu7wkTrgkQZF+0G1hi5AI3Qmzvv0bXgc5THBqi7mAsdd4Xll27ASbRt9fEyavWi6m0QP9B8lThf+rDKy8hg==";
1375        let password = "password";
1376        let r = Password::try_from(im_pw).expect("Failed to parse");
1377
1378        // Known weak, require upgrade.
1379        assert!(r.requires_upgrade());
1380        assert!(r.verify(password).unwrap_or(false));
1381    }
1382
1383    #[test]
1384    fn test_password_from_ds_ssha512() {
1385        let im_pw = "{SSHA512}JwrSUHkI7FTAfHRVR6KoFlSN0E3dmaQWARjZ+/UsShYlENOqDtFVU77HJLLrY2MuSp0jve52+pwtdVl2QUAHukQ0XUf5LDtM";
1386        let _r = Password::try_from(im_pw).expect("Failed to parse");
1387
1388        let im_pw = "{ssha512}JwrSUHkI7FTAfHRVR6KoFlSN0E3dmaQWARjZ+/UsShYlENOqDtFVU77HJLLrY2MuSp0jve52+pwtdVl2QUAHukQ0XUf5LDtM";
1389        let password = "password";
1390        let r = Password::try_from(im_pw).expect("Failed to parse");
1391
1392        // Known weak, require upgrade.
1393        assert!(r.requires_upgrade());
1394        assert!(r.verify(password).unwrap_or(false));
1395    }
1396
1397    // Can be generated with:
1398    // slappasswd -s password -o module-load=/usr/lib64/openldap/pw-argon2.so -h {ARGON2}
1399
1400    #[test]
1401    fn test_password_from_openldap_pkbdf2() {
1402        let im_pw = "{PBKDF2}10000$IlfapjA351LuDSwYC0IQ8Q$saHqQTuYnjJN/tmAndT.8mJt.6w";
1403        let password = "password";
1404        let r = Password::try_from(im_pw).expect("Failed to parse");
1405        assert!(r.requires_upgrade());
1406        assert!(r.verify(password).unwrap_or(false));
1407    }
1408
1409    #[test]
1410    fn test_password_from_openldap_pkbdf2_sha1() {
1411        let im_pw = "{PBKDF2-SHA1}10000$ZBEH6B07rgQpJSikyvMU2w$TAA03a5IYkz1QlPsbJKvUsTqNV";
1412        let password = "password";
1413        let r = Password::try_from(im_pw).expect("Failed to parse");
1414        assert!(r.requires_upgrade());
1415        assert!(r.verify(password).unwrap_or(false));
1416    }
1417
1418    #[test]
1419    fn test_password_from_openldap_pkbdf2_sha256() {
1420        let im_pw = "{PBKDF2-SHA256}10000$henZGfPWw79Cs8ORDeVNrQ$1dTJy73v6n3bnTmTZFghxHXHLsAzKaAy8SksDfZBPIw";
1421        let password = "password";
1422        let r = Password::try_from(im_pw).expect("Failed to parse");
1423        assert!(r.requires_upgrade());
1424        assert!(r.verify(password).unwrap_or(false));
1425    }
1426
1427    #[test]
1428    fn test_password_from_openldap_pkbdf2_sha512() {
1429        let im_pw = "{PBKDF2-SHA512}10000$Je1Uw19Bfv5lArzZ6V3EPw$g4T/1sqBUYWl9o93MVnyQ/8zKGSkPbKaXXsT8WmysXQJhWy8MRP2JFudSL.N9RklQYgDPxPjnfum/F2f/TrppA";
1430        let password = "password";
1431        let r = Password::try_from(im_pw).expect("Failed to parse");
1432        assert!(r.requires_upgrade());
1433        assert!(r.verify(password).unwrap_or(false));
1434    }
1435
1436    // Not supported in openssl, may need an external crate.
1437    #[test]
1438    fn test_password_from_openldap_argon2() {
1439        sketching::test_init();
1440        let im_pw = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$IyTQMsvzB2JHDiWx8fq7Ew$VhYOA7AL0kbRXI5g2kOyyp8St1epkNj7WZyUY4pAIQQ";
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    /*
1448     * wbrown - 20221104 - I tried to programmatically enable the legacy provider, but
1449     * it consistently "did nothing at all", meaning we have to rely on users to enable
1450     * this for this test.
1451     */
1452
1453    #[test]
1454    fn test_password_from_ipa_nt_hash() {
1455        sketching::test_init();
1456        // Base64 no pad
1457        let im_pw = "ipaNTHash: iEb36u6PsRetBr3YMLdYbA";
1458        let password = "password";
1459        let r = Password::try_from(im_pw).expect("Failed to parse");
1460        assert!(r.requires_upgrade());
1461
1462        assert!(r.verify(password).expect("Failed to hash"));
1463        let im_pw = "ipaNTHash: pS43DjQLcUYhaNF_cd_Vhw==";
1464        Password::try_from(im_pw).expect("Failed to parse");
1465    }
1466
1467    #[test]
1468    fn test_password_from_samba_nt_hash() {
1469        sketching::test_init();
1470        // Base64 no pad
1471        let im_pw = "sambaNTPassword: 8846F7EAEE8FB117AD06BDD830B7586C";
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).expect("Failed to hash"));
1476    }
1477
1478    #[test]
1479    fn test_password_from_crypt_md5() {
1480        sketching::test_init();
1481        let im_pw = "{crypt}$1$zaRIAsoe$7887GzjDTrst0XbDPpF5m.";
1482        let password = "password";
1483        let r = Password::try_from(im_pw).expect("Failed to parse");
1484
1485        assert!(r.requires_upgrade());
1486        assert!(r.verify(password).unwrap_or(false));
1487    }
1488
1489    #[test]
1490    fn test_password_from_crypt_sha256() {
1491        sketching::test_init();
1492        let im_pw = "{crypt}$5$3UzV7Sut8EHCUxlN$41V.jtMQmFAOucqI4ImFV43r.bRLjPlN.hyfoCdmGE2";
1493        let password = "password";
1494        let r = Password::try_from(im_pw).expect("Failed to parse");
1495
1496        assert!(r.requires_upgrade());
1497        assert!(r.verify(password).unwrap_or(false));
1498    }
1499
1500    #[test]
1501    fn test_password_from_crypt_sha512() {
1502        sketching::test_init();
1503        let im_pw = "{crypt}$6$aXn8azL8DXUyuMvj$9aJJC/KEUwygIpf2MTqjQa.f0MEXNg2cGFc62Fet8XpuDVDedM05CweAlxW6GWxnmHqp14CRf6zU7OQoE/bCu0";
1504        let password = "password";
1505        let r = Password::try_from(im_pw).expect("Failed to parse");
1506
1507        assert!(r.requires_upgrade());
1508        assert!(r.verify(password).unwrap_or(false));
1509    }
1510
1511    #[test]
1512    fn test_password_argon2id_hsm_bind() {
1513        sketching::test_init();
1514
1515        let mut hsm: Box<dyn TpmHmacS256> = Box::new(SoftTpm::default());
1516
1517        let auth_value = AuthValue::ephemeral().unwrap();
1518
1519        let loadable_machine_key = hsm.root_storage_key_create(&auth_value).unwrap();
1520        let machine_key = hsm
1521            .root_storage_key_load(&auth_value, &loadable_machine_key)
1522            .unwrap();
1523
1524        let loadable_hmac_key = hsm.hmac_s256_create(&machine_key).unwrap();
1525        let key = hsm
1526            .hmac_s256_load(&machine_key, &loadable_hmac_key)
1527            .unwrap();
1528
1529        let ctx: &mut dyn TpmHmacS256 = &mut *hsm;
1530
1531        let p = CryptoPolicy::minimum();
1532        let c = Password::new_argon2id_hsm(&p, "password", ctx, &key).unwrap();
1533
1534        assert!(matches!(
1535            c.verify("password"),
1536            Err(CryptoError::HsmContextMissing)
1537        ));
1538
1539        // Assert it fails without the hmac
1540        let dup = match &c.material {
1541            Kdf::TPM_ARGON2ID {
1542                m_cost,
1543                t_cost,
1544                p_cost,
1545                version,
1546                salt,
1547                key,
1548            } => Password {
1549                material: Kdf::ARGON2ID {
1550                    m_cost: *m_cost,
1551                    t_cost: *t_cost,
1552                    p_cost: *p_cost,
1553                    version: *version,
1554                    salt: salt.clone(),
1555                    key: key.clone(),
1556                },
1557            },
1558            #[allow(clippy::unreachable)]
1559            _ => unreachable!(),
1560        };
1561
1562        assert!(!dup.verify("password").unwrap());
1563
1564        assert!(c.verify_ctx("password", Some((ctx, &key))).unwrap());
1565        assert!(!c.verify_ctx("password1", Some((ctx, &key))).unwrap());
1566        assert!(!c.verify_ctx("Password1", Some((ctx, &key))).unwrap());
1567    }
1568}