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