passgen/
random.rs

1//! # Randomness generation
2//!
3//! This module contains various sources of randomness. Randomness itself is handled by the `rand`
4//! crate. These types implement the [RandomSource] trait to generate *n* independent streams of
5//! randomness.
6//!
7//! Fundamentally, there are two types of randomness:
8//! * *Non-deterministic*, typically using whatever strong randomness generator is available on the
9//!   system. This is the default for Passgen, and should be used when you want to generate a new
10//!   random passphrase and do not need the ability to later re-generate it.
11//! * *Deterministic*. These are used for generating passphrases from some known secret using a
12//!   pseudorandom number generator (such as a stream cipher). These should be used when you want
13//!   to be able to later re-generate the same passphrase from the secret.
14//!
15//! Some of the randomness sources (specifically the [Zero] and [Xoshiro] ones) exist mainly for
16//! testing purposes and should not be used by end-users. They may be gated by some feature in the
17//! future to remove them from the Passgen tool.
18//!
19//! For each source of randomness, Passgen requires the ability to create *n* random number
20//! generators with independent streams of random values to allow it to generate passphrases
21//! from multiple threads, see the [RandomSource] documentation for more information on this.
22//!
23//! ## Examples
24//!
25//! Creating a randomness source, instantiating multiple random number generators from it
26//! and showing the the streams are independent and deterministic.
27//!
28//! ```
29//! use passgen::random::*;
30//! use rand::Rng;
31//!
32//! // using the xoshiro deterministic pseudorandom number generator
33//! let random = Xoshiro::new(564);
34//!
35//! // stream zero
36//! let mut rng = random.get_rng(0);
37//! assert_eq!(rng.gen::<u8>(), 45);
38//! assert_eq!(rng.gen::<u8>(), 106);
39//!
40//! // stream one (independent from zero)
41//! let mut rng = random.get_rng(1);
42//! assert_eq!(rng.gen::<u8>(), 156);
43//! assert_eq!(rng.gen::<u8>(), 212);
44//! ```
45
46use argon2::Argon2;
47use rand::RngCore;
48use rand::SeedableRng;
49use rand_chacha::ChaCha20Rng;
50use rand_xoshiro::Xoshiro256PlusPlus;
51use std::str::FromStr;
52
53/// Randomness source
54///
55/// Applications using Passgen should allow the user to configure the source of randomness by
56/// specifying this value. It defaults to using the [System] randomness generator, which is
57/// non-deterministic.
58#[derive(Debug, Clone, PartialEq)]
59pub enum Random {
60    System(System),
61    Zero(Zero),
62    Xoshiro(Xoshiro),
63    ChaCha20(ChaCha20),
64}
65
66impl Default for Random {
67    fn default() -> Self {
68        Self::System(System)
69    }
70}
71
72impl Random {
73    /// When using master pass mode, this method should be used (with the specified master
74    /// passphrase) to generate the appropriate source of randomness.
75    pub fn from_master_pass(pass: &str, salt: &str) -> Self {
76        let mut seed = [0; 32];
77        Argon2::new_with_secret(
78            b"passgen",
79            Default::default(),
80            Default::default(),
81            Default::default(),
82        )
83        .unwrap()
84        .hash_password_into(pass.as_bytes(), salt.as_bytes(), &mut seed)
85        .unwrap();
86        Self::ChaCha20(ChaCha20(seed))
87    }
88}
89
90/// Error parsing a [Random] value from a string.
91#[derive(thiserror::Error, Debug)]
92pub enum ParseError {
93    #[error("unknown randomness source '{0:}'")]
94    Unknown(String),
95}
96
97impl FromStr for Random {
98    type Err = ParseError;
99
100    fn from_str(input: &str) -> Result<Self, Self::Err> {
101        let split = input
102            .split_once(':')
103            .map(|(name, opt)| (name, Some(opt)))
104            .unwrap_or((input, None));
105        match split {
106            ("system", None) => Ok(Self::System(System)),
107            ("zero", None) => Ok(Self::Zero(Zero)),
108            ("xoshiro", Some(params)) => Ok(Self::Xoshiro(Xoshiro(params.parse().unwrap()))),
109            _ => Err(ParseError::Unknown(input.to_string())),
110        }
111    }
112}
113
114/// Source of randomness
115///
116/// ## Streams
117///
118/// When Passgen generates multiple random sequences, it could use a single stream of randomness
119/// sequentially, however this makes it impossible to use multi-threading. For that reason, every
120/// sequence it generates uses an independent stream of randomness. For nondeterministic
121/// randomness, these independent streams can actually be the same stream. For deterministic
122/// sources of randomness, these streams should be independent and each stream should always
123/// produce the same (pseudorandom) data.
124pub trait RandomSource {
125    /// Underlying type of the randomness source.
126    type Rng: RngCore + 'static;
127
128    /// Return a stream of randomness for the given index.
129    fn get_rng(&self, index: usize) -> Self::Rng;
130
131    /// Return a type-erased stream of randomness for the given index.
132    fn get_rng_boxed(&self, index: usize) -> Box<dyn RngCore> {
133        Box::new(self.get_rng(index))
134    }
135}
136
137impl RandomSource for Random {
138    type Rng = Box<dyn RngCore>;
139
140    fn get_rng(&self, index: usize) -> Box<dyn RngCore> {
141        match self {
142            Self::System(random) => random.get_rng_boxed(index),
143            Self::Zero(random) => random.get_rng_boxed(index),
144            Self::Xoshiro(random) => random.get_rng_boxed(index),
145            Self::ChaCha20(random) => random.get_rng_boxed(index),
146        }
147    }
148
149    fn get_rng_boxed(&self, index: usize) -> Box<dyn RngCore> {
150        self.get_rng(index)
151    }
152}
153
154/// System randomness generator for secure, non-deterministic passphrase generation.
155#[derive(Debug, Clone, Copy, PartialEq, Default)]
156pub struct System;
157
158impl RandomSource for System {
159    type Rng = rand::rngs::ThreadRng;
160
161    fn get_rng(&self, _index: usize) -> Self::Rng {
162        rand::thread_rng()
163    }
164}
165
166/// Source of randomness that always returns zeroes.
167#[derive(Debug, Clone, Copy, PartialEq, Default)]
168pub struct Zero;
169
170impl RandomSource for Zero {
171    type Rng = rand::rngs::mock::StepRng;
172
173    fn get_rng(&self, _index: usize) -> Self::Rng {
174        rand::rngs::mock::StepRng::new(0, 0)
175    }
176}
177
178/// [Xoshiro256PlusPlus] based pseudorandom generator.
179#[derive(Debug, Clone, Copy, PartialEq)]
180pub struct Xoshiro(u64);
181
182impl Xoshiro {
183    pub fn new(seed: u64) -> Self {
184        Xoshiro(seed)
185    }
186}
187
188impl RandomSource for Xoshiro {
189    type Rng = Xoshiro256PlusPlus;
190
191    fn get_rng(&self, index: usize) -> Self::Rng {
192        let mut rng = Xoshiro256PlusPlus::seed_from_u64(self.0);
193        for _ in 0..index {
194            rng.jump();
195        }
196        rng
197    }
198}
199
200/// [ChaCha20Rng]-based pseudorandom generator.
201#[derive(Debug, Clone, Copy, PartialEq)]
202pub struct ChaCha20([u8; 32]);
203
204impl RandomSource for ChaCha20 {
205    type Rng = ChaCha20Rng;
206
207    fn get_rng(&self, index: usize) -> Self::Rng {
208        let mut rng = ChaCha20Rng::from_seed(self.0);
209        rng.set_stream(index as u64);
210        rng
211    }
212}