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

            
19
use digest::{ExtendableOutput, Update};
20

            
21
use rand_core::TryRngCore;
22
use sha3::Shake256;
23
use 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.
37
pub trait EntropicRng: rand_core::CryptoRng {}
38

            
39
impl EntropicRng for CautiousRng {}
40

            
41
/// Functionality for testing Rng code that requires an EntropicRng.
42
#[cfg(feature = "testing")]
43
mod 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")]
65
#[cfg_attr(docsrs, doc(cfg(feature = "rpc")))]
66
pub use testing::FakeEntropicRng;
67

            
68
/// An exceptionally cautious wrapper for [`rand_core::OsRng`]
69
///
70
/// Ordinarily, one trusts `OsRng`.
71
/// But we want Arti to run on a wide variety of platforms,
72
/// and the chances of a bogus OsRng increases the more places we run.
73
/// This Rng combines OsRng with several other entropy sources,
74
/// in an attempt to reduce the likelihood of creating compromised keys.[^scary]
75
///
76
/// This Rng is slower than `OsRng`.
77
///
78
/// # Panics
79
///
80
/// This rng will panic if `OsRng` fails;
81
/// but that's the only sensible behavior for a cryptographic-heavy application like ours.
82
///
83
/// [^scary]: Who else remembers [CVE-2008-0166](https://www.cve.org/CVERecord?id=CVE-2008-0166)?
84
#[derive(Default)]
85
#[allow(clippy::exhaustive_structs)]
86
pub struct CautiousRng;
87

            
88
impl rand_core::RngCore for CautiousRng {
89
    fn next_u32(&mut self) -> u32 {
90
        let mut buf = Zeroizing::new([0_u8; 4]);
91
        self.fill_bytes(buf.as_mut());
92
        u32::from_le_bytes(*buf)
93
    }
94

            
95
    fn next_u64(&mut self) -> u64 {
96
        let mut buf = Zeroizing::new([0_u8; 8]);
97
        self.fill_bytes(buf.as_mut());
98
        u64::from_le_bytes(*buf)
99
    }
100

            
101
1221
    fn fill_bytes(&mut self, dest: &mut [u8]) {
102
1221
        let mut xof = Shake256::default();
103
1221
        let mut buf = Zeroizing::new([0_u8; 32]);
104

            
105
        // According to some oldschool crypto wisdom,
106
        // provided by cryptographers wearing tinfoil hats,
107
        // when you're making a construction like this you should poll your RNGs
108
        // from least trusted to most-trusted,
109
        // in case one of the least trusted ones is secretly Pascal's Demon,
110
        // providing the input deliberately tuned to make your Shake256 output predictable.
111
        //
112
        // The idea is somewhat ludicrous, but we have to poll in _some_ order,
113
        // and just writing this code has put us into a world of tinfoil hats.
114

            
115
        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
116
1221
        if let Ok(mut rdrand) = rdrand::RdRand::new() {
117
1221
            // We'll tolerate a failure from rdrand here,
118
1221
            // since it can indicate a few different error conditions,
119
1221
            // including a lack of hardware support, or exhausted CPU entropy
120
1221
            // (whatever that is supposed to mean).
121
1221
            // We only want to panic on a failure from OsRng.
122
1221
            let _ignore_failure = rdrand.try_fill_bytes(buf.as_mut());
123
1221

            
124
1221
            // We add the output from rdrand unconditionally, since a partial return is possible,
125
1221
            // and since there's no real harm in doing so.
126
1221
            // (Performance is likely swamped by syscall overhead, and call to our BackupRng.)
127
1221
            // In the worst case, we just add some NULs in this case, which is fine.
128
1221
            xof.update(buf.as_ref());
129
1221
        }
130
        // TODO: Consider using rndr on aarch64.
131

            
132
        #[cfg(not(target_arch = "wasm32"))]
133
        {
134
1221
            if let Some(mut rng) = backup::backup_rng() {
135
1221
                rng.fill_bytes(buf.as_mut());
136
1221
                xof.update(buf.as_ref());
137
1221
            }
138
        }
139

            
140
1221
        rand::rng().fill_bytes(buf.as_mut());
141
1221
        xof.update(buf.as_ref());
142
1221

            
143
1221
        rand_core::OsRng
144
1221
            .try_fill_bytes(buf.as_mut())
145
1221
            .expect("No strong entropy source was available: cannot proceed");
146
1221
        xof.update(buf.as_ref());
147
1221

            
148
1221
        xof.finalize_xof_into(dest);
149
1221
    }
150
}
151

            
152
impl rand_core::CryptoRng for CautiousRng {}
153

            
154
/// A backup RNG, independent of other known sources.
155
///
156
/// Not necessarily strong, but hopefully random enough to cause an attacker some trouble
157
/// in the event of catastrophic failure.
158
///
159
/// A failure from this RNG _does not_ cause a panic.
160
#[cfg(not(target_arch = "wasm32"))]
161
mod backup {
162

            
163
    use once_cell::sync::Lazy;
164
    use rand::{rngs::ReseedingRng, RngCore};
165
    use rand_chacha::ChaCha20Core;
166
    use std::sync::{Mutex, MutexGuard};
167

            
168
    /// The type we've chosen to use for our backup Rng.
169
    ///
170
    /// (We need to box this because the default JitterRng is unnameable.)
171
    ///
172
    /// We use JitterRng to reseed a ChaCha20 core
173
    /// because it is potentially _very_ slow.
174
    type BackupRng = ReseedingRng<ChaCha20Core, Box<dyn RngCore + Send>>;
175

            
176
    /// Static instance of our BackupRng; None if we failed to construct one.
177
    static JITTER_BACKUP: Lazy<Option<Mutex<BackupRng>>> = Lazy::new(new_backup_rng);
178

            
179
    /// Construct a new instance of our backup Rng;
180
    /// return None on failure.
181
185
    fn new_backup_rng() -> Option<Mutex<BackupRng>> {
182
185
        let jitter = rand_jitter::JitterRng::new().ok()?;
183
185
        let jitter: Box<dyn RngCore + Send> = Box::new(jitter);
184
        // The "1024" here is chosen more or less arbitrarily;
185
        // we might want to tune it if we find that it matters.
186
185
        let reseeding = ReseedingRng::new(1024, jitter).ok()?;
187
185
        Some(Mutex::new(reseeding))
188
185
    }
189

            
190
    /// Return a MutexGuard for our backup rng, or None if we couldn't construct one.
191
1221
    pub(super) fn backup_rng() -> Option<MutexGuard<'static, BackupRng>> {
192
1221
        JITTER_BACKUP
193
1221
            .as_ref()
194
2475
            .map(|mutex| mutex.lock().expect("lock poisoned"))
195
1221
    }
196
}