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                // This is the binary version of 389-ds pbkdf2 which we do not support.
753                "pbkdf2_sha256" => Err(PasswordError::InvalidFormat),
754
755                "argon2" => parse_argon(hash_value),
756                "crypt" => parse_crypt(hash_value),
757                "sha" => {
758                    let h = general_purpose::STANDARD.decode(hash_value)?;
759                    if h.len() != DS_SHA1_HASH_LEN {
760                        return Err(PasswordError::InvalidSaltLength);
761                    }
762                    Ok(Password {
763                        material: Kdf::SHA1(h.to_vec()),
764                    })
765                }
766                "ssha" => {
767                    let sh = general_purpose::STANDARD.decode(hash_value)?;
768                    let (h, s) = sh
769                        .split_at_checked(DS_SHA1_HASH_LEN)
770                        .ok_or(PasswordError::InvalidLength)?;
771
772                    Ok(Password {
773                        material: Kdf::SSHA1(s.to_vec(), h.to_vec()),
774                    })
775                }
776                "sha256" => {
777                    let h = general_purpose::STANDARD.decode(hash_value)?;
778                    if h.len() != DS_SHA256_HASH_LEN {
779                        return Err(PasswordError::InvalidSaltLength);
780                    }
781                    Ok(Password {
782                        material: Kdf::SHA256(h.to_vec()),
783                    })
784                }
785                "ssha256" => {
786                    let sh = general_purpose::STANDARD.decode(hash_value)?;
787                    let (h, s) = sh
788                        .split_at_checked(DS_SHA256_HASH_LEN)
789                        .ok_or(PasswordError::InvalidLength)?;
790                    Ok(Password {
791                        material: Kdf::SSHA256(s.to_vec(), h.to_vec()),
792                    })
793                }
794                "sha512" => {
795                    let h = general_purpose::STANDARD.decode(hash_value)?;
796                    if h.len() != DS_SHA512_HASH_LEN {
797                        return Err(PasswordError::InvalidSaltLength);
798                    }
799                    Ok(Password {
800                        material: Kdf::SHA512(h.to_vec()),
801                    })
802                }
803                "ssha512" => {
804                    let sh = general_purpose::STANDARD.decode(hash_value)?;
805                    if sh.len() <= DS_SHA512_HASH_LEN {
806                        return Err(PasswordError::InvalidSaltLength);
807                    }
808                    let (h, s) = sh
809                        .split_at_checked(DS_SHA512_HASH_LEN)
810                        .ok_or(PasswordError::InvalidLength)?;
811                    Ok(Password {
812                        material: Kdf::SSHA512(s.to_vec(), h.to_vec()),
813                    })
814                }
815                _ => Err(PasswordError::NoDecoderFound(hash_format)),
816            }
817        } else {
818            Err(PasswordError::NoDecoderFound(
819                value.chars().take(5).collect(),
820            ))
821        }
822    }
823}
824
825impl Password {
826    fn bench_argon2id(params: Params) -> Option<Duration> {
827        let mut rng = rand::rng();
828        let salt: Vec<u8> = (0..ARGON2_SALT_LEN).map(|_| rng.random()).collect();
829        let input: Vec<u8> = (0..ARGON2_SALT_LEN).map(|_| rng.random()).collect();
830        let mut key: Vec<u8> = (0..ARGON2_KEY_LEN).map(|_| 0).collect();
831
832        let argon = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
833
834        let start = Instant::now();
835        argon
836            .hash_password_into(input.as_slice(), salt.as_slice(), key.as_mut_slice())
837            .ok()?;
838        let end = Instant::now();
839
840        end.checked_duration_since(start)
841    }
842
843    pub fn new_pbkdf2(policy: &CryptoPolicy, cleartext: &str) -> Result<Self, CryptoError> {
844        let pbkdf2_cost = policy.pbkdf2_cost;
845        let mut rng = rand::rng();
846        let salt: Vec<u8> = (0..PBKDF2_SALT_LEN).map(|_| rng.random()).collect();
847        let mut key: Vec<u8> = (0..PBKDF2_KEY_LEN).map(|_| 0).collect();
848
849        pbkdf2_hmac(
850            cleartext.as_bytes(),
851            salt.as_slice(),
852            pbkdf2_cost,
853            MessageDigest::sha256(),
854            key.as_mut_slice(),
855        )
856        .map(|()| {
857            // Turn key to a vec.
858            Kdf::PBKDF2(pbkdf2_cost, salt, key)
859        })
860        .map(|material| Password { material })
861        .map_err(|e| e.into())
862    }
863
864    pub fn new_argon2id(policy: &CryptoPolicy, cleartext: &str) -> Result<Self, CryptoError> {
865        let version = Version::V0x13;
866
867        let argon = Argon2::new(Algorithm::Argon2id, version, policy.argon2id_params.clone());
868
869        let mut rng = rand::rng();
870        let salt: Vec<u8> = (0..ARGON2_SALT_LEN).map(|_| rng.random()).collect();
871        let mut key: Vec<u8> = (0..ARGON2_KEY_LEN).map(|_| 0).collect();
872
873        argon
874            .hash_password_into(cleartext.as_bytes(), salt.as_slice(), key.as_mut_slice())
875            .map(|()| Kdf::ARGON2ID {
876                m_cost: policy.argon2id_params.m_cost(),
877                t_cost: policy.argon2id_params.t_cost(),
878                p_cost: policy.argon2id_params.p_cost(),
879                version: version as u32,
880                salt,
881                key,
882            })
883            .map_err(|_| CryptoError::Argon2)
884            .map(|material| Password { material })
885    }
886
887    pub fn new_argon2id_hsm(
888        policy: &CryptoPolicy,
889        cleartext: &str,
890        hsm: &mut dyn TpmHmacS256,
891        hmac_key: &HmacS256Key,
892    ) -> Result<Self, CryptoError> {
893        let version = Version::V0x13;
894
895        let argon = Argon2::new(Algorithm::Argon2id, version, policy.argon2id_params.clone());
896
897        let mut rng = rand::rng();
898        let salt: Vec<u8> = (0..ARGON2_SALT_LEN).map(|_| rng.random()).collect();
899        let mut check_key: Vec<u8> = (0..ARGON2_KEY_LEN).map(|_| 0).collect();
900
901        argon
902            .hash_password_into(
903                cleartext.as_bytes(),
904                salt.as_slice(),
905                check_key.as_mut_slice(),
906            )
907            .map_err(|_| CryptoError::Argon2)
908            .and_then(|()| {
909                hsm.hmac_s256(hmac_key, &check_key)
910                    .map_err(|err| {
911                        error!(?err, "hsm error");
912                        CryptoError::Hsm
913                    })
914                    .map(|hmac_output| hmac_output.into_bytes().to_vec())
915            })
916            .map(|key| Kdf::TPM_ARGON2ID {
917                m_cost: policy.argon2id_params.m_cost(),
918                t_cost: policy.argon2id_params.t_cost(),
919                p_cost: policy.argon2id_params.p_cost(),
920                version: version as u32,
921                salt,
922                key,
923            })
924            .map(|material| Password { material })
925    }
926
927    #[inline]
928    pub fn new(policy: &CryptoPolicy, cleartext: &str) -> Result<Self, CryptoError> {
929        Self::new_argon2id(policy, cleartext)
930    }
931
932    pub fn verify(&self, cleartext: &str) -> Result<bool, CryptoError> {
933        self.verify_ctx(cleartext, None)
934    }
935
936    pub fn verify_ctx(
937        &self,
938        cleartext: &str,
939        hsm: Option<(&mut dyn TpmHmacS256, &HmacS256Key)>,
940    ) -> Result<bool, CryptoError> {
941        match (&self.material, hsm) {
942            (
943                Kdf::TPM_ARGON2ID {
944                    m_cost,
945                    t_cost,
946                    p_cost,
947                    version,
948                    salt,
949                    key,
950                },
951                Some((hsm, hmac_key)),
952            ) => {
953                let version: Version = (*version).try_into().map_err(|_| {
954                    error!("Failed to convert {} to valid argon2id version", version);
955                    CryptoError::Argon2Version
956                })?;
957
958                let key_len = key.len();
959
960                let params =
961                    Params::new(*m_cost, *t_cost, *p_cost, Some(key_len)).map_err(|e| {
962                        error!(err = ?e, "invalid argon2id parameters");
963                        CryptoError::Argon2Parameters
964                    })?;
965
966                let argon = Argon2::new(Algorithm::Argon2id, version, params);
967                let mut check_key: Vec<u8> = (0..key_len).map(|_| 0).collect();
968
969                argon
970                    .hash_password_into(
971                        cleartext.as_bytes(),
972                        salt.as_slice(),
973                        check_key.as_mut_slice(),
974                    )
975                    .map_err(|e| {
976                        error!(err = ?e, "unable to perform argon2id hash");
977                        CryptoError::Argon2
978                    })
979                    .and_then(|()| {
980                        hsm.hmac_s256(hmac_key, &check_key).map_err(|err| {
981                            error!(?err, "hsm error");
982                            CryptoError::Hsm
983                        })
984                    })
985                    .map(|hmac_key| {
986                        // Actually compare the outputs.
987                        hmac_key.into_bytes().as_slice() == key
988                    })
989            }
990            (Kdf::TPM_ARGON2ID { .. }, None) => {
991                error!("Unable to validate password - not hsm context available");
992                Err(CryptoError::HsmContextMissing)
993            }
994            (
995                Kdf::ARGON2ID {
996                    m_cost,
997                    t_cost,
998                    p_cost,
999                    version,
1000                    salt,
1001                    key,
1002                },
1003                _,
1004            ) => {
1005                let version: Version = (*version).try_into().map_err(|_| {
1006                    error!("Failed to convert {} to valid argon2id version", version);
1007                    CryptoError::Argon2Version
1008                })?;
1009
1010                let key_len = key.len();
1011
1012                let params =
1013                    Params::new(*m_cost, *t_cost, *p_cost, Some(key_len)).map_err(|e| {
1014                        error!(err = ?e, "invalid argon2id parameters");
1015                        CryptoError::Argon2Parameters
1016                    })?;
1017
1018                let argon = Argon2::new(Algorithm::Argon2id, version, params);
1019                let mut check_key: Vec<u8> = (0..key_len).map(|_| 0).collect();
1020
1021                argon
1022                    .hash_password_into(
1023                        cleartext.as_bytes(),
1024                        salt.as_slice(),
1025                        check_key.as_mut_slice(),
1026                    )
1027                    .map_err(|e| {
1028                        error!(err = ?e, "unable to perform argon2id hash");
1029                        CryptoError::Argon2
1030                    })
1031                    .map(|()| {
1032                        // Actually compare the outputs.
1033                        &check_key == key
1034                    })
1035            }
1036            (Kdf::PBKDF2(cost, salt, key), _) => {
1037                // We have to get the number of bits to derive from our stored hash
1038                // as some imported hash types may have variable lengths
1039                let key_len = key.len();
1040                debug_assert!(key_len >= PBKDF2_MIN_NIST_KEY_LEN);
1041                let mut chal_key: Vec<u8> = (0..key_len).map(|_| 0).collect();
1042                pbkdf2_hmac(
1043                    cleartext.as_bytes(),
1044                    salt.as_slice(),
1045                    *cost,
1046                    MessageDigest::sha256(),
1047                    chal_key.as_mut_slice(),
1048                )
1049                .map(|()| {
1050                    // Actually compare the outputs.
1051                    &chal_key == key
1052                })
1053                .map_err(|e| e.into())
1054            }
1055            (Kdf::PBKDF2_SHA1(cost, salt, key), _) => {
1056                let key_len = key.len();
1057                debug_assert!(key_len >= PBKDF2_SHA1_MIN_KEY_LEN);
1058                let mut chal_key: Vec<u8> = (0..key_len).map(|_| 0).collect();
1059                pbkdf2_hmac(
1060                    cleartext.as_bytes(),
1061                    salt.as_slice(),
1062                    *cost,
1063                    MessageDigest::sha1(),
1064                    chal_key.as_mut_slice(),
1065                )
1066                .map(|()| {
1067                    // Actually compare the outputs.
1068                    &chal_key == key
1069                })
1070                .map_err(|e| e.into())
1071            }
1072            (Kdf::PBKDF2_SHA512(cost, salt, key), _) => {
1073                let key_len = key.len();
1074                debug_assert!(key_len >= PBKDF2_MIN_NIST_KEY_LEN);
1075                let mut chal_key: Vec<u8> = (0..key_len).map(|_| 0).collect();
1076                pbkdf2_hmac(
1077                    cleartext.as_bytes(),
1078                    salt.as_slice(),
1079                    *cost,
1080                    MessageDigest::sha512(),
1081                    chal_key.as_mut_slice(),
1082                )
1083                .map(|()| {
1084                    // Actually compare the outputs.
1085                    &chal_key == key
1086                })
1087                .map_err(|e| e.into())
1088            }
1089            (Kdf::SHA1(key), _) => {
1090                let mut hasher = Sha1::new();
1091                hasher.update(cleartext.as_bytes());
1092                let r = hasher.finish();
1093                Ok(key == &(r.to_vec()))
1094            }
1095            (Kdf::SSHA1(salt, key), _) => {
1096                let mut hasher = Sha1::new();
1097                hasher.update(cleartext.as_bytes());
1098                hasher.update(salt);
1099                let r = hasher.finish();
1100                Ok(key == &(r.to_vec()))
1101            }
1102            (Kdf::SHA256(key), _) => {
1103                let mut hasher = Sha256::new();
1104                hasher.update(cleartext.as_bytes());
1105                let r = hasher.finish();
1106                Ok(key == &(r.to_vec()))
1107            }
1108            (Kdf::SSHA256(salt, key), _) => {
1109                let mut hasher = Sha256::new();
1110                hasher.update(cleartext.as_bytes());
1111                hasher.update(salt);
1112                let r = hasher.finish();
1113                Ok(key == &(r.to_vec()))
1114            }
1115            (Kdf::SHA512(key), _) => {
1116                let mut hasher = Sha512::new();
1117                hasher.update(cleartext.as_bytes());
1118                let r = hasher.finish();
1119                Ok(key == &(r.to_vec()))
1120            }
1121            (Kdf::SSHA512(salt, key), _) => {
1122                let mut hasher = Sha512::new();
1123                hasher.update(cleartext.as_bytes());
1124                hasher.update(salt);
1125                let r = hasher.finish();
1126                Ok(key == &(r.to_vec()))
1127            }
1128            (Kdf::NT_MD4(key), _) => {
1129                // We need to get the cleartext to utf16le for reasons.
1130                let clear_utf16le: Vec<u8> = cleartext
1131                    .encode_utf16()
1132                    .map(|c| c.to_le_bytes())
1133                    .flat_map(|i| i.into_iter())
1134                    .collect();
1135
1136                let mut hasher = Md4::new();
1137                hasher.update(&clear_utf16le);
1138                let chal_key = hasher.finalize();
1139
1140                Ok(chal_key.as_slice() == key)
1141            }
1142            (Kdf::CRYPT_MD5 { s, h }, _) => {
1143                let chal_key = crypt_md5::do_md5_crypt(cleartext.as_bytes(), s);
1144                Ok(chal_key == *h)
1145            }
1146            (Kdf::CRYPT_SHA256 { h }, _) => {
1147                let is_valid = sha_crypt::sha256_check(cleartext, h.as_str()).is_ok();
1148
1149                Ok(is_valid)
1150            }
1151            (Kdf::CRYPT_SHA512 { h }, _) => {
1152                let is_valid = sha_crypt::sha512_check(cleartext, h.as_str()).is_ok();
1153
1154                Ok(is_valid)
1155            }
1156        }
1157    }
1158
1159    pub fn to_dbpasswordv1(&self) -> DbPasswordV1 {
1160        match &self.material {
1161            Kdf::TPM_ARGON2ID {
1162                m_cost,
1163                t_cost,
1164                p_cost,
1165                version,
1166                salt,
1167                key,
1168            } => DbPasswordV1::TPM_ARGON2ID {
1169                m: *m_cost,
1170                t: *t_cost,
1171                p: *p_cost,
1172                v: *version,
1173                s: salt.clone().into(),
1174                k: key.clone().into(),
1175            },
1176            Kdf::ARGON2ID {
1177                m_cost,
1178                t_cost,
1179                p_cost,
1180                version,
1181                salt,
1182                key,
1183            } => DbPasswordV1::ARGON2ID {
1184                m: *m_cost,
1185                t: *t_cost,
1186                p: *p_cost,
1187                v: *version,
1188                s: salt.clone().into(),
1189                k: key.clone().into(),
1190            },
1191            Kdf::PBKDF2(cost, salt, hash) => {
1192                DbPasswordV1::PBKDF2(*cost, salt.clone(), hash.clone())
1193            }
1194            Kdf::PBKDF2_SHA1(cost, salt, hash) => {
1195                DbPasswordV1::PBKDF2_SHA1(*cost, salt.clone(), hash.clone())
1196            }
1197            Kdf::PBKDF2_SHA512(cost, salt, hash) => {
1198                DbPasswordV1::PBKDF2_SHA512(*cost, salt.clone(), hash.clone())
1199            }
1200            Kdf::SHA1(hash) => DbPasswordV1::SHA1(hash.clone()),
1201            Kdf::SSHA1(salt, hash) => DbPasswordV1::SSHA1(salt.clone(), hash.clone()),
1202            Kdf::SHA256(hash) => DbPasswordV1::SHA256(hash.clone()),
1203            Kdf::SSHA256(salt, hash) => DbPasswordV1::SSHA256(salt.clone(), hash.clone()),
1204            Kdf::SHA512(hash) => DbPasswordV1::SHA512(hash.clone()),
1205            Kdf::SSHA512(salt, hash) => DbPasswordV1::SSHA512(salt.clone(), hash.clone()),
1206            Kdf::NT_MD4(hash) => DbPasswordV1::NT_MD4(hash.clone()),
1207            Kdf::CRYPT_MD5 { s, h } => DbPasswordV1::CRYPT_MD5 {
1208                s: s.clone().into(),
1209                h: h.clone().into(),
1210            },
1211            Kdf::CRYPT_SHA256 { h } => DbPasswordV1::CRYPT_SHA256 { h: h.clone() },
1212            Kdf::CRYPT_SHA512 { h } => DbPasswordV1::CRYPT_SHA512 { h: h.clone() },
1213        }
1214    }
1215
1216    pub fn requires_upgrade(&self) -> bool {
1217        match &self.material {
1218            Kdf::ARGON2ID {
1219                m_cost,
1220                t_cost,
1221                p_cost,
1222                version,
1223                salt,
1224                key,
1225            } => {
1226                *version < ARGON2_VERSION ||
1227                salt.len() < ARGON2_SALT_LEN ||
1228                key.len() < ARGON2_KEY_LEN ||
1229                // Can't multi-thread
1230                *p_cost > ARGON2_MAX_P_COST ||
1231                // Likely too long on cpu time.
1232                *t_cost > ARGON2_MAX_T_COST ||
1233                // Too much ram
1234                *m_cost > ARGON2_MAX_RAM_KIB
1235            }
1236            // Only used in unixd today
1237            Kdf::TPM_ARGON2ID { .. } => false,
1238            // All now upgraded to argon2id
1239            Kdf::PBKDF2(_, _, _)
1240            | Kdf::PBKDF2_SHA512(_, _, _)
1241            | Kdf::PBKDF2_SHA1(_, _, _)
1242            | Kdf::SHA1(_)
1243            | Kdf::SSHA1(_, _)
1244            | Kdf::SHA256(_)
1245            | Kdf::SSHA256(_, _)
1246            | Kdf::SHA512(_)
1247            | Kdf::SSHA512(_, _)
1248            | Kdf::NT_MD4(_)
1249            | Kdf::CRYPT_MD5 { .. }
1250            | Kdf::CRYPT_SHA256 { .. }
1251            | Kdf::CRYPT_SHA512 { .. } => true,
1252        }
1253    }
1254}
1255
1256#[cfg(test)]
1257mod tests {
1258    use kanidm_hsm_crypto::{
1259        provider::{SoftTpm, TpmHmacS256},
1260        AuthValue,
1261    };
1262    use std::convert::TryFrom;
1263
1264    use crate::*;
1265
1266    #[test]
1267    fn test_credential_simple() {
1268        let p = CryptoPolicy::minimum();
1269        let c = Password::new(&p, "password").unwrap();
1270        assert!(c.verify("password").unwrap());
1271        assert!(!c.verify("password1").unwrap());
1272        assert!(!c.verify("Password1").unwrap());
1273        assert!(!c.verify("It Works!").unwrap());
1274        assert!(!c.verify("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap());
1275    }
1276
1277    #[test]
1278    fn test_password_pbkdf2() {
1279        let p = CryptoPolicy::minimum();
1280        let c = Password::new_pbkdf2(&p, "password").unwrap();
1281        assert!(c.verify("password").unwrap());
1282        assert!(!c.verify("password1").unwrap());
1283        assert!(!c.verify("Password1").unwrap());
1284    }
1285
1286    #[test]
1287    fn test_password_argon2id() {
1288        let p = CryptoPolicy::minimum();
1289        let c = Password::new_argon2id(&p, "password").unwrap();
1290        assert!(c.verify("password").unwrap());
1291        assert!(!c.verify("password1").unwrap());
1292        assert!(!c.verify("Password1").unwrap());
1293    }
1294
1295    #[test]
1296    fn test_password_from_invalid() {
1297        assert!(Password::try_from("password").is_err())
1298    }
1299
1300    #[test]
1301    fn test_password_from_django_pbkdf2_sha256() {
1302        let im_pw = "pbkdf2_sha256$36000$xIEozuZVAoYm$uW1b35DUKyhvQAf1mBqMvoBDcqSD06juzyO/nmyV0+w=";
1303        let password = "eicieY7ahchaoCh0eeTa";
1304        let r = Password::try_from(im_pw).expect("Failed to parse");
1305        assert!(r.verify(password).unwrap_or(false));
1306    }
1307
1308    #[test]
1309    fn test_password_from_ds_sha1() {
1310        let im_pw = "{SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=";
1311        let _r = Password::try_from(im_pw).expect("Failed to parse");
1312
1313        let im_pw = "{sha}W6ph5Mm5Pz8GgiULbPgzG37mj9g=";
1314        let password = "password";
1315        let r = Password::try_from(im_pw).expect("Failed to parse");
1316
1317        // Known weak, require upgrade.
1318        assert!(r.requires_upgrade());
1319        assert!(r.verify(password).unwrap_or(false));
1320    }
1321
1322    #[test]
1323    fn test_password_from_ds_ssha1() {
1324        let im_pw = "{SSHA}EyzbBiP4u4zxOrLpKTORI/RX3HC6TCTJtnVOCQ==";
1325        let _r = Password::try_from(im_pw).expect("Failed to parse");
1326
1327        let im_pw = "{ssha}EyzbBiP4u4zxOrLpKTORI/RX3HC6TCTJtnVOCQ==";
1328        let password = "password";
1329        let r = Password::try_from(im_pw).expect("Failed to parse");
1330
1331        // Known weak, require upgrade.
1332        assert!(r.requires_upgrade());
1333        assert!(r.verify(password).unwrap_or(false));
1334    }
1335
1336    #[test]
1337    fn test_password_from_ds_sha256() {
1338        let im_pw = "{SHA256}XohImNooBHFR0OVvjcYpJ3NgPQ1qq73WKhHvch0VQtg=";
1339        let _r = Password::try_from(im_pw).expect("Failed to parse");
1340
1341        let im_pw = "{sha256}XohImNooBHFR0OVvjcYpJ3NgPQ1qq73WKhHvch0VQtg=";
1342        let password = "password";
1343        let r = Password::try_from(im_pw).expect("Failed to parse");
1344
1345        // Known weak, require upgrade.
1346        assert!(r.requires_upgrade());
1347        assert!(r.verify(password).unwrap_or(false));
1348    }
1349
1350    #[test]
1351    fn test_password_from_ds_ssha256() {
1352        let im_pw = "{SSHA256}luYWfFJOZgxySTsJXHgIaCYww4yMpu6yest69j/wO5n5OycuHFV/GQ==";
1353        let _r = Password::try_from(im_pw).expect("Failed to parse");
1354
1355        let im_pw = "{ssha256}luYWfFJOZgxySTsJXHgIaCYww4yMpu6yest69j/wO5n5OycuHFV/GQ==";
1356        let password = "password";
1357        let r = Password::try_from(im_pw).expect("Failed to parse");
1358
1359        // Known weak, require upgrade.
1360        assert!(r.requires_upgrade());
1361        assert!(r.verify(password).unwrap_or(false));
1362    }
1363
1364    #[test]
1365    fn test_password_from_ds_sha512() {
1366        let im_pw = "{SHA512}sQnzu7wkTrgkQZF+0G1hi5AI3Qmzvv0bXgc5THBqi7mAsdd4Xll27ASbRt9fEyavWi6m0QP9B8lThf+rDKy8hg==";
1367        let _r = Password::try_from(im_pw).expect("Failed to parse");
1368
1369        let im_pw = "{sha512}sQnzu7wkTrgkQZF+0G1hi5AI3Qmzvv0bXgc5THBqi7mAsdd4Xll27ASbRt9fEyavWi6m0QP9B8lThf+rDKy8hg==";
1370        let password = "password";
1371        let r = Password::try_from(im_pw).expect("Failed to parse");
1372
1373        // Known weak, require upgrade.
1374        assert!(r.requires_upgrade());
1375        assert!(r.verify(password).unwrap_or(false));
1376    }
1377
1378    #[test]
1379    fn test_password_from_ds_ssha512() {
1380        let im_pw = "{SSHA512}JwrSUHkI7FTAfHRVR6KoFlSN0E3dmaQWARjZ+/UsShYlENOqDtFVU77HJLLrY2MuSp0jve52+pwtdVl2QUAHukQ0XUf5LDtM";
1381        let _r = Password::try_from(im_pw).expect("Failed to parse");
1382
1383        let im_pw = "{ssha512}JwrSUHkI7FTAfHRVR6KoFlSN0E3dmaQWARjZ+/UsShYlENOqDtFVU77HJLLrY2MuSp0jve52+pwtdVl2QUAHukQ0XUf5LDtM";
1384        let password = "password";
1385        let r = Password::try_from(im_pw).expect("Failed to parse");
1386
1387        // Known weak, require upgrade.
1388        assert!(r.requires_upgrade());
1389        assert!(r.verify(password).unwrap_or(false));
1390    }
1391
1392    // Can be generated with:
1393    // slappasswd -s password -o module-load=/usr/lib64/openldap/pw-argon2.so -h {ARGON2}
1394
1395    #[test]
1396    fn test_password_from_openldap_pkbdf2() {
1397        let im_pw = "{PBKDF2}10000$IlfapjA351LuDSwYC0IQ8Q$saHqQTuYnjJN/tmAndT.8mJt.6w";
1398        let password = "password";
1399        let r = Password::try_from(im_pw).expect("Failed to parse");
1400        assert!(r.requires_upgrade());
1401        assert!(r.verify(password).unwrap_or(false));
1402    }
1403
1404    #[test]
1405    fn test_password_from_openldap_pkbdf2_sha1() {
1406        let im_pw = "{PBKDF2-SHA1}10000$ZBEH6B07rgQpJSikyvMU2w$TAA03a5IYkz1QlPsbJKvUsTqNV";
1407        let password = "password";
1408        let r = Password::try_from(im_pw).expect("Failed to parse");
1409        assert!(r.requires_upgrade());
1410        assert!(r.verify(password).unwrap_or(false));
1411    }
1412
1413    #[test]
1414    fn test_password_from_openldap_pkbdf2_sha256() {
1415        let im_pw = "{PBKDF2-SHA256}10000$henZGfPWw79Cs8ORDeVNrQ$1dTJy73v6n3bnTmTZFghxHXHLsAzKaAy8SksDfZBPIw";
1416        let password = "password";
1417        let r = Password::try_from(im_pw).expect("Failed to parse");
1418        assert!(r.requires_upgrade());
1419        assert!(r.verify(password).unwrap_or(false));
1420    }
1421
1422    #[test]
1423    fn test_password_from_openldap_pkbdf2_sha512() {
1424        let im_pw = "{PBKDF2-SHA512}10000$Je1Uw19Bfv5lArzZ6V3EPw$g4T/1sqBUYWl9o93MVnyQ/8zKGSkPbKaXXsT8WmysXQJhWy8MRP2JFudSL.N9RklQYgDPxPjnfum/F2f/TrppA";
1425        let password = "password";
1426        let r = Password::try_from(im_pw).expect("Failed to parse");
1427        assert!(r.requires_upgrade());
1428        assert!(r.verify(password).unwrap_or(false));
1429    }
1430
1431    // Not supported in openssl, may need an external crate.
1432    #[test]
1433    fn test_password_from_openldap_argon2() {
1434        sketching::test_init();
1435        let im_pw = "{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$IyTQMsvzB2JHDiWx8fq7Ew$VhYOA7AL0kbRXI5g2kOyyp8St1epkNj7WZyUY4pAIQQ";
1436        let password = "password";
1437        let r = Password::try_from(im_pw).expect("Failed to parse");
1438        assert!(!r.requires_upgrade());
1439        assert!(r.verify(password).unwrap_or(false));
1440    }
1441
1442    /*
1443     * wbrown - 20221104 - I tried to programmatically enable the legacy provider, but
1444     * it consistently "did nothing at all", meaning we have to rely on users to enable
1445     * this for this test.
1446     */
1447
1448    #[test]
1449    fn test_password_from_ipa_nt_hash() {
1450        sketching::test_init();
1451        // Base64 no pad
1452        let im_pw = "ipaNTHash: iEb36u6PsRetBr3YMLdYbA";
1453        let password = "password";
1454        let r = Password::try_from(im_pw).expect("Failed to parse");
1455        assert!(r.requires_upgrade());
1456
1457        assert!(r.verify(password).expect("Failed to hash"));
1458        let im_pw = "ipaNTHash: pS43DjQLcUYhaNF_cd_Vhw==";
1459        Password::try_from(im_pw).expect("Failed to parse");
1460    }
1461
1462    #[test]
1463    fn test_password_from_samba_nt_hash() {
1464        sketching::test_init();
1465        // Base64 no pad
1466        let im_pw = "sambaNTPassword: 8846F7EAEE8FB117AD06BDD830B7586C";
1467        let password = "password";
1468        let r = Password::try_from(im_pw).expect("Failed to parse");
1469        assert!(r.requires_upgrade());
1470        assert!(r.verify(password).expect("Failed to hash"));
1471    }
1472
1473    #[test]
1474    fn test_password_from_crypt_md5() {
1475        sketching::test_init();
1476        let im_pw = "{crypt}$1$zaRIAsoe$7887GzjDTrst0XbDPpF5m.";
1477        let password = "password";
1478        let r = Password::try_from(im_pw).expect("Failed to parse");
1479
1480        assert!(r.requires_upgrade());
1481        assert!(r.verify(password).unwrap_or(false));
1482    }
1483
1484    #[test]
1485    fn test_password_from_crypt_sha256() {
1486        sketching::test_init();
1487        let im_pw = "{crypt}$5$3UzV7Sut8EHCUxlN$41V.jtMQmFAOucqI4ImFV43r.bRLjPlN.hyfoCdmGE2";
1488        let password = "password";
1489        let r = Password::try_from(im_pw).expect("Failed to parse");
1490
1491        assert!(r.requires_upgrade());
1492        assert!(r.verify(password).unwrap_or(false));
1493    }
1494
1495    #[test]
1496    fn test_password_from_crypt_sha512() {
1497        sketching::test_init();
1498        let im_pw = "{crypt}$6$aXn8azL8DXUyuMvj$9aJJC/KEUwygIpf2MTqjQa.f0MEXNg2cGFc62Fet8XpuDVDedM05CweAlxW6GWxnmHqp14CRf6zU7OQoE/bCu0";
1499        let password = "password";
1500        let r = Password::try_from(im_pw).expect("Failed to parse");
1501
1502        assert!(r.requires_upgrade());
1503        assert!(r.verify(password).unwrap_or(false));
1504    }
1505
1506    #[test]
1507    fn test_password_argon2id_hsm_bind() {
1508        sketching::test_init();
1509
1510        let mut hsm: Box<dyn TpmHmacS256> = Box::new(SoftTpm::default());
1511
1512        let auth_value = AuthValue::ephemeral().unwrap();
1513
1514        let loadable_machine_key = hsm.root_storage_key_create(&auth_value).unwrap();
1515        let machine_key = hsm
1516            .root_storage_key_load(&auth_value, &loadable_machine_key)
1517            .unwrap();
1518
1519        let loadable_hmac_key = hsm.hmac_s256_create(&machine_key).unwrap();
1520        let key = hsm
1521            .hmac_s256_load(&machine_key, &loadable_hmac_key)
1522            .unwrap();
1523
1524        let ctx: &mut dyn TpmHmacS256 = &mut *hsm;
1525
1526        let p = CryptoPolicy::minimum();
1527        let c = Password::new_argon2id_hsm(&p, "password", ctx, &key).unwrap();
1528
1529        assert!(matches!(
1530            c.verify("password"),
1531            Err(CryptoError::HsmContextMissing)
1532        ));
1533
1534        // Assert it fails without the hmac
1535        let dup = match &c.material {
1536            Kdf::TPM_ARGON2ID {
1537                m_cost,
1538                t_cost,
1539                p_cost,
1540                version,
1541                salt,
1542                key,
1543            } => Password {
1544                material: Kdf::ARGON2ID {
1545                    m_cost: *m_cost,
1546                    t_cost: *t_cost,
1547                    p_cost: *p_cost,
1548                    version: *version,
1549                    salt: salt.clone(),
1550                    key: key.clone(),
1551                },
1552            },
1553            #[allow(clippy::unreachable)]
1554            _ => unreachable!(),
1555        };
1556
1557        assert!(!dup.verify("password").unwrap());
1558
1559        assert!(c.verify_ctx("password", Some((ctx, &key))).unwrap());
1560        assert!(!c.verify_ctx("password1", Some((ctx, &key))).unwrap());
1561        assert!(!c.verify_ctx("Password1", Some((ctx, &key))).unwrap());
1562    }
1563}