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 std::convert::Infallible;
20

            
21
use digest::{ExtendableOutput, Update};
22

            
23
use rand::rngs::SysRng;
24
use rand_core::TryRng;
25
use sha3::Shake256;
26
use 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.
40
pub trait EntropicRng: rand_core::CryptoRng {}
41

            
42
impl EntropicRng for CautiousRng {}
43

            
44
/// Functionality for testing Rng code that requires an EntropicRng.
45
#[cfg(feature = "testing")]
46
mod 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")]
75
pub 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)]
95
pub struct CautiousRng;
96

            
97
impl TryRng for CautiousRng {
98
    type Error = Infallible;
99

            
100
3160
    fn try_next_u32(&mut self) -> Result<u32, Infallible> {
101
3160
        let mut buf = Zeroizing::new([0_u8; 4]);
102
3160
        self.try_fill_bytes(buf.as_mut())?;
103
3160
        Ok(u32::from_le_bytes(*buf))
104
3160
    }
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
68840
    fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Infallible> {
113
68840
        let mut xof = Shake256::default();
114
68840
        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", target_arch = "aarch64"))]
127
68840
        if let Ok(mut rdrand) = rdrand::RdRand::new() {
128
68840
            // We'll tolerate a failure from rdrand here,
129
68840
            // since it can indicate a few different error conditions,
130
68840
            // including a lack of hardware support, or exhausted CPU entropy
131
68840
            // (whatever that is supposed to mean).
132
68840
            // We only want to panic on a failure from SysRng.
133
68840
            let _ignore_failure = rdrand.try_fill_bytes(buf.as_mut());
134
68840

            
135
68840
            // We add the output from rdrand unconditionally, since a partial return is possible,
136
68840
            // and since there's no real harm in doing so.
137
68840
            // (Performance is likely swamped by syscall overhead, and call to our BackupRng.)
138
68840
            // In the worst case, we just add some NULs in this case, which is fine.
139
68840
            xof.update(buf.as_ref());
140
68840
        }
141

            
142
        #[cfg(not(target_arch = "wasm32"))]
143
        {
144
68840
            if let Some(mut rng) = backup::backup_rng() {
145
68840
                let _ignore_failure = rng.try_fill_bytes(buf.as_mut());
146
68840
                xof.update(buf.as_ref());
147
68840
            }
148
        }
149

            
150
68840
        rand::rng().try_fill_bytes(buf.as_mut())?;
151
68840
        xof.update(buf.as_ref());
152

            
153
68840
        SysRng
154
68840
            .try_fill_bytes(buf.as_mut())
155
68840
            .expect("No strong entropy source was available: cannot proceed");
156
68840
        xof.update(buf.as_ref());
157

            
158
68840
        xof.finalize_xof_into(dest);
159

            
160
68840
        Ok(())
161
68840
    }
162
}
163

            
164
impl rand_core::TryCryptoRng for CautiousRng {}
165

            
166
/// A backup RNG, independent of other known sources.
167
///
168
/// Not necessarily strong, but hopefully random enough to cause an attacker some trouble
169
/// in the event of catastrophic failure.
170
///
171
/// A failure from this RNG _does not_ cause a panic.
172
#[cfg(not(target_arch = "wasm32"))]
173
mod backup {
174

            
175
    use rand::TryRng;
176
    use rand_chacha::ChaCha20Rng;
177
    use reseeding_rng::ReseedingRng;
178
    use std::convert::Infallible;
179
    use std::sync::LazyLock;
180
    use std::sync::{Mutex, MutexGuard};
181

            
182
    /// The type we've chosen to use for our backup Rng.
183
    ///
184
    /// (We need to box this because the default JitterRng is unnameable.)
185
    ///
186
    /// We use JitterRng to reseed a ChaCha20 core
187
    /// because it is potentially _very_ slow.
188
    type BackupRng = ReseedingRng<ChaCha20Rng, Box<dyn TryRng<Error = Infallible> + Send>>;
189

            
190
    /// Static instance of our BackupRng; None if we failed to construct one.
191
    static JITTER_BACKUP: LazyLock<Option<Mutex<BackupRng>>> = LazyLock::new(new_backup_rng);
192

            
193
    /// Construct a new instance of our backup Rng;
194
    /// return None on failure.
195
280
    fn new_backup_rng() -> Option<Mutex<BackupRng>> {
196
280
        let jitter = rand_jitter::JitterRng::new().ok()?;
197
280
        let jitter: Box<dyn TryRng<Error = Infallible> + Send> = Box::new(jitter);
198
        // The "1024" here is chosen more or less arbitrarily;
199
        // we might want to tune it if we find that it matters.
200
280
        let reseeding = ReseedingRng::try_new(1024, jitter).ok()?;
201
280
        Some(Mutex::new(reseeding))
202
280
    }
203

            
204
    /// Return a MutexGuard for our backup rng, or None if we couldn't construct one.
205
68840
    pub(super) fn backup_rng() -> Option<MutexGuard<'static, BackupRng>> {
206
68840
        JITTER_BACKUP
207
68840
            .as_ref()
208
142843
            .map(|mutex| mutex.lock().expect("lock poisoned"))
209
68840
    }
210
}