main.rs (11654B)
1 /* 2 rusty-diceware - a password / passphrasse generator using wordlists, with or without dice. 3 Copyright (C) 2015-2022 Yuval Langer 4 5 This program is free software: you can redistribute it and/or modify 6 it under the terms of the GNU Affero General Public License as published by 7 the Free Software Foundation, either version 3 of the License, or 8 (at your option) any later version. 9 10 This program is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU Affero General Public License for more details. 14 15 You should have received a copy of the GNU Affero General Public License 16 along with this program. If not, see <https://www.gnu.org/licenses/>. 17 */ 18 19 extern crate getopts; 20 extern crate rand; 21 22 use std::env; 23 use std::fs::File; 24 use std::io::stdin; 25 use std::io::Bytes; 26 use std::io::Read; 27 use std::process::exit; 28 use std::str::FromStr; 29 30 use crate::rand::Rng; 31 use diceware_wordlists::Wordlist; 32 use getopts::Options; 33 use rand::rngs::ThreadRng; 34 use rand::thread_rng; 35 36 fn make_options() -> Options { 37 let mut opts = Options::new(); 38 opts.optflag("h", "help", "This help message."); 39 opts.optflag("e", "entropy", "Display number of entropy bits."); 40 opts.optflag("r", "dicerolls", "Provide results of physical dice rolls. Word per line, same digit order as in the files, digits between and including 1 and 6."); 41 opts.optopt("n", "nword", "Number of words in a passphrase.", "NWORD"); 42 opts.optopt( 43 "d", 44 "delimiter", 45 "The delimiter character used to separate the words.", 46 "DELIM", 47 ); 48 opts.optopt("f", "wordlist-file", "Path to a wordlist file.", "FILE"); 49 opts.optopt( 50 "l", 51 "wordlist", 52 "Wordlist to use. (efflong (default), effshort1, effshort2, minilock, reinhold, or beale)", 53 "WORDLIST", 54 ); 55 opts 56 } 57 58 fn print_usage(program: &str, opts: Options) { 59 let brief = format!("Usage: {} [options]", program); 60 print!("{}", opts.usage(&brief)); 61 } 62 63 fn unknown_wordlist(wordlist_name: &str) -> ! { 64 eprintln!( 65 "Unknown wordlist: {}. Available wordlists: efflong (default), effshort1, effshort2, beale, reinhold, or minilock.", 66 wordlist_name, 67 ); 68 exit(-1) 69 } 70 71 struct ReadWordDieCasts<'a, T> { 72 bytes_iter: &'a mut Bytes<T>, 73 wordlist: &'a Vec<&'a str>, 74 } 75 76 impl<T> ReadWordDieCasts<'_, T> { 77 fn new<'a>( 78 bytes_iter: &'a mut Bytes<T>, 79 wordlist: &'a Vec<&'a str>, 80 ) -> ReadWordDieCasts<'a, T> { 81 ReadWordDieCasts { 82 bytes_iter, 83 wordlist, 84 } 85 } 86 } 87 88 impl<T: Read> Iterator for ReadWordDieCasts<'_, T> { 89 type Item = String; 90 91 fn next(&mut self) -> Option<<ReadWordDieCasts<T> as Iterator>::Item> { 92 read_single_word(self.bytes_iter, self.wordlist) 93 } 94 } 95 96 fn read_single_word<T>(bytes_iter: &mut Bytes<T>, wordlist: &[&str]) -> Option<String> 97 where 98 T: Read, 99 { 100 let die_cast_per_word = die_cast_per_word(wordlist.len()); 101 let mut word_index = 0; 102 103 for die_index in 0..=die_cast_per_word { 104 // for die_cast_per_word == 5 we have: 105 // 106 // die_significance = die_cast_per_word - die_index 107 // 5 = 5 - 0 : just starting, may be an EOF, so next() == None and we then return None 108 // 4 = 5 - 1 . 109 // 3 = 5 - 2 . 110 // 2 = 5 - 3 . 111 // 1 = 5 - 4 . 112 // 0 = 5 - 5 : should be an end of line byte. 113 114 if die_index == die_cast_per_word { 115 let current_byte_result = match bytes_iter.next() { 116 Some(x) => x, 117 None => { 118 eprintln!("Each roll must end with a newline."); 119 exit(-1); 120 } 121 }; 122 123 match current_byte_result { 124 Ok(b'\n') => { 125 return Some(wordlist[word_index].to_string()); 126 } 127 Ok(n) => match n { 128 b'1' | b'2' | b'3' | b'4' | b'5' | b'6' => { 129 eprintln!( 130 "Too many rolls. Each word must have exactly {} die rolls.", 131 die_cast_per_word 132 ); 133 exit(-1); 134 } 135 err => { 136 eprintln!("Input must be either 1 to 6 inclusive digits or newline. (Got: ascii code {})", err); 137 exit(-1); 138 } 139 }, 140 Err(err) => { 141 eprintln!("Unknown error: {}", err); 142 exit(-1); 143 } 144 }; 145 } else { 146 let current_byte_result = match bytes_iter.next() { 147 Some(x) => x, 148 None => { 149 if die_index == 0 { 150 // We are at a legal EOF state, right before a single word die casting sequence. 151 return None; 152 } else { 153 // We are in the middle of die casting and somehow stdin ended. 154 eprintln!( 155 "Bad number of rolls. Each word must have exactly {} die rolls.", 156 die_cast_per_word 157 ); 158 exit(-1); 159 } 160 } 161 }; 162 163 let die_significance = die_cast_per_word - die_index - 1; 164 word_index += match current_byte_result { 165 Ok(b'1') => 0, 166 Ok(b'2') => 6_usize.pow(die_significance), 167 Ok(b'3') => 2 * 6_usize.pow(die_significance), 168 Ok(b'4') => 3 * 6_usize.pow(die_significance), 169 Ok(b'5') => 4 * 6_usize.pow(die_significance), 170 Ok(b'6') => 5 * 6_usize.pow(die_significance), 171 Ok(b'\n') => { 172 eprintln!( 173 "Bad number of rolls. Each word must have exactly {} die rolls.", 174 die_cast_per_word 175 ); 176 exit(-1); 177 } 178 Ok(x) => { 179 eprintln!("Input must be either 1 to 6 inclusive digits or newline. (Got ascii code {})", x); 180 exit(-1); 181 } 182 Err(err) => { 183 eprintln!("Error while reading die casts: {}", err); 184 exit(-1); 185 } 186 }; 187 }; 188 } 189 190 Some(wordlist[word_index].to_string()) 191 } 192 193 fn entropy(wordlist: &[&str]) -> f64 { 194 (wordlist.len() as f64).log2() 195 } 196 197 fn entropyn(wordlist: Vec<&str>, n: u64) -> f64 { 198 entropy(&wordlist) * (n as f64) 199 } 200 201 struct ReadRngWord<'a> { 202 rng: &'a mut ThreadRng, 203 wordlist: &'a Vec<&'a str>, 204 } 205 206 impl<'a> ReadRngWord<'a> { 207 fn new(rng: &'a mut ThreadRng, wordlist: &'a Vec<&'a str>) -> ReadRngWord<'a> { 208 ReadRngWord { rng, wordlist } 209 } 210 } 211 212 impl<'a> Iterator for ReadRngWord<'a> { 213 type Item = String; 214 215 fn next(&mut self) -> Option<String> { 216 Some(self.wordlist[self.rng.gen_range(0..self.wordlist.len())].to_string()) 217 } 218 } 219 220 fn load_wordlist_file(filepath: &str) -> String { 221 let mut wordlist_file = match File::open(filepath) { 222 Ok(ok) => ok, 223 Err(err) => panic!("Unable to open file: {}; due to error: {}", filepath, err), 224 }; 225 let mut wordlist_string = String::new(); 226 if let Err(err) = wordlist_file.read_to_string(&mut wordlist_string) { 227 panic!("Unable to read file: {}; due to error: {}", filepath, err) 228 } 229 wordlist_string 230 } 231 232 fn die_cast_per_word(wordlist_length: usize) -> u32 { 233 wordlist_length.ilog(6) 234 } 235 236 fn main() { 237 let args: Vec<String> = env::args().collect(); 238 let program = &args[0]; 239 240 let opts = make_options(); 241 242 let matches = match opts.parse(&args[1..]) { 243 Ok(m) => m, 244 Err(f) => { 245 println!("{}\n", f); 246 print_usage(program, opts); 247 exit(-1); 248 } 249 }; 250 251 if matches.opt_present("h") { 252 print_usage(program, opts); 253 return; 254 }; 255 256 let is_physical_rolls = matches.opt_present("r"); 257 258 let word_num: u64 = matches 259 .opt_str("n") 260 .map_or(8, |n_str| n_str.parse::<u64>().ok().unwrap()); 261 262 let delimiter: char = matches 263 .opt_str("d") 264 .map_or(' ', |n_str| n_str.parse::<char>().ok().unwrap()); 265 266 let is_entropy_printed = matches.opt_present("entropy"); 267 268 let wordlist_name = if let Some(wordlist_option) = matches.opt_str("l") { 269 match Wordlist::from_str(&wordlist_option.to_lowercase()) { 270 Ok(list) => list, 271 _ => unknown_wordlist(&wordlist_option), 272 } 273 } else { 274 Wordlist::default() 275 }; 276 277 let wordlist_string; 278 279 let wordlist = if let Some(wordlist_filepath) = matches.opt_str("f") { 280 wordlist_string = load_wordlist_file(&wordlist_filepath); 281 wordlist_string 282 .split('\n') 283 .map(|x| x.trim()) 284 .filter(|x| x != &"") 285 .collect::<Vec<&str>>() 286 } else { 287 wordlist_name.get_list().to_vec() 288 }; 289 let mut words_count = 0; 290 291 if is_physical_rolls { 292 let stdin = stdin(); 293 let mut bytes_iter = stdin.bytes(); 294 295 let mut words = ReadWordDieCasts::new(&mut bytes_iter, &wordlist); 296 297 if let Some(word) = words.next() { 298 print!("{}", word); 299 300 words_count = 1; 301 302 for word in words { 303 print!("{}{}", delimiter, word); 304 words_count += 1; 305 } 306 println!(); 307 }; 308 } else if word_num != 0 { 309 let mut rng = thread_rng(); 310 311 let mut words = ReadRngWord::new(&mut rng, &wordlist); 312 313 print!("{}", words.next().unwrap()); 314 315 words_count = 1; 316 317 for _ in 1..word_num { 318 print!("{}{}", delimiter, words.next().unwrap()); 319 words_count += 1; 320 } 321 322 println!(); 323 } else { 324 print_usage(program, opts); 325 exit(-1); 326 }; 327 328 if is_entropy_printed { 329 println!("{}", entropyn(wordlist, words_count)); 330 } 331 } 332 333 #[cfg(test)] 334 mod tests { 335 use super::*; 336 use std::io::BufReader; 337 338 #[test] 339 fn test_read_single_word() { 340 let wordlist = Wordlist::default().get_list().to_vec(); 341 let input = &b"11111\n"[..]; 342 let mut reader = BufReader::with_capacity(input.len(), input).bytes(); 343 assert_eq!( 344 Some("abacus".to_string()), 345 read_single_word(&mut reader, &wordlist) 346 ); 347 } 348 349 #[test] 350 fn test_read_words_from_die_casting() { 351 let wordlist = Wordlist::default().get_list().to_vec(); 352 let input = &b"11111\n66665\n"[..]; 353 let mut reader = BufReader::with_capacity(input.len(), input).bytes(); 354 let mut words_iter = ReadWordDieCasts::new(&mut reader, &wordlist); 355 assert_eq!(Some("abacus".to_string()), words_iter.next()); 356 assert_eq!(Some("zoology".to_string()), words_iter.next()); 357 assert_eq!(None, words_iter.next()); 358 } 359 360 // #[test] 361 // fn test_read_words_from_rolls() { 362 // let wordlist = Wordlist::default().get_list().to_vec(); 363 // let input = &b"11111\n11111\n"[..]; 364 // let mut reader = std::io::BufReader::with_capacity(input.len(), input).bytes(); 365 // assert_eq!("abacus", read_words_from_rolls::<std::io::Bytes<std::io::BufReader<&[u8]>>, u8>(reader, &wordlist).unwrap()); 366 // } 367 }