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