rusty-diceware

Commandline diceware, with or without dice, written in Rustlang.
git clone https://kaka.farm/~git/rusty-diceware
Log | Files | Refs | README | LICENSE

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 }