summaryrefslogtreecommitdiff
path: root/crates/tor-llcrypto/src/rng.rs
blob: cd2ac93604f1251dc10eef5bdfa6e3a935ca88c8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
//! Random number generation.
//!
//! For most purposes in Arti, we use one of two random number generators:
//!  - `rand::rng()` (formerly called `rand::thread_rng()`, up till rand 0.9)
//!  - The [`CautiousRng`] implemented here.
//!
//! [`CautiousRng`] should be used whenever we are generating
//! a medium- or long-term cryptographic key:
//! one that will be stored to disk, or used for more than a single communication.
//! It is slower than [`rand::rng()`],
//! but is more robust against several kinds of failure.
//
// Note: Although we want to use CautiousRng
// whenever we generate a medium- or long-term key,
// we do not consider it a major
// security hole if we use rand::rng() instead:
// CautiousRng is a defense-in-depth mechanism.

use std::convert::Infallible;

use digest::{ExtendableOutput, Update};

use rand::rngs::SysRng;
use rand_core::TryRng;
use sha3::Shake256;
use zeroize::Zeroizing;

/// Trait representing an Rng where every output is derived from
/// supposedly strong entropy.
///
/// Implemented by [`CautiousRng`].
///
/// # Warning
///
/// Do not implement this trait for new Rngs unless you know what you are doing;
/// any Rng to which you apply this trait should be _at least_ as
/// unpredictable and secure as `SysRng`.
///
/// We recommend using [`CautiousRng`] when you need an instance of this trait.
pub trait EntropicRng: rand_core::CryptoRng {}

impl EntropicRng for CautiousRng {}

/// Functionality for testing Rng code that requires an EntropicRng.
#[cfg(feature = "testing")]
mod testing {
    use std::convert::Infallible;

    /// Testing only: Pretend that an inner RNG truly implements `EntropicRng`.
    #[allow(clippy::exhaustive_structs)]
    pub struct FakeEntropicRng<R>(pub R);

    impl<R: rand_core::TryRng<Error = Infallible>> rand_core::TryRng for FakeEntropicRng<R> {
        type Error = Infallible;

        fn try_next_u32(&mut self) -> Result<u32, Infallible> {
            self.0.try_next_u32()
        }

        fn try_next_u64(&mut self) -> Result<u64, Infallible> {
            self.0.try_next_u64()
        }

        fn try_fill_bytes(&mut self, dst: &mut [u8]) -> Result<(), Infallible> {
            self.0.try_fill_bytes(dst)
        }
    }
    impl<R: rand_core::TryCryptoRng<Error = Infallible>> rand_core::TryCryptoRng
        for FakeEntropicRng<R>
    {
    }
    impl<R: rand_core::CryptoRng> super::EntropicRng for FakeEntropicRng<R> {}
}
#[cfg(feature = "testing")]
pub use testing::FakeEntropicRng;

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

impl TryRng for CautiousRng {
    type Error = Infallible;

    fn try_next_u32(&mut self) -> Result<u32, Infallible> {
        let mut buf = Zeroizing::new([0_u8; 4]);
        self.try_fill_bytes(buf.as_mut())?;
        Ok(u32::from_le_bytes(*buf))
    }

    fn try_next_u64(&mut self) -> Result<u64, Infallible> {
        let mut buf = Zeroizing::new([0_u8; 8]);
        self.try_fill_bytes(buf.as_mut())?;
        Ok(u64::from_le_bytes(*buf))
    }

    fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Infallible> {
        let mut xof = Shake256::default();
        let mut buf = Zeroizing::new([0_u8; 32]);

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

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

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

        #[cfg(not(target_arch = "wasm32"))]
        {
            if let Some(mut rng) = backup::backup_rng() {
                let _ignore_failure = rng.try_fill_bytes(buf.as_mut());
                xof.update(buf.as_ref());
            }
        }

        rand::rng().try_fill_bytes(buf.as_mut())?;
        xof.update(buf.as_ref());

        SysRng
            .try_fill_bytes(buf.as_mut())
            .expect("No strong entropy source was available: cannot proceed");
        xof.update(buf.as_ref());

        xof.finalize_xof_into(dest);

        Ok(())
    }
}

impl rand_core::TryCryptoRng for CautiousRng {}

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

    use rand::TryRng;
    use rand_chacha::ChaCha20Rng;
    use reseeding_rng::ReseedingRng;
    use std::convert::Infallible;
    use std::sync::LazyLock;
    use std::sync::{Mutex, MutexGuard};

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

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

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

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