kanidm_lib_crypto/
lib.rs

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