use crate::ctx::Context; use crate::err::Error; use crate::sql::value::Value; use md5::Digest; use md5::Md5; use sha1::Sha1; use sha2::Sha256; use sha2::Sha512; pub fn md5(_: &Context, mut args: Vec) -> Result { let mut hasher = Md5::new(); hasher.update(args.remove(0).as_string().as_str()); let val = hasher.finalize(); let val = format!("{:x}", val); Ok(val.into()) } pub fn sha1(_: &Context, mut args: Vec) -> Result { let mut hasher = Sha1::new(); hasher.update(args.remove(0).as_string().as_str()); let val = hasher.finalize(); let val = format!("{:x}", val); Ok(val.into()) } pub fn sha256(_: &Context, mut args: Vec) -> Result { let mut hasher = Sha256::new(); hasher.update(args.remove(0).as_string().as_str()); let val = hasher.finalize(); let val = format!("{:x}", val); Ok(val.into()) } pub fn sha512(_: &Context, mut args: Vec) -> Result { let mut hasher = Sha512::new(); hasher.update(args.remove(0).as_string().as_str()); let val = hasher.finalize(); let val = format!("{:x}", val); Ok(val.into()) } /// Allowed to cost this much more than default setting for each hash function. const COST_ALLOWANCE: u32 = 4; /// Like verify_password, but takes a closure to determine whether the cost of performing the /// operation is not too high. macro_rules! bounded_verify_password { ($algo: ident, $instance: expr, $password: expr, $hash: expr, $bound: expr) => { if let (Some(salt), Some(expected_output)) = (&$hash.salt, &$hash.hash) { if let Some(params) = <$algo as PasswordHasher>::Params::try_from($hash).ok().filter($bound) { if let Ok(computed_hash) = $instance.hash_password_customized( $password.as_ref(), Some($hash.algorithm), $hash.version, params, *salt, ) { if let Some(computed_output) = &computed_hash.hash { expected_output == computed_output } else { false } } else { false } } else { false } } else { false } }; ($algo: ident, $password: expr, $hash: expr, $bound: expr) => { bounded_verify_password!($algo, $algo::default(), $password, $hash, $bound) }; } pub mod argon2 { use super::COST_ALLOWANCE; use crate::ctx::Context; use crate::err::Error; use crate::sql::value::Value; use argon2::{ password_hash::{PasswordHash, PasswordHasher, SaltString}, Argon2, }; use rand::rngs::OsRng; pub fn cmp(_: &Context, args: Vec) -> Result { let args: [Value; 2] = args.try_into().unwrap(); let [hash, pass] = args.map(Value::as_string); type Params<'a> = as PasswordHasher>::Params; Ok(PasswordHash::new(&hash) .ok() .filter(|test| { bounded_verify_password!(Argon2, pass, test, |params: &Params| { params.m_cost() <= Params::DEFAULT_M_COST.saturating_mul(COST_ALLOWANCE) && params.t_cost() <= Params::DEFAULT_T_COST.saturating_mul(COST_ALLOWANCE) && params.p_cost() <= Params::DEFAULT_P_COST.saturating_mul(COST_ALLOWANCE) }) }) .is_some() .into()) } pub fn gen(_: &Context, mut args: Vec) -> Result { let algo = Argon2::default(); let pass = args.remove(0).as_string(); let salt = SaltString::generate(&mut OsRng); let hash = algo.hash_password(pass.as_ref(), salt.as_ref()).unwrap().to_string(); Ok(hash.into()) } } pub mod pbkdf2 { use super::COST_ALLOWANCE; use crate::ctx::Context; use crate::err::Error; use crate::sql::value::Value; use pbkdf2::{ password_hash::{PasswordHash, PasswordHasher, SaltString}, Pbkdf2, }; use rand::rngs::OsRng; pub fn cmp(_: &Context, args: Vec) -> Result { let args: [Value; 2] = args.try_into().unwrap(); let [hash, pass] = args.map(Value::as_string); type Params = ::Params; Ok(PasswordHash::new(&hash) .ok() .filter(|test| { bounded_verify_password!(Pbkdf2, Pbkdf2, pass, test, |params: &Params| { params.rounds <= Params::default().rounds.saturating_mul(COST_ALLOWANCE) && params.output_length <= Params::default() .output_length .saturating_mul(COST_ALLOWANCE as usize) }) }) .is_some() .into()) } pub fn gen(_: &Context, mut args: Vec) -> Result { let pass = args.remove(0).as_string(); let salt = SaltString::generate(&mut OsRng); let hash = Pbkdf2.hash_password(pass.as_ref(), salt.as_ref()).unwrap().to_string(); Ok(hash.into()) } } pub mod scrypt { use crate::ctx::Context; use crate::err::Error; use crate::sql::value::Value; use rand::rngs::OsRng; use scrypt::{ password_hash::{PasswordHash, PasswordHasher, SaltString}, Scrypt, }; pub fn cmp(_: &Context, args: Vec) -> Result { let args: [Value; 2] = args.try_into().unwrap(); let [hash, pass] = args.map(Value::as_string); type Params = ::Params; Ok(PasswordHash::new(&hash) .ok() .filter(|test| { bounded_verify_password!(Scrypt, Scrypt, pass, test, |params: &Params| { // Scrypt is slow, use lower cost allowance. params.log_n() <= Params::default().log_n().saturating_add(2) && params.r() <= Params::default().r().saturating_mul(2) && params.p() <= Params::default().p().saturating_mul(4) }) }) .is_some() .into()) } pub fn gen(_: &Context, mut args: Vec) -> Result { let pass = args.remove(0).as_string(); let salt = SaltString::generate(&mut OsRng); let hash = Scrypt.hash_password(pass.as_ref(), salt.as_ref()).unwrap().to_string(); Ok(hash.into()) } }