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