use chbs::{config::BasicConfig, word::WordSampler};
use rand::Rng;
use secrecy::{ExposeSecret, SecretString};
use zxcvbn::{zxcvbn, Entropy};
use crate::{crypto::csprng, Result};
pub mod diceware {
use crate::{Error, Result};
use chbs::{
config::{BasicConfig, BasicConfigBuilder},
prelude::*,
probability::Probability,
word::{WordList, WordSampler},
};
use secrecy::{Secret, SecretString};
use once_cell::sync::Lazy;
static WORD_LIST: Lazy<WordList> = Lazy::new(WordList::builtin_eff_large);
pub fn generate_passphrase_config(
config: &BasicConfig<WordSampler>,
) -> Result<(SecretString, f64)> {
if config.words < 6 {
return Err(Error::DicewareWordsTooFew(config.words, 6));
}
let scheme = config.to_scheme();
Ok((Secret::new(scheme.generate()), scheme.entropy().bits()))
}
pub fn generate_passphrase_words(
words: usize,
) -> Result<(SecretString, f64)> {
let config = default_config(words);
generate_passphrase_config(&config)
}
pub fn generate_passphrase() -> Result<(SecretString, f64)> {
generate_passphrase_words(6)
}
pub fn default_config(words: usize) -> BasicConfig<WordSampler> {
let config = BasicConfigBuilder::default()
.word_provider(WORD_LIST.sampler())
.words(words)
.separator(' ')
.capitalize_first(Probability::Never)
.capitalize_words(Probability::Never)
.build()
.unwrap();
config
}
}
const ROMAN_LOWER: &str = "abcdefghijklmnopqrstuvwxyz";
const ROMAN_UPPER: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const DIGITS: &str = "0123456789";
const PUNCTUATION: &str = "!\"#$%&'()*+,-./:;<=>?@`~\\]^_{}";
pub fn measure_entropy(password: &str, user_inputs: &[&str]) -> Entropy {
zxcvbn(password, user_inputs)
}
#[derive(Debug, Clone)]
pub struct PasswordResult {
pub password: SecretString,
pub entropy: Entropy,
}
#[derive(Debug, Clone)]
pub struct PasswordGen {
length: usize,
characters: Vec<&'static str>,
diceware: Option<BasicConfig<WordSampler>>,
}
impl PasswordGen {
pub fn new(length: usize) -> Self {
Self {
length,
characters: vec![],
diceware: None,
}
}
pub fn new_alpha(length: usize) -> Self {
Self::new(length).upper().lower()
}
pub fn new_numeric(length: usize) -> Self {
Self::new(length).numeric()
}
pub fn new_alpha_numeric(length: usize) -> Self {
Self::new(length).upper().lower().numeric()
}
pub fn new_ascii_printable(length: usize) -> Self {
Self::new(length)
.upper()
.lower()
.numeric()
.ascii_printable()
}
pub fn new_diceware(length: usize) -> Self {
Self::new(length).diceware()
}
pub fn len(&self) -> usize {
self.length
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn lower(mut self) -> Self {
self.characters.push(ROMAN_LOWER);
self
}
pub fn upper(mut self) -> Self {
self.characters.push(ROMAN_UPPER);
self
}
pub fn numeric(mut self) -> Self {
self.characters.push(DIGITS);
self
}
pub fn ascii_printable(mut self) -> Self {
self.characters.push(PUNCTUATION);
self
}
pub fn diceware(mut self) -> Self {
self.diceware = Some(diceware::default_config(self.len()));
self
}
pub fn one(&self) -> Result<PasswordResult> {
let password = if let Some(config) = &self.diceware {
let (passphrase, _) =
diceware::generate_passphrase_config(config)?;
passphrase
} else {
let rng = &mut csprng();
let len = self.characters.iter().fold(0, |acc, s| acc + s.len());
let mut characters = Vec::with_capacity(len);
for chars in &self.characters {
let mut list = chars.chars().collect();
characters.append(&mut list);
}
let mut password = String::new();
for _ in 0..self.length {
password.push(characters[rng.gen_range(0..len)]);
}
SecretString::new(password)
};
let entropy = zxcvbn(password.expose_secret(), &[]);
let result = PasswordResult { password, entropy };
Ok(result)
}
pub fn many(&self, count: usize) -> Result<Vec<PasswordResult>> {
let mut results = Vec::new();
for _ in 0..count {
results.push(self.one()?);
}
Ok(results)
}
}
#[cfg(test)]
mod test {
use super::*;
use anyhow::Result;
use secrecy::ExposeSecret;
#[test]
fn passgen_alpha() -> Result<()> {
let generator = PasswordGen::new_alpha(12);
let result = generator.one()?;
assert_eq!(generator.len(), result.password.expose_secret().len());
Ok(())
}
#[test]
fn passgen_numeric() -> Result<()> {
let generator = PasswordGen::new_numeric(12);
let result = generator.one()?;
assert_eq!(generator.len(), result.password.expose_secret().len());
Ok(())
}
#[test]
fn passgen_alphanumeric() -> Result<()> {
let generator = PasswordGen::new_alpha_numeric(12);
let result = generator.one()?;
assert_eq!(generator.len(), result.password.expose_secret().len());
Ok(())
}
#[test]
fn passgen_ascii_printable() -> Result<()> {
let generator = PasswordGen::new_ascii_printable(12);
let result = generator.one()?;
assert_eq!(generator.len(), result.password.expose_secret().len());
Ok(())
}
#[test]
fn passgen_ascii_printable_long() -> Result<()> {
let generator = PasswordGen::new_ascii_printable(32);
let result = generator.one()?;
assert_eq!(generator.len(), result.password.expose_secret().len());
Ok(())
}
#[test]
fn passgen_diceware() -> Result<()> {
let generator = PasswordGen::new_diceware(6);
let result = generator.one()?;
let words: Vec<String> = result
.password
.expose_secret()
.split(' ')
.map(|s| s.to_owned())
.collect();
assert_eq!(generator.len(), words.len());
Ok(())
}
#[test]
fn passgen_generate() -> Result<()> {
let generator = PasswordGen::new_ascii_printable(12);
let count = 5;
let passwords = generator.many(count)?;
assert_eq!(count, passwords.len());
for result in passwords {
assert_eq!(
generator.len(),
result.password.expose_secret().len()
);
}
Ok(())
}
}