]>
Commit | Line | Data |
---|---|---|
416331ca XL |
1 | // Copyright 2018 Developers of the Rand project. |
2 | // | |
3 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | |
4 | // https://www.apache.org/licenses/LICENSE-2.0> or the MIT license | |
5 | // <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your | |
6 | // option. This file may not be copied, modified, or distributed | |
7 | // except according to those terms. | |
8 | ||
9 | //! Entropy generator, or wrapper around external generators | |
10 | ||
11 | use rand_core::{RngCore, CryptoRng, Error, ErrorKind, impls}; | |
12 | #[allow(unused)] | |
13 | use rngs; | |
14 | ||
15 | /// An interface returning random data from external source(s), provided | |
16 | /// specifically for securely seeding algorithmic generators (PRNGs). | |
17 | /// | |
18 | /// Where possible, `EntropyRng` retrieves random data from the operating | |
19 | /// system's interface for random numbers ([`OsRng`]); if that fails it will | |
20 | /// fall back to the [`JitterRng`] entropy collector. In the latter case it will | |
21 | /// still try to use [`OsRng`] on the next usage. | |
22 | /// | |
23 | /// If no secure source of entropy is available `EntropyRng` will panic on use; | |
24 | /// i.e. it should never output predictable data. | |
25 | /// | |
26 | /// This is either a little slow ([`OsRng`] requires a system call) or extremely | |
27 | /// slow ([`JitterRng`] must use significant CPU time to generate sufficient | |
28 | /// jitter); for better performance it is common to seed a local PRNG from | |
29 | /// external entropy then primarily use the local PRNG ([`thread_rng`] is | |
30 | /// provided as a convenient, local, automatically-seeded CSPRNG). | |
31 | /// | |
32 | /// # Panics | |
33 | /// | |
34 | /// On most systems, like Windows, Linux, macOS and *BSD on common hardware, it | |
35 | /// is highly unlikely for both [`OsRng`] and [`JitterRng`] to fail. But on | |
36 | /// combinations like webassembly without Emscripten or stdweb both sources are | |
37 | /// unavailable. If both sources fail, only [`try_fill_bytes`] is able to | |
38 | /// report the error, and only the one from `OsRng`. The other [`RngCore`] | |
39 | /// methods will panic in case of an error. | |
40 | /// | |
41 | /// [`OsRng`]: struct.OsRng.html | |
42 | /// [`JitterRng`]: jitter/struct.JitterRng.html | |
43 | /// [`thread_rng`]: ../fn.thread_rng.html | |
44 | /// [`RngCore`]: ../trait.RngCore.html | |
45 | /// [`try_fill_bytes`]: ../trait.RngCore.html#method.tymethod.try_fill_bytes | |
46 | #[derive(Debug)] | |
47 | pub struct EntropyRng { | |
48 | source: Source, | |
49 | } | |
50 | ||
51 | #[derive(Debug)] | |
52 | enum Source { | |
53 | Os(Os), | |
54 | Custom(Custom), | |
55 | Jitter(Jitter), | |
56 | None, | |
57 | } | |
58 | ||
59 | impl EntropyRng { | |
60 | /// Create a new `EntropyRng`. | |
61 | /// | |
62 | /// This method will do no system calls or other initialization routines, | |
63 | /// those are done on first use. This is done to make `new` infallible, | |
64 | /// and `try_fill_bytes` the only place to report errors. | |
65 | pub fn new() -> Self { | |
66 | EntropyRng { source: Source::None } | |
67 | } | |
68 | } | |
69 | ||
70 | impl Default for EntropyRng { | |
71 | fn default() -> Self { | |
72 | EntropyRng::new() | |
73 | } | |
74 | } | |
75 | ||
76 | impl RngCore for EntropyRng { | |
77 | fn next_u32(&mut self) -> u32 { | |
78 | impls::next_u32_via_fill(self) | |
79 | } | |
80 | ||
81 | fn next_u64(&mut self) -> u64 { | |
82 | impls::next_u64_via_fill(self) | |
83 | } | |
84 | ||
85 | fn fill_bytes(&mut self, dest: &mut [u8]) { | |
86 | self.try_fill_bytes(dest).unwrap_or_else(|err| | |
87 | panic!("all entropy sources failed; first error: {}", err)) | |
88 | } | |
89 | ||
90 | fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { | |
91 | let mut reported_error = None; | |
92 | ||
93 | if let Source::Os(ref mut os_rng) = self.source { | |
94 | match os_rng.fill(dest) { | |
95 | Ok(()) => return Ok(()), | |
96 | Err(err) => { | |
97 | warn!("EntropyRng: OsRng failed \ | |
98 | [trying other entropy sources]: {}", err); | |
99 | reported_error = Some(err); | |
100 | }, | |
101 | } | |
102 | } else if Os::is_supported() { | |
103 | match Os::new_and_fill(dest) { | |
104 | Ok(os_rng) => { | |
105 | debug!("EntropyRng: using OsRng"); | |
106 | self.source = Source::Os(os_rng); | |
107 | return Ok(()); | |
108 | }, | |
109 | Err(err) => { reported_error = reported_error.or(Some(err)) }, | |
110 | } | |
111 | } | |
112 | ||
113 | if let Source::Custom(ref mut rng) = self.source { | |
114 | match rng.fill(dest) { | |
115 | Ok(()) => return Ok(()), | |
116 | Err(err) => { | |
117 | warn!("EntropyRng: custom entropy source failed \ | |
118 | [trying other entropy sources]: {}", err); | |
119 | reported_error = Some(err); | |
120 | }, | |
121 | } | |
122 | } else if Custom::is_supported() { | |
123 | match Custom::new_and_fill(dest) { | |
124 | Ok(custom) => { | |
125 | debug!("EntropyRng: using custom entropy source"); | |
126 | self.source = Source::Custom(custom); | |
127 | return Ok(()); | |
128 | }, | |
129 | Err(err) => { reported_error = reported_error.or(Some(err)) }, | |
130 | } | |
131 | } | |
132 | ||
133 | if let Source::Jitter(ref mut jitter_rng) = self.source { | |
134 | match jitter_rng.fill(dest) { | |
135 | Ok(()) => return Ok(()), | |
136 | Err(err) => { | |
137 | warn!("EntropyRng: JitterRng failed: {}", err); | |
138 | reported_error = Some(err); | |
139 | }, | |
140 | } | |
141 | } else if Jitter::is_supported() { | |
142 | match Jitter::new_and_fill(dest) { | |
143 | Ok(jitter_rng) => { | |
144 | debug!("EntropyRng: using JitterRng"); | |
145 | self.source = Source::Jitter(jitter_rng); | |
146 | return Ok(()); | |
147 | }, | |
148 | Err(err) => { reported_error = reported_error.or(Some(err)) }, | |
149 | } | |
150 | } | |
151 | ||
152 | if let Some(err) = reported_error { | |
153 | Err(Error::with_cause(ErrorKind::Unavailable, | |
154 | "All entropy sources failed", | |
155 | err)) | |
156 | } else { | |
157 | Err(Error::new(ErrorKind::Unavailable, | |
158 | "No entropy sources available")) | |
159 | } | |
160 | } | |
161 | } | |
162 | ||
163 | impl CryptoRng for EntropyRng {} | |
164 | ||
165 | ||
166 | ||
167 | trait EntropySource { | |
168 | fn new_and_fill(dest: &mut [u8]) -> Result<Self, Error> | |
169 | where Self: Sized; | |
170 | ||
171 | fn fill(&mut self, dest: &mut [u8]) -> Result<(), Error>; | |
172 | ||
173 | fn is_supported() -> bool { true } | |
174 | } | |
175 | ||
176 | #[allow(unused)] | |
177 | #[derive(Clone, Debug)] | |
178 | struct NoSource; | |
179 | ||
180 | #[allow(unused)] | |
181 | impl EntropySource for NoSource { | |
182 | fn new_and_fill(dest: &mut [u8]) -> Result<Self, Error> { | |
183 | Err(Error::new(ErrorKind::Unavailable, "Source not supported")) | |
184 | } | |
185 | ||
186 | fn fill(&mut self, dest: &mut [u8]) -> Result<(), Error> { | |
187 | unreachable!() | |
188 | } | |
189 | ||
190 | fn is_supported() -> bool { false } | |
191 | } | |
192 | ||
193 | ||
194 | #[cfg(all(feature="std", | |
195 | any(target_os = "linux", target_os = "android", | |
196 | target_os = "netbsd", | |
197 | target_os = "dragonfly", | |
198 | target_os = "haiku", | |
199 | target_os = "emscripten", | |
200 | target_os = "solaris", | |
201 | target_os = "cloudabi", | |
202 | target_os = "macos", target_os = "ios", | |
203 | target_os = "freebsd", | |
204 | target_os = "openbsd", target_os = "bitrig", | |
205 | target_os = "redox", | |
206 | target_os = "fuchsia", | |
207 | windows, | |
208 | all(target_arch = "wasm32", feature = "stdweb"), | |
209 | all(target_arch = "wasm32", feature = "wasm-bindgen"), | |
210 | )))] | |
211 | #[derive(Clone, Debug)] | |
212 | pub struct Os(rngs::OsRng); | |
213 | ||
214 | #[cfg(all(feature="std", | |
215 | any(target_os = "linux", target_os = "android", | |
216 | target_os = "netbsd", | |
217 | target_os = "dragonfly", | |
218 | target_os = "haiku", | |
219 | target_os = "emscripten", | |
220 | target_os = "solaris", | |
221 | target_os = "cloudabi", | |
222 | target_os = "macos", target_os = "ios", | |
223 | target_os = "freebsd", | |
224 | target_os = "openbsd", target_os = "bitrig", | |
225 | target_os = "redox", | |
226 | target_os = "fuchsia", | |
227 | windows, | |
228 | all(target_arch = "wasm32", feature = "stdweb"), | |
229 | all(target_arch = "wasm32", feature = "wasm-bindgen"), | |
230 | )))] | |
231 | impl EntropySource for Os { | |
232 | fn new_and_fill(dest: &mut [u8]) -> Result<Self, Error> { | |
233 | let mut rng = rngs::OsRng::new()?; | |
234 | rng.try_fill_bytes(dest)?; | |
235 | Ok(Os(rng)) | |
236 | } | |
237 | ||
238 | fn fill(&mut self, dest: &mut [u8]) -> Result<(), Error> { | |
239 | self.0.try_fill_bytes(dest) | |
240 | } | |
241 | } | |
242 | ||
243 | #[cfg(not(all(feature="std", | |
244 | any(target_os = "linux", target_os = "android", | |
245 | target_os = "netbsd", | |
246 | target_os = "dragonfly", | |
247 | target_os = "haiku", | |
248 | target_os = "emscripten", | |
249 | target_os = "solaris", | |
250 | target_os = "cloudabi", | |
251 | target_os = "macos", target_os = "ios", | |
252 | target_os = "freebsd", | |
253 | target_os = "openbsd", target_os = "bitrig", | |
254 | target_os = "redox", | |
255 | target_os = "fuchsia", | |
256 | windows, | |
257 | all(target_arch = "wasm32", feature = "stdweb"), | |
258 | all(target_arch = "wasm32", feature = "wasm-bindgen"), | |
259 | ))))] | |
260 | type Os = NoSource; | |
261 | ||
262 | ||
263 | type Custom = NoSource; | |
264 | ||
265 | ||
266 | #[cfg(not(target_arch = "wasm32"))] | |
267 | #[derive(Clone, Debug)] | |
268 | pub struct Jitter(rngs::JitterRng); | |
269 | ||
270 | #[cfg(not(target_arch = "wasm32"))] | |
271 | impl EntropySource for Jitter { | |
272 | fn new_and_fill(dest: &mut [u8]) -> Result<Self, Error> { | |
273 | let mut rng = rngs::JitterRng::new()?; | |
274 | rng.try_fill_bytes(dest)?; | |
275 | Ok(Jitter(rng)) | |
276 | } | |
277 | ||
278 | fn fill(&mut self, dest: &mut [u8]) -> Result<(), Error> { | |
279 | self.0.try_fill_bytes(dest) | |
280 | } | |
281 | } | |
282 | ||
283 | #[cfg(target_arch = "wasm32")] | |
284 | type Jitter = NoSource; | |
285 | ||
286 | ||
287 | #[cfg(test)] | |
288 | mod test { | |
289 | use super::*; | |
290 | ||
291 | #[test] | |
292 | fn test_entropy() { | |
293 | let mut rng = EntropyRng::new(); | |
294 | let n = (rng.next_u32() ^ rng.next_u32()).count_ones(); | |
295 | assert!(n >= 2); // p(failure) approx 1e-7 | |
296 | } | |
297 | } |