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