1use rand::seq::IteratorRandom as _;
2use zeroize::Zeroize as _;
3
4use crate::locked;
5
6const SYMBOLS: &[u8] = b"!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
7const NUMBERS: &[u8] = b"0123456789";
8const LETTERS: &[u8] =
9 b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
10const NONCONFUSABLES: &[u8] = b"34678abcdefhjkmnpqrtuwxy";
11
12#[derive(Debug, Eq, PartialEq, Copy, Clone)]
13pub enum Type {
14 AllChars,
15 NoSymbols,
16 Numbers,
17 NonConfusables,
18 Diceware,
19}
20
21pub fn pwgen(ty: Type, len: usize) -> locked::Password {
30 let mut rng = rand::rng();
31
32 let alphabet = match ty {
33 Type::AllChars => {
34 let mut v = vec![];
35 v.extend(SYMBOLS.iter().copied());
36 v.extend(NUMBERS.iter().copied());
37 v.extend(LETTERS.iter().copied());
38 v
39 }
40 Type::NoSymbols => {
41 let mut v = vec![];
42 v.extend(NUMBERS.iter().copied());
43 v.extend(LETTERS.iter().copied());
44 v
45 }
46 Type::Numbers => {
47 let mut v = vec![];
48 v.extend(NUMBERS.iter().copied());
49 v
50 }
51 Type::NonConfusables => {
52 let mut v = vec![];
53 v.extend(NONCONFUSABLES.iter().copied());
54 v
55 }
56 Type::Diceware => {
57 return diceware(&mut rng, len);
58 }
59 };
60
61 let mut buf = locked::Vec::new();
62 buf.extend(
63 std::iter::repeat_with(|| *alphabet.iter().choose(&mut rng).unwrap())
64 .take(len),
65 );
66 locked::Password::new(buf)
67}
68
69fn diceware(rng: &mut impl rand::RngCore, len: usize) -> locked::Password {
70 let mut words = vec![];
71 for _ in 0..len {
72 words.push(*crate::wordlist::EFF_LONG.iter().choose(rng).unwrap());
74 }
75 let mut joined = words.join(" ");
76 let mut buf = locked::Vec::new();
77 buf.extend(joined.as_bytes().iter().copied());
78 joined.zeroize();
81 locked::Password::new(buf)
82}
83
84#[cfg(test)]
85mod test {
86 use super::*;
87
88 #[test]
89 fn test_pwgen() {
90 let pw = pwgen(Type::AllChars, 50);
91 assert_eq!(pw.password().len(), 50);
92 assert_duplicates(pw.password());
95
96 let pw = pwgen(Type::AllChars, 100);
97 assert_eq!(pw.password().len(), 100);
98 assert_duplicates(pw.password());
99
100 let pw = pwgen(Type::NoSymbols, 100);
101 assert_eq!(pw.password().len(), 100);
102 assert_duplicates(pw.password());
103
104 let pw = pwgen(Type::Numbers, 100);
105 assert_eq!(pw.password().len(), 100);
106 assert_duplicates(pw.password());
107
108 let pw = pwgen(Type::NonConfusables, 100);
109 assert_eq!(pw.password().len(), 100);
110 assert_duplicates(pw.password());
111 }
112
113 #[track_caller]
114 fn assert_duplicates(bytes: &[u8]) {
115 let s = std::str::from_utf8(bytes).unwrap();
116 let mut set = std::collections::HashSet::new();
117 for c in s.chars() {
118 set.insert(c);
119 }
120 assert!(set.len() < s.len());
121 }
122}