use crate::Result;
use chbs::{config::BasicConfig, word::WordSampler};
use rand::Rng;
use secrecy::{ExposeSecret, SecretString};
use sos_core::csprng;
use zxcvbn::{zxcvbn, Entropy};
const ROMAN_LOWER: &str = "abcdefghijklmnopqrstuvwxyz";
const ROMAN_UPPER: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const DIGITS: &str = "0123456789";
const PUNCTUATION: &str = "!\"#$%&'()*+,-./:;<=>?@`~\\]^_{}";
use super::{diceware, memorable::memorable_password};
pub fn measure_entropy(password: &str, user_inputs: &[&str]) -> Entropy {
zxcvbn(password, user_inputs)
}
pub fn generate_one(kind: &PasswordType) -> Result<PasswordResult> {
let builder = PasswordBuilder::new(kind);
builder.one()
}
pub fn generate_many(
kind: &PasswordType,
count: usize,
) -> Result<Vec<PasswordResult>> {
let builder = PasswordBuilder::new(kind);
builder.many(count)
}
#[derive(Debug)]
pub enum PasswordType {
Memorable {
words: usize,
},
Alpha {
characters: usize,
},
AlphaNumeric {
characters: usize,
},
Numeric {
digits: usize,
},
Random {
length: usize,
},
Diceware {
words: usize,
},
}
impl Default for PasswordType {
fn default() -> Self {
Self::Memorable { words: 3 }
}
}
#[derive(Debug, Clone)]
pub struct PasswordResult {
pub password: SecretString,
pub entropy: Entropy,
}
#[derive(Debug, Clone)]
pub struct PasswordBuilder {
length: usize,
memorable: bool,
characters: Vec<&'static str>,
diceware: Option<BasicConfig<WordSampler>>,
}
impl PasswordBuilder {
pub fn new(kind: &PasswordType) -> Self {
match kind {
PasswordType::Memorable { words } => {
PasswordBuilder::new_memorable(*words)
}
PasswordType::Alpha { characters } => {
PasswordBuilder::new_alpha(*characters)
}
PasswordType::AlphaNumeric { characters } => {
PasswordBuilder::new_alpha_numeric(*characters)
}
PasswordType::Numeric { digits } => {
PasswordBuilder::new_numeric(*digits)
}
PasswordType::Random { length } => {
PasswordBuilder::new_ascii_printable(*length)
}
PasswordType::Diceware { words } => {
PasswordBuilder::new_diceware(*words)
}
}
}
fn new_length(length: usize) -> Self {
Self {
length,
memorable: false,
characters: vec![],
diceware: None,
}
}
pub fn new_memorable(length: usize) -> Self {
Self::new_length(length).memorable()
}
pub fn new_alpha(length: usize) -> Self {
Self::new_length(length).upper().lower()
}
pub fn new_numeric(length: usize) -> Self {
Self::new_length(length).numeric()
}
pub fn new_alpha_numeric(length: usize) -> Self {
Self::new_length(length).upper().lower().numeric()
}
pub fn new_ascii_printable(length: usize) -> Self {
Self::new_length(length)
.upper()
.lower()
.numeric()
.ascii_printable()
}
pub fn new_diceware(length: usize) -> Self {
Self::new_length(length).diceware()
}
pub fn len(&self) -> usize {
self.length
}
pub fn memorable(mut self) -> Self {
self.memorable = true;
self
}
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 if self.memorable {
memorable_password(self.length).into()
} 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)]);
}
password.into()
};
let entropy = zxcvbn(password.expose_secret(), &[]);
Ok(PasswordResult { password, entropy })
}
pub fn many(&self, count: usize) -> Result<Vec<PasswordResult>> {
let mut results = Vec::with_capacity(count);
for _ in 0..count {
results.push(self.one()?);
}
Ok(results)
}
}