//! `Primitive` in `libpasta` refers to the raw hashing algorithms as //! implemented in many libraries. //! //! The main algorithms here are re-exported for general use. //! Each algorithm has a `new` and `default` function. The former can //! be provided parameters and creates a new dynamic instance of that //! parameter set. Whereas the latter refers to a statically referenced //! parameter set. //! //! All implementations are wrapped in a `Primitive` struct, //! which in effect works like a trait, since it derefs to a `PrimitiveImpl`. //! This means that whether using a new or default parameter set, the overall //! behaviour is equivalent. /// `Argon2` implementations /// /// Currently only a native Rust implementation through `argon2rs`. mod argon2; pub use self::argon2::Argon2; /// `Bcrypt` implementations /// /// Currently uses `rust_crypto`s `bcrypt` algorithm. mod bcrypt; pub use self::bcrypt::Bcrypt; /// `HMAC` implementations /// /// Uses `ring::hmac` to provide an HMAC implementation. Key must either be /// passed using `Hmac::with_key` or will be generated randomly with `Hmac::new`. /// Still need to consider the best way to maintain keys for an application. /// Perhaps need some kind of "key service" module. mod hmac; pub use self::hmac::Hmac; /// `PBKDF2` implementations. /// /// Implementations are from both `ring` and the C `fastpbkdf2` implementations. /// The latter is currently in use. mod pbkdf2; pub use self::pbkdf2::{Pbkdf2, RingPbkdf2}; /// `Scrypt` implementations. /// /// Currently uses `ring_pwhash` for the implementation. mod scrypt; pub use self::scrypt::Scrypt; use sod::Sod; use config; use itertools::Itertools; use itertools::FoldWhile::{Continue, Done}; use num_traits; use num_traits::FromPrimitive; use ring::{constant_time, digest}; use serde_mcf::{Hashes, Map, Value}; use std::cmp::Ordering; use std::fmt; use std::fmt::Write; use std::ops::Deref; use std::sync::Arc; /// Password hashing primitives /// /// Each variant is backed up by different implementation. /// Internally, primitives can either be static values, for example, /// the `lazy_static` generated value `DEFAULT_PRIM`, or dynamically allocated /// variables, which are `Arc<Box<...>>`. /// /// Most operations are expected to be performed using the static functions, /// since most use the default algorithms. However, the flexibilty to support /// arbitrary parameter sets is essential. #[derive(Clone, PartialEq, PartialOrd)] pub struct Primitive(pub Sod<PrimitiveImpl>); impl fmt::Debug for Primitive { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self.0.deref()) } } /// Trait defining the functionality of a hashing primitive. pub trait PrimitiveImpl: fmt::Debug + Send + Sync { /// Compute the output of the primitive with input `password` and `salt`. fn compute(&self, password: &[u8], salt: &[u8]) -> Vec<u8>; /// Verify the password and salt against the hash. /// /// In many cases, this just checks whether /// `compute(password, salt) == hash`. fn verify(&self, password: &[u8], salt: &[u8], hash: &[u8]) -> bool { constant_time::verify_slices_are_equal(&self.compute(password, salt), hash).is_ok() } /// Output the parameters of the primitive as a list of tuples. fn params_as_vec(&self) -> Vec<(&'static str, String)>; /// Return algorithm type as a MCF-compatible hash identifier. fn hash_id(&self) -> Hashes; /// Use the supplied `Config` to update the current `Primitive` with /// a new key source. fn update_key(&self, _config: &config::Config) -> Option<Primitive> { None } } impl<P: PrimitiveImpl + 'static> From<P> for Primitive { fn from(other: P) -> Self { Primitive(Sod::Dynamic(Arc::new(Box::new(other)))) } } impl PartialEq<PrimitiveImpl> for PrimitiveImpl { fn eq(&self, other: &PrimitiveImpl) -> bool { self.hash_id() == other.hash_id() && self.params_as_vec() == other.params_as_vec() } } /// Compare two primitive parameterisations by first checking for equality of /// the hash identifiers, and then attempting to compare the parameters /// numerically. impl PartialOrd<PrimitiveImpl> for PrimitiveImpl { fn partial_cmp(&self, other: &PrimitiveImpl) -> Option<Ordering> { if self.hash_id() == other.hash_id() { self.params_as_vec() .iter() .zip(other.params_as_vec().iter()) .map(|(x, y)| if x == y { Some(Ordering::Equal) } else if x.0 != y.0 { None } else if let Ok(x) = x.1.parse::<f64>() { if let Ok(y) = y.1.parse::<f64>() { x.partial_cmp(&y) } else { None } } else { None }) .fold_while(None, |acc, c| if acc.is_none() { Continue(c) } else if c == acc || c == Some(Ordering::Equal) { Continue(acc) } else { Done(None) }) .into_inner() } else { None } } } impl Deref for Primitive { type Target = Sod<PrimitiveImpl>; fn deref(&self) -> &Sod<PrimitiveImpl> { &self.0 } } #[derive(Debug, PartialEq, PartialOrd)] pub(crate) struct Poisoned; impl PrimitiveImpl for Poisoned { fn compute(&self, _password: &[u8], _salt: &[u8]) -> Vec<u8> { unreachable!() } fn verify(&self, _password: &[u8], _salt: &[u8], _hash: &[u8]) -> bool { unreachable!() } fn params_as_vec(&self) -> Vec<(&'static str, String)> { vec![("poisoned", "".to_string())] } fn hash_id(&self) -> Hashes { Hashes::Custom } } /// Helper macro to unwrap the value or early return with `Poisoned`. /// Necessary until `TryFrom` stabilises. macro_rules! try_or_poisoned { ($f:expr) => ( match $f { Some(x) => x, None => return Poisoned.into() } ) } /// This will be `TryFrom` when it stabilises. /// For now we just return a `Poisoned` impl<'a> From<(&'a Hashes, &'a Map<String, Value>)> for Primitive { fn from(other: (&Hashes, &Map<String, Value>)) -> Self { match *other.0 { Hashes::Argon2i | Hashes::Argon2d => { let passes = try_or_poisoned!(other.1.get("t").and_then(value_as_int)); let lanes = try_or_poisoned!(other.1.get("p").and_then(value_as_int)); let kib = try_or_poisoned!(other.1.get("m").and_then(value_as_int)); Argon2::new(passes, lanes, kib) } Hashes::BcryptMcf => { let cost = try_or_poisoned!(other.1.get("cost").and_then(value_as_int)); Bcrypt::new(cost) } Hashes::Hmac => { let hash_id = try_or_poisoned!(other.1.get("h").and_then(Value::as_str)); let key_id = try_or_poisoned!(other.1.get("key_id").and_then(Value::as_str)); Hmac::with_key_id(hash_from_id(hash_id), key_id) } ref x @ Hashes::Pbkdf2Sha1 | ref x @ Hashes::Pbkdf2Sha256 | ref x @ Hashes::Pbkdf2Sha512 => { let iterations = try_or_poisoned!(other.1.get("n").and_then(value_as_int)); match *x { Hashes::Pbkdf2Sha1 => pbkdf2::Pbkdf2::new(iterations, &digest::SHA1), Hashes::Pbkdf2Sha256 => pbkdf2::Pbkdf2::new(iterations, &digest::SHA256), Hashes::Pbkdf2Sha512 => pbkdf2::Pbkdf2::new(iterations, &digest::SHA512), _ => Poisoned.into() // not actually be possible due to previous matching, } } Hashes::Scrypt => { let log_n = try_or_poisoned!(other.1.get("ln").and_then(value_as_int)); let r = try_or_poisoned!(other.1.get("r").and_then(value_as_int)); let p = try_or_poisoned!(other.1.get("p").and_then(value_as_int)); Scrypt::new(log_n, r, p) } _ => Poisoned.into(), } } } fn value_as_int<T>(val: &Value) -> Option<T> where T: num_traits::Num + FromPrimitive { match *val { Value::Number(ref x) => { if let Some(x) = x.as_u64() { T::from_u64(x) } else { None } } Value::String(ref s) => T::from_str_radix(s.as_str(), 10).ok(), _ => None, } } impl<'a> From<&'a Primitive> for (Hashes, Map<String, Value>) { fn from(other: &Primitive) -> Self { let mut map = Map::new(); for (key, value) in other.0.params_as_vec() { let _ = map.insert(key.to_string(), Value::String(value)); } (other.0.hash_id(), map) } } fn hash_to_id(algorithm: &'static digest::Algorithm) -> String { let mut name = String::new(); #[allow(use_debug)] write!(&mut name, "{:?}", algorithm).expect("error writing to String"); name } fn hash_from_id(id: &str) -> &'static digest::Algorithm { match id { "SHA1" => &digest::SHA1, "SHA256" => &digest::SHA256, "SHA384" => &digest::SHA384, "SHA512" => &digest::SHA512, "SHA512_256" => &digest::SHA512_256, _ => panic!("Unknown digest algorithm"), } } #[cfg(test)] mod test { use super::*; #[test] fn test_comparisons() { let bcrypt = Bcrypt::new(10); let bcrypt_better = Bcrypt::new(20); let scrypt = Scrypt::new(10, 8, 1); let scrypt_better = Scrypt::new(14, 8, 1); let scrypt_diff = Scrypt::new(15, 4, 1); assert_eq!(bcrypt, bcrypt); assert_eq!(scrypt, scrypt); assert_eq!(bcrypt.partial_cmp(&bcrypt_better), Some(Ordering::Less)); assert!(scrypt < scrypt_better); assert_eq!(scrypt.partial_cmp(&scrypt_diff), None); assert_eq!(scrypt_better.partial_cmp(&scrypt_diff), None); assert_eq!(scrypt.partial_cmp(&bcrypt), None); } }