1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
//! _Note: this crate is still a work in progress, APIs might change until
//! stabilisation_
//!
//! A secure, easy to use, configurable and extendable passphrase generation library
//! based on a wordlist, generally known as [diceware].
//!
//! The crate name `chbs` is short for the well known "correct horse battery staple" password
//! which originates from an [XKCD][xkcd] comic shown in the README [here][xkcd_comic].
//!
//! This library uses cryptographically secure randomization, and may be used
//! for generating secret passphrases.
//! Please refer to the [README][readme] for more information on security.
//!
//! ## Concepts
//! As the passphrase generation system in this crate is thoroughly abstracted it is
//! important to understand how the concepts used in this crate work.
//!
//! Here is what is required for passphrase generation:
//! - A [`Scheme`](scheme::Scheme) defines how a passphrase is generated. Passphrases are only
//!   generated through a scheme.
//! - A [`Scheme`](scheme::Scheme) contains components which represents how the passphrase is built
//!   up and styled. Four kinds of components exist, defining the passphrase generation
//!   steps. For some kinds one must be defined,
//!   for other kinds any number is fine:
//!     1.  [`WordSetProvider`](component::traits::WordSetProvider) (`1` required):
//!         provides a list of words to use in a passphrase.
//!     2.  [`WordStyler`](component::traits::WordStyler) (`>=0` required):
//!         styles passphrase words, for example, to capitalize.
//!     3.  [`PhraseBuilder`](component::traits::PhraseBuilder) (`1` required):
//!         builds a phrase from a set of passphrase words.
//!     4.  [`PhraseStyler`](component::traits::PhraseBuilder) (`>=0` required):
//!         styles a whole passphrase.
//!
//! Things to understand:
//! - Passphrase generation schemes are commonly created by using a configuration
//!   structure. Such as structure will provide various configurable fields, and
//!   builds a corresponding scheme based on it for passphrase generation.
//!
//! The usual steps for generating a passphrase:
//! - A configuration structure is built and configured,
//!   such as [`BasicConfig`](config::BasicConfig).
//! - The configuration struct creates a corresponding passphrase generation scheme.
//! - The scheme is used to generate as many passphrases as needed.
//! - Instead, the [`passphrase()`](passphrase) helper method may be used to generate a passphrase
//!   with zero configuration for ease of use.
//!
//! See, it isn't too difficult, but allows great extensibility. You probably won't
//! use most of what this crate provides.  
//! Take a look at [`BasicConfig`](config::BasicConfig) to see how to configure your first
//! passphrase generator.
//!
//! Additional good-to-know things:
//! - This crate provides a selection of components for specific tasks, custom
//!   components may be built.
//! - This crate provides a [`WordList`](word::WordList) struct to hold a static wordlist,
//!   that may use a built-in wordlist or loads a wordlist from a specified file.
//! - A [`WordSampler`](word::WordSampler) may be [constructed](word::WordList::sampler) based
//!   on a [`WordList`](word::WordList) to allow randomized word sampling in an uniform manner.
//!   Such a sampler is usually what is used as word provider in a configuration struct.
//!
//! ## Examples
//! Here are two very basic examples.
//! First to generate a passphrase with zero configuration using a helper function applying
//! library defaults ([src][example_passphrase]):
//!
//! ```rust
//! use chbs::passphrase;
//!
//! println!("Passphrase: {:?}", passphrase());
//! ```
//!
//! Generating a passphrase with configuration is recommended, here is a basic
//! example ([src][example_passphrase_config]):
//!
//! ```rust
//! use chbs::{config::BasicConfig, prelude::*, probability::Probability};
//!
//! // Build a custom configuration to:
//! let mut config = BasicConfig::default();
//! config.words = 8;
//! config.separator = "-".into();
//! config.capitalize_first = Probability::from(0.33);
//! config.capitalize_words = Probability::half();
//! let mut scheme = config.to_scheme();
//!
//! println!("Passphrase: {:?}", scheme.generate());
//! println!("Entropy: {:?}", scheme.entropy().bits());
//! ```
//!
//! More examples are available in the documentation throughout the crate,
//! and in the [`./examples`][examples] directory.
//!
//! ## More information
//! Please reference to the [README][readme] in the [code repository][repo] for more information.
//!
//! [diceware]: https://en.wikipedia.org/wiki/Diceware
//! [examples]: https://gitlab.com/timvisee/chbs/tree/master/examples
//! [example_passphrase]: https://gitlab.com/timvisee/chbs/blob/master/examples/passphrase.rs
//! [example_passphrase_config]: https://gitlab.com/timvisee/chbs/blob/master/examples/passphrase_config.rs
//! [readme]: https://gitlab.com/timvisee/chbs/blob/master/README.md
//! [repo]: https://gitlab.com/timvisee/chbs
//! [xkcd]: https://xkcd.com/936/
//! [xkcd_comic]: https://gitlab.com/timvisee/chbs#rust-library-correct-horse-battery-staple

#[macro_use]
extern crate derive_builder;
extern crate rand;

use crate::config::BasicConfig;
use crate::prelude::*;

pub mod component;
pub mod config;
pub mod entropy;
pub mod prelude;
pub mod probability;
pub mod scheme;
pub mod word;

/// The default number of words the passphrase will consist of.
const DEFAULT_WORDS: usize = 5;

/// The default separator used between passphrase words.
const DEFAULT_SEPARATOR: &str = " ";

/// Zero-configuration passphrase generation helper
///
/// A quick way to generate a passphrase with no configuration.  
/// Passphrases are based on the `default()` of a [`BasicConfig`](config::BasicConfig),
/// detailed properties can be found in it's documentation.
///
/// Although this crate considers the used configuration secure, your project might have different
/// requirements. It is therefore highly recommended however to set up your own configuration to
/// meet your requirements. This can easily be done by choosing any of the configuration structs
/// in the [`config`](::config) module such as [`BasicConfig`](config::BasicConfig), which has a
/// [builder](config::BasicConfigBuilder) available.  
/// Or build your own configuration type with support for converting it into a
/// [`Scheme`](scheme::Scheme) by implementing the [`ToScheme`](scheme::ToScheme) trait.
///
/// A configuration instance is created each time this method is invoked. For generating multiple
/// passphrases it is recommended to build a [`Scheme`](scheme::Scheme) instead as it's much more
/// performant.
///
/// # Entropy
///
/// To figure out what entropy these passphrases have, use:
///
/// ```rust
/// use chbs::{config::BasicConfig, prelude::*};
///
/// let entropy = BasicConfig::default().to_scheme().entropy();
/// println!("passphrase() entropy: {:?}", entropy);
/// ```
pub fn passphrase() -> String {
    BasicConfig::default().to_scheme().generate()
}

#[cfg(test)]
mod tests {
    use std::sync::mpsc::RecvError;
    use std::sync::{mpsc::channel, mpsc::Sender, Arc};
    use std::thread;

    use super::config::BasicConfig;
    use super::passphrase;
    use super::scheme::{Scheme, ToScheme};
    use super::word::WordList;

    /// How many times to iterate for small or infinite tests.
    const ITERS: usize = 32;

    /// Generating a passphrase must produce a string of at least 10 characters.
    #[test]
    fn passphrase_len() {
        assert!(
            passphrase().len() >= 10,
            "passphrase generated by defaults helper is too short",
        );
    }

    /// Repeatedly generating passphrases should produce somewhat unique results.
    #[test]
    fn passphrase_unique() {
        // Generate phrases with helper and dedup
        let mut phrases: Vec<String> = (1..=ITERS).map(|_| passphrase()).collect();
        phrases.dedup();

        // There must be at least 2 unique passphrases
        assert!(phrases.len() > 1);
    }

    #[test]
    fn sampler_into_iterator() {
        let words = WordList::default();
        let iterator = words.sampler().into_iter();
        let result: Vec<String> = iterator.take(8).collect();
        assert_eq!(8, result.len());
    }

    #[test]
    fn threading() -> Result<(), RecvError> {
        let scheme = Arc::new(BasicConfig::default().to_scheme());
        let (tx, rx) = channel::<String>();

        let handle1 = spawn_thread(scheme.clone(), tx.clone());
        let handle2 = spawn_thread(scheme.clone(), tx.clone());

        handle1.join().unwrap();
        handle2.join().unwrap();
        std::mem::drop(tx);

        rx.recv()?;
        rx.recv()?;
        Ok(())
    }

    fn spawn_thread(scheme: Arc<Scheme>, tx: Sender<String>) -> thread::JoinHandle<()> {
        thread::spawn(move || {
            tx.send(scheme.generate()).unwrap();
        })
    }
}