tor_llcrypto/rng.rs
1//! Random number generation.
2//!
3//! For most purposes in Arti, we use one of two random number generators:
4//! - `rand::rng()` (formerly called `rand::thread_rng()`, up till rand 0.9)
5//! - The [`CautiousRng`] implemented here.
6//!
7//! [`CautiousRng`] should be used whenever we are generating
8//! a medium- or long-term cryptographic key:
9//! one that will be stored to disk, or used for more than a single communication.
10//! It is slower than [`rand::rng()`],
11//! but is more robust against several kinds of failure.
12//
13// Note: Although we want to use CautiousRng
14// whenever we generate a medium- or long-term key,
15// we do not consider it a major
16// security hole if we use rand::rng() instead:
17// CautiousRng is a defense-in-depth mechanism.
18
19use std::convert::Infallible;
20
21use digest::{ExtendableOutput, Update};
22
23use rand::rngs::SysRng;
24use rand_core::TryRng;
25use sha3::Shake256;
26use zeroize::Zeroizing;
27
28/// Trait representing an Rng where every output is derived from
29/// supposedly strong entropy.
30///
31/// Implemented by [`CautiousRng`].
32///
33/// # Warning
34///
35/// Do not implement this trait for new Rngs unless you know what you are doing;
36/// any Rng to which you apply this trait should be _at least_ as
37/// unpredictable and secure as `SysRng`.
38///
39/// We recommend using [`CautiousRng`] when you need an instance of this trait.
40pub trait EntropicRng: rand_core::CryptoRng {}
41
42impl EntropicRng for CautiousRng {}
43
44/// Functionality for testing Rng code that requires an EntropicRng.
45#[cfg(feature = "testing")]
46mod testing {
47 use std::convert::Infallible;
48
49 /// Testing only: Pretend that an inner RNG truly implements `EntropicRng`.
50 #[allow(clippy::exhaustive_structs)]
51 pub struct FakeEntropicRng<R>(pub R);
52
53 impl<R: rand_core::TryRng<Error = Infallible>> rand_core::TryRng for FakeEntropicRng<R> {
54 type Error = Infallible;
55
56 fn try_next_u32(&mut self) -> Result<u32, Infallible> {
57 self.0.try_next_u32()
58 }
59
60 fn try_next_u64(&mut self) -> Result<u64, Infallible> {
61 self.0.try_next_u64()
62 }
63
64 fn try_fill_bytes(&mut self, dst: &mut [u8]) -> Result<(), Infallible> {
65 self.0.try_fill_bytes(dst)
66 }
67 }
68 impl<R: rand_core::TryCryptoRng<Error = Infallible>> rand_core::TryCryptoRng
69 for FakeEntropicRng<R>
70 {
71 }
72 impl<R: rand_core::CryptoRng> super::EntropicRng for FakeEntropicRng<R> {}
73}
74#[cfg(feature = "testing")]
75pub use testing::FakeEntropicRng;
76
77/// An exceptionally cautious wrapper for [`SysRng`]
78///
79/// Ordinarily, one trusts `SysRng`.
80/// But we want Arti to run on a wide variety of platforms,
81/// and the chances of a bogus SysRng increases the more places we run.
82/// This Rng combines SysRng with several other entropy sources,
83/// in an attempt to reduce the likelihood of creating compromised keys.[^scary]
84///
85/// This Rng is slower than `SysRng`.
86///
87/// # Panics
88///
89/// This rng will panic if `SysRng` fails;
90/// but that's the only sensible behavior for a cryptographic-heavy application like ours.
91///
92/// [^scary]: Who else remembers [CVE-2008-0166](https://www.cve.org/CVERecord?id=CVE-2008-0166)?
93#[derive(Default)]
94#[allow(clippy::exhaustive_structs)]
95pub struct CautiousRng;
96
97impl TryRng for CautiousRng {
98 type Error = Infallible;
99
100 fn try_next_u32(&mut self) -> Result<u32, Infallible> {
101 let mut buf = Zeroizing::new([0_u8; 4]);
102 self.try_fill_bytes(buf.as_mut())?;
103 Ok(u32::from_le_bytes(*buf))
104 }
105
106 fn try_next_u64(&mut self) -> Result<u64, Infallible> {
107 let mut buf = Zeroizing::new([0_u8; 8]);
108 self.try_fill_bytes(buf.as_mut())?;
109 Ok(u64::from_le_bytes(*buf))
110 }
111
112 fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Infallible> {
113 let mut xof = Shake256::default();
114 let mut buf = Zeroizing::new([0_u8; 32]);
115
116 // According to some oldschool crypto wisdom,
117 // provided by cryptographers wearing tinfoil hats,
118 // when you're making a construction like this you should poll your RNGs
119 // from least trusted to most-trusted,
120 // in case one of the least trusted ones is secretly Pascal's Demon,
121 // providing the input deliberately tuned to make your Shake256 output predictable.
122 //
123 // The idea is somewhat ludicrous, but we have to poll in _some_ order,
124 // and just writing this code has put us into a world of tinfoil hats.
125
126 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
127 if let Ok(mut rdrand) = rdrand::RdRand::new() {
128 // We'll tolerate a failure from rdrand here,
129 // since it can indicate a few different error conditions,
130 // including a lack of hardware support, or exhausted CPU entropy
131 // (whatever that is supposed to mean).
132 // We only want to panic on a failure from SysRng.
133 let _ignore_failure = rdrand.try_fill_bytes(buf.as_mut());
134
135 // We add the output from rdrand unconditionally, since a partial return is possible,
136 // and since there's no real harm in doing so.
137 // (Performance is likely swamped by syscall overhead, and call to our BackupRng.)
138 // In the worst case, we just add some NULs in this case, which is fine.
139 xof.update(buf.as_ref());
140 }
141 // TODO: Consider using rndr on aarch64.
142
143 #[cfg(not(target_arch = "wasm32"))]
144 {
145 if let Some(mut rng) = backup::backup_rng() {
146 let _ignore_failure = rng.try_fill_bytes(buf.as_mut());
147 xof.update(buf.as_ref());
148 }
149 }
150
151 rand::rng().try_fill_bytes(buf.as_mut())?;
152 xof.update(buf.as_ref());
153
154 SysRng
155 .try_fill_bytes(buf.as_mut())
156 .expect("No strong entropy source was available: cannot proceed");
157 xof.update(buf.as_ref());
158
159 xof.finalize_xof_into(dest);
160
161 Ok(())
162 }
163}
164
165impl rand_core::TryCryptoRng for CautiousRng {}
166
167/// A backup RNG, independent of other known sources.
168///
169/// Not necessarily strong, but hopefully random enough to cause an attacker some trouble
170/// in the event of catastrophic failure.
171///
172/// A failure from this RNG _does not_ cause a panic.
173#[cfg(not(target_arch = "wasm32"))]
174mod backup {
175
176 use rand::TryRng;
177 use rand_chacha::ChaCha20Rng;
178 use reseeding_rng::ReseedingRng;
179 use std::convert::Infallible;
180 use std::sync::LazyLock;
181 use std::sync::{Mutex, MutexGuard};
182
183 /// The type we've chosen to use for our backup Rng.
184 ///
185 /// (We need to box this because the default JitterRng is unnameable.)
186 ///
187 /// We use JitterRng to reseed a ChaCha20 core
188 /// because it is potentially _very_ slow.
189 type BackupRng = ReseedingRng<ChaCha20Rng, Box<dyn TryRng<Error = Infallible> + Send>>;
190
191 /// Static instance of our BackupRng; None if we failed to construct one.
192 static JITTER_BACKUP: LazyLock<Option<Mutex<BackupRng>>> = LazyLock::new(new_backup_rng);
193
194 /// Construct a new instance of our backup Rng;
195 /// return None on failure.
196 fn new_backup_rng() -> Option<Mutex<BackupRng>> {
197 let jitter = rand_jitter::JitterRng::new().ok()?;
198 let jitter: Box<dyn TryRng<Error = Infallible> + Send> = Box::new(jitter);
199 // The "1024" here is chosen more or less arbitrarily;
200 // we might want to tune it if we find that it matters.
201 let reseeding = ReseedingRng::try_new(1024, jitter).ok()?;
202 Some(Mutex::new(reseeding))
203 }
204
205 /// Return a MutexGuard for our backup rng, or None if we couldn't construct one.
206 pub(super) fn backup_rng() -> Option<MutexGuard<'static, BackupRng>> {
207 JITTER_BACKUP
208 .as_ref()
209 .map(|mutex| mutex.lock().expect("lock poisoned"))
210 }
211}