kanidm_lib_crypto/
lib.rs

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