Skip to main content

yo_common/
entropy.rs

1//! Bytes nobody can guess, from the operating system.
2//!
3//! [`crate::rng`] is the generator everything else in the engine uses and it is
4//! deliberately not this: it is nine lines of arithmetic on a seed, so a trial
5//! that fails can be run again and `SPOP` can be tested. `ACL GENPASS` is the
6//! one caller that wants the opposite property. It hands a client a password
7//! that is going to guard a server, so the whole value of it is that the next
8//! one cannot be worked out from the last one, and a seeded stream fails that
9//! by construction.
10//!
11//! So this asks the system, which is the only thing on the machine that is
12//! collecting real entropy. `/dev/urandom` on unix and `BCryptGenRandom` on
13//! Windows, both of which are the documented interface rather than the clever
14//! one, and neither of which needs a handle opened up front or a fallback for
15//! being called early.
16//!
17//! There is no error path. A machine whose random device cannot be read is a
18//! machine that cannot keep a secret, and handing back a password anyway,
19//! filled with whatever was in the buffer, is worse than stopping. So this
20//! panics, which is the same call every serious implementation of this makes.
21
22/// Fill `into` with bytes from the system's random source.
23///
24/// # Panics
25///
26/// If the system will not produce them, because the alternative is a password
27/// made of nothing.
28pub fn fill(into: &mut [u8]) {
29    if into.is_empty() {
30        return;
31    }
32    imp::fill(into);
33}
34
35#[cfg(unix)]
36mod imp {
37    use std::fs::File;
38    use std::io::Read;
39
40    /// Read the whole buffer out of `/dev/urandom`.
41    ///
42    /// Opened per call rather than kept open, because the one caller is a
43    /// command an operator runs by hand and the open is not what it costs. A
44    /// held descriptor would have to survive `fork` and every process that has
45    /// got that wrong has got it badly wrong.
46    ///
47    /// The loop is because a read is allowed to return short, which on this
48    /// device it will not, but the code that assumes it will not is the code
49    /// that hands back a half filled buffer the day it does.
50    pub fn fill(into: &mut [u8]) {
51        let mut file = File::open("/dev/urandom").expect("no /dev/urandom to take a password from");
52        let mut at = 0;
53        while at < into.len() {
54            let n = file
55                .read(&mut into[at..])
56                .expect("could not read /dev/urandom");
57            assert!(n != 0, "/dev/urandom stopped early");
58            at += n;
59        }
60    }
61}
62
63#[cfg(windows)]
64mod imp {
65    /// Ask the system preferred generator, which is what the flag below means.
66    ///
67    /// The flag is the documented way to call this without opening an algorithm
68    /// handle first, so there is no state here and nothing to shut down.
69    const USE_SYSTEM_PREFERRED_RNG: u32 = 0x0000_0002;
70
71    // The library has to be named, because nothing else in the tree pulls it
72    // in. The standard library used to, back when its own generator was this
73    // same call, and it has since moved to `ProcessPrng` in another library,
74    // so a build that linked by accident stopped linking when the toolchain
75    // caught up. The failure is at link time and only on the MSVC target,
76    // which is not a target a person developing this is usually on.
77    #[link(name = "bcrypt")]
78    unsafe extern "system" {
79        fn BCryptGenRandom(
80            algorithm: *mut core::ffi::c_void,
81            buffer: *mut u8,
82            count: u32,
83            flags: u32,
84        ) -> i32;
85    }
86
87    /// Fill the buffer in chunks a `u32` can count, which every real call is
88    /// well inside and which costs one comparison to be right about anyway.
89    pub fn fill(into: &mut [u8]) {
90        for chunk in into.chunks_mut(u32::MAX as usize) {
91            // SAFETY: the pointer and the length are one buffer we hold
92            // exclusively, and the null handle is what the flag asks for.
93            let status = unsafe {
94                BCryptGenRandom(
95                    core::ptr::null_mut(),
96                    chunk.as_mut_ptr(),
97                    chunk.len() as u32,
98                    USE_SYSTEM_PREFERRED_RNG,
99                )
100            };
101            assert!(status == 0, "BCryptGenRandom failed with {status:#x}");
102        }
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::fill;
109
110    /// Two draws differ, and the buffer was actually written to.
111    ///
112    /// Testing a random source is testing that it is not a constant, which is
113    /// the whole of what a test can say without a statistics library. Thirty two
114    /// bytes the same twice is a one in 2^256 accident and a certainty if the
115    /// call did nothing, so this catches the failure that matters.
116    #[test]
117    fn two_draws_are_not_the_same_bytes() {
118        let mut one = [0u8; 32];
119        let mut two = [0u8; 32];
120        fill(&mut one);
121        fill(&mut two);
122        assert_ne!(one, two);
123        assert_ne!(one, [0u8; 32]);
124    }
125
126    /// An empty buffer is not an error and a short one is filled.
127    #[test]
128    fn every_length_up_to_a_block_comes_back_written() {
129        fill(&mut []);
130        for len in 1..=64 {
131            let mut buf = vec![0u8; len];
132            fill(&mut buf);
133            // A short draw can legitimately be all zeroes, so what is asserted
134            // is only that the call returned, which is the boundary being
135            // checked here.
136            assert_eq!(buf.len(), len);
137        }
138    }
139}