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 {
28 let mut rng = rand::rng();
29
30 let alphabet = match ty {
31 Type::AllChars => {
32 let mut v = vec![];
33 v.extend(SYMBOLS.iter().copied());
34 v.extend(NUMBERS.iter().copied());
35 v.extend(LETTERS.iter().copied());
36 v
37 }
38 Type::NoSymbols => {
39 let mut v = vec![];
40 v.extend(NUMBERS.iter().copied());
41 v.extend(LETTERS.iter().copied());
42 v
43 }
44 Type::Numbers => {
45 let mut v = vec![];
46 v.extend(NUMBERS.iter().copied());
47 v
48 }
49 Type::NonConfusables => {
50 let mut v = vec![];
51 v.extend(NONCONFUSABLES.iter().copied());
52 v
53 }
54 Type::Diceware => {
55 return diceware(&mut rng, len);
56 }
57 };
58
59 let mut buf = locked::Vec::new();
60 buf.extend(
61 std::iter::repeat_with(|| *alphabet.iter().choose(&mut rng).unwrap())
62 .take(len),
63 );
64 locked::Password::new(buf)
65}
66
67fn diceware(rng: &mut impl rand::RngCore, len: usize) -> locked::Password {
68 let mut words = vec![];
69 for _ in 0..len {
70 words.push(*crate::wordlist::EFF_LONG.iter().choose(rng).unwrap());
72 }
73 let mut joined = words.join(" ");
74 let mut buf = locked::Vec::new();
75 buf.extend(joined.as_bytes().iter().copied());
76 joined.zeroize();
79 locked::Password::new(buf)
80}
81
82#[cfg(test)]
83mod test {
84 use super::*;
85
86 #[test]
87 fn test_pwgen() {
88 let pw = pwgen(Type::AllChars, 50);
89 assert_eq!(pw.password().len(), 50);
90 assert_duplicates(pw.password());
93
94 let pw = pwgen(Type::AllChars, 100);
95 assert_eq!(pw.password().len(), 100);
96 assert_duplicates(pw.password());
97
98 let pw = pwgen(Type::NoSymbols, 100);
99 assert_eq!(pw.password().len(), 100);
100 assert_duplicates(pw.password());
101
102 let pw = pwgen(Type::Numbers, 100);
103 assert_eq!(pw.password().len(), 100);
104 assert_duplicates(pw.password());
105
106 let pw = pwgen(Type::NonConfusables, 100);
107 assert_eq!(pw.password().len(), 100);
108 assert_duplicates(pw.password());
109 }
110
111 #[track_caller]
112 fn assert_duplicates(bytes: &[u8]) {
113 let s = std::str::from_utf8(bytes).unwrap();
114 let mut set = std::collections::HashSet::new();
115 for c in s.chars() {
116 set.insert(c);
117 }
118 assert!(set.len() < s.len());
119 }
120}