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}