Skip to main content

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