#![cfg_attr(all(feature = "bench", test), feature(test))]
#![cfg_attr(all(feature = "bench", test), allow(unstable_features))]
extern crate data_encoding;
#[macro_use]
extern crate error_chain;
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate log;
extern crate num_traits;
extern crate ring;
#[macro_use]
extern crate serde;
extern crate serde_mcf;
extern crate serde_yaml;
pub mod rpassword {
extern crate rpassword;
pub use self::rpassword::*;
}
#[allow(deprecated)]
pub mod errors {
use ring;
use serde_mcf;
use std::{fmt, result};
error_chain! {
foreign_links {
Deserialize(serde_mcf::errors::Error) #[doc = "Errors from de/serializing MCF password hashes."] ;
Ring(ring::error::Unspecified) #[doc = "Errors originating from `ring`"] ;
}
}
pub trait ExpectReport {
type Inner;
fn expect_report(self, msg: &str) -> Self::Inner;
}
impl<T, E: fmt::Debug> ExpectReport for result::Result<T, E> {
type Inner = T;
fn expect_report(self, msg: &str) -> T {
self.unwrap_or_else(|_| {
panic!(
"{}\nIf you are seeing this message, you have encountered \
a situation we did not think was possible. Please submit a bug \
report at https://github.com/libpasta/libpasta/issues with this message.\n",
msg
)
})
}
}
impl<T> ExpectReport for Option<T> {
type Inner = T;
fn expect_report(self, msg: &str) -> T {
self.unwrap_or_else(|| {
panic!(
"{}\nIf you are seeing this message, you have encountered\
a situation we did not think was possible. Please submit a bug\
report at https://github.com/libpasta/libpasta/issues with this message.\n",
msg
)
})
}
}
}
use errors::Result;
use ring::rand::SecureRandom;
#[macro_use]
mod bench;
pub mod config;
pub use config::Config;
pub mod hashing;
pub mod key;
use hashing::Output;
pub mod primitives;
pub mod sod;
#[must_use]
pub fn hash_password(password: &str) -> String {
config::DEFAULT_CONFIG.hash_password(password)
}
#[doc(hidden)]
pub fn hash_password_safe(password: &str) -> Result<String> {
config::DEFAULT_CONFIG.hash_password_safe(password)
}
#[must_use]
pub fn verify_password(hash: &str, password: &str) -> bool {
verify_password_safe(hash, password).unwrap_or(false)
}
#[doc(hidden)]
pub fn verify_password_safe(hash: &str, password: &str) -> Result<bool> {
let pwd_hash: Output = serde_mcf::from_str(hash)?;
Ok(pwd_hash.verify(password))
}
#[derive(Debug, PartialEq)]
#[must_use]
pub enum HashUpdate {
Verified(Option<String>),
Failed,
}
pub fn verify_password_update_hash(hash: &str, password: &str) -> HashUpdate {
config::DEFAULT_CONFIG.verify_password_update_hash(hash, password)
}
#[doc(hidden)]
pub fn verify_password_update_hash_safe(hash: &str, password: &str) -> Result<HashUpdate> {
config::DEFAULT_CONFIG.verify_password_update_hash_safe(hash, password)
}
#[must_use]
pub fn migrate_hash(hash: &str) -> Option<String> {
config::DEFAULT_CONFIG.migrate_hash(hash)
}
#[doc(hidden)]
pub fn migrate_hash_safe(hash: &str) -> Result<Option<String>> {
config::DEFAULT_CONFIG.migrate_hash_safe(hash)
}
fn gen_salt(rng: &dyn SecureRandom) -> Vec<u8> {
let mut salt = vec![0_u8; 16];
if rng.fill(&mut salt).is_ok() {
salt
} else {
error!(
"failed to get fresh randomness, relying on backup seed to generate pseudoranom output"
);
config::backup_gen_salt()
}
}
#[cfg(test)]
use ring::rand::SystemRandom;
#[cfg(test)]
fn get_salt() -> Vec<u8> {
gen_salt(&SystemRandom::new())
}
#[cfg(test)]
mod api_tests {
#![allow(clippy::clippy::shadow_unrelated)]
#![allow(clippy::non_ascii_literal)]
use super::*;
use config::DEFAULT_PRIM;
use hashing::{Algorithm, Output};
use primitives::{Bcrypt, Hmac};
#[test]
fn sanity_check() {
let password = "";
let hash = hash_password(password);
println!("Hash: {:?}", hash);
let password = "";
assert!(verify_password(&hash, password));
assert!(!verify_password(&hash, "wrong password"));
let password = "hunter2";
let hash = hash_password(password);
let password = "hunter2";
assert!(verify_password(&hash, password));
assert!(!verify_password(&hash, "wrong password"));
}
#[test]
fn external_check() {
let password = "hunter2";
let hash = "$2a$10$u.Fhlm/a1DpHr/z5KrsLG.iZ7iM9r8DInJvZ57VArRKuhlHAoVZOi";
let pwd_hash: Output = serde_mcf::from_str(hash).unwrap();
println!("{:?}", pwd_hash);
let expected_hash = pwd_hash
.alg
.hash_with_salt(password.as_bytes(), &pwd_hash.salt);
assert_eq!(pwd_hash.hash, &expected_hash[..]);
assert!(verify_password(&hash, password));
}
#[test]
fn emoji_password() {
let password = "emojisaregreat💖💖💖";
let hash = hash_password(password);
assert!(verify_password(&hash, password));
}
#[test]
fn nested_hash() {
let password = "hunter2";
let fast_prim = Bcrypt::new(5);
let params = Algorithm::Nested {
inner: Box::new(Algorithm::Single(fast_prim.clone())),
outer: fast_prim,
};
let hash = params.hash(&password);
let password = "hunter2";
println!("{:?}", hash);
assert!(hash.verify(&password));
let password = "hunter2";
let hash = serde_mcf::to_string(&hash).unwrap();
println!("{:?}", hash);
let hash_output: Output = serde_mcf::from_str(&hash).unwrap();
println!("{:?}", hash_output);
assert!(verify_password(&hash, password));
}
#[test]
fn verify_update() {
let password = "hunter2";
let params = Algorithm::Nested {
inner: Box::new(Algorithm::default()),
outer: DEFAULT_PRIM.clone(),
};
let hash = params.hash(&password);
let password = "hunter2";
assert!(hash.verify(&password));
let password = "hunter2";
let hash = serde_mcf::to_string(&hash).unwrap();
assert!(verify_password(&hash, password));
}
#[test]
fn migrate() {
let password = "hunter2";
let params = Algorithm::Single(Bcrypt::new(5));
let mut hash = serde_mcf::to_string(¶ms.hash(&password)).unwrap();
println!("Original: {:?}", hash);
if let Some(new_hash) = migrate_hash(&hash) {
hash = new_hash;
}
println!("Migrated: {:?}", hash);
assert!(verify_password(&hash, password));
if let HashUpdate::Verified(Some(new_hash)) = verify_password_update_hash(&hash, password) {
let mut pwd_hash: Output = serde_mcf::from_str(&new_hash).unwrap();
pwd_hash.alg = Algorithm::default();
assert!(pwd_hash.verify(&password));
} else {
panic!("hash was not verified/migrated");
}
}
#[test]
fn handles_broken_hashes() {
let password = "hunter2";
let hash =
"$$scrypt$ln=14p=1$Yw/fI4D7b2PNqpUCg5UzKA$kp6humqf/GUV+6HQ/jND3gd8Zoz4VyBgGqk4DHt+k5c";
assert!(!verify_password(&hash, password));
let hash =
"$$nocrypt$ln=14p=1$Yw/fI4D7b2PNqpUCg5UzKA$kp6humqf/GUV+6HQ/jND3gd8Zoz4VyBgGqk4DHt+k5c";
assert!(!verify_password(&hash, password));
let hash = "$$scrypt$ln=14p=1$$kp6humqf/GUV+6HQ/jND3gd8Zoz4VyBgGqk4DHt+k5c";
assert!(!verify_password(&hash, password));
let hash = "$$scrypt$ln=14p=1$kp6humqf/GUV+6HQ/jND3gd8Zoz4VyBgGqk4DHt+k5c";
assert!(!verify_password(&hash, password));
let hash = "$$scrypt$ln=14,r=8,\
p=1$Yw/fI4D7b2PNqpUCg5UzKA$kp6humqf/GUV+6HQ/jND3gd8Zoz4VyBgGqk4DHt";
assert!(!verify_password(&hash, password));
let hash = "$$scrypt$ln=14,r=8,\
p=1$Yw/fI4D7b2PNqpUCg5UzKA$kp6humqf/GUV+6HQ/jND3gd8Zoz4VyBgGqk4DHt+k5cAAAA";
assert!(!verify_password(&hash, password));
}
#[test]
fn migrate_hash_ok() {
let hash = "$2a$10$175ikf/E6E.73e83.fJRbODnYWBwmfS0ENdzUBZbedUNGO.99wJfa".to_owned();
let new_hash = migrate_hash(&hash).unwrap();
assert!(new_hash != hash);
assert!(migrate_hash(&new_hash).is_none());
}
#[test]
fn vpuh_ok() {
let password = "hunter2";
let cfg = Config::with_primitive(Bcrypt::default());
let hash = cfg.hash_password(password);
let res = verify_password_update_hash(&hash, "hunter2");
let hash = match res {
HashUpdate::Verified(Some(x)) => x,
_ => panic!("should have migrated"),
};
assert_eq!(
verify_password_update_hash(&hash, "hunter2"),
HashUpdate::Verified(None)
);
assert_eq!(
verify_password_update_hash(&hash, "*******"),
HashUpdate::Failed
);
}
#[test]
fn hash_and_key() {
let password = "hunter2";
let alg = Algorithm::Single(Bcrypt::default()).into_wrapped(Hmac::default());
let hash = serde_mcf::to_string(&alg.hash(&password)).unwrap();
assert!(verify_password(&hash, password));
}
}