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