diff options
Diffstat (limited to 'rand/rand_os/src/lib.rs')
-rw-r--r-- | rand/rand_os/src/lib.rs | 439 |
1 files changed, 439 insertions, 0 deletions
diff --git a/rand/rand_os/src/lib.rs b/rand/rand_os/src/lib.rs new file mode 100644 index 0000000..67b0dfe --- /dev/null +++ b/rand/rand_os/src/lib.rs @@ -0,0 +1,439 @@ +// Copyright 2018 Developers of the Rand project. +// Copyright 2013-2015 The Rust Project Developers. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Interface to the random number generator of the operating system. +//! +//! `OsRng` is the preferred external source of entropy for most applications. +//! Commonly it is used to initialize a user-space RNG, which can then be used +//! to generate random values with much less overhead than `OsRng`. +//! +//! You may prefer to use [`EntropyRng`] instead of `OsRng`. It is unlikely, but +//! not entirely theoretical, for `OsRng` to fail. In such cases [`EntropyRng`] +//! falls back on a good alternative entropy source. +//! +//! `OsRng::new()` is guaranteed to be very cheap (after the first successful +//! call), and will never consume more than one file handle per process. +//! +//! # Usage example +//! ``` +//! use rand_os::OsRng; +//! use rand_os::rand_core::RngCore; +//! +//! let mut os_rng = OsRng::new().unwrap(); +//! let mut key = [0u8; 16]; +//! os_rng.fill_bytes(&mut key); +//! let random_u64 = os_rng.next_u64(); +//! ``` +//! +//! # Platform sources +//! +//! | OS | interface +//! |------------------|--------------------------------------------------------- +//! | Linux, Android | [`getrandom`][1] system call if available, otherwise [`/dev/urandom`][2] after reading from `/dev/random` once +//! | Windows | [`RtlGenRandom`][3] +//! | macOS, iOS | [`SecRandomCopyBytes`][4] +//! | FreeBSD | [`kern.arandom`][5] +//! | OpenBSD, Bitrig | [`getentropy`][6] +//! | NetBSD | [`/dev/urandom`][7] after reading from `/dev/random` once +//! | Dragonfly BSD | [`/dev/random`][8] +//! | Solaris, illumos | [`getrandom`][9] system call if available, otherwise [`/dev/random`][10] +//! | Fuchsia OS | [`cprng_draw`][11] +//! | Redox | [`rand:`][12] +//! | CloudABI | [`random_get`][13] +//! | Haiku | `/dev/random` (identical to `/dev/urandom`) +//! | Web browsers | [`Crypto.getRandomValues`][14] (see [Support for WebAssembly and ams.js][14]) +//! | Node.js | [`crypto.randomBytes`][15] (see [Support for WebAssembly and ams.js][16]) +//! +//! Rand doesn't have a blanket implementation for all Unix-like operating +//! systems that reads from `/dev/urandom`. This ensures all supported operating +//! systems are using the recommended interface and respect maximum buffer +//! sizes. +//! +//! ## Support for WebAssembly and ams.js +//! +//! The three Emscripten targets `asmjs-unknown-emscripten`, +//! `wasm32-unknown-emscripten` and `wasm32-experimental-emscripten` use +//! Emscripten's emulation of `/dev/random` on web browsers and Node.js. +//! +//! The bare WASM target `wasm32-unknown-unknown` tries to call the javascript +//! methods directly, using either `stdweb` or `wasm-bindgen` depending on what +//! features are activated for this crate. Note that if both features are +//! enabled `wasm-bindgen` will be used. +//! +//! ## Early boot +//! +//! It is possible that early in the boot process the OS hasn't had enough time +//! yet to collect entropy to securely seed its RNG, especially on virtual +//! machines. +//! +//! Some operating systems always block the thread until the RNG is securely +//! seeded. This can take anywhere from a few seconds to more than a minute. +//! Others make a best effort to use a seed from before the shutdown and don't +//! document much. +//! +//! A few, Linux, NetBSD and Solaris, offer a choice between blocking, and +//! getting an error. With `try_fill_bytes` we choose to get the error +//! ([`ErrorKind::NotReady`]), while the other methods use a blocking interface. +//! +//! On Linux (when the `genrandom` system call is not available) and on NetBSD +//! reading from `/dev/urandom` never blocks, even when the OS hasn't collected +//! enough entropy yet. As a countermeasure we try to do a single read from +//! `/dev/random` until we know the OS RNG is initialized (and store this in a +//! global static). +//! +//! # Panics and error handling +//! +//! We cannot guarantee that `OsRng` will fail, but if it does, it will likely +//! be either when `OsRng::new()` is first called or when data is first read. +//! If you wish to catch errors early, then test reading of at least one byte +//! from `OsRng` via [`try_fill_bytes`]. If this succeeds, it is extremely +//! unlikely that any further errors will occur. +//! +//! Only [`try_fill_bytes`] is able to report the cause of an error; the other +//! [`RngCore`] methods may (depending on the error kind) retry several times, +//! but must eventually panic if the error persists. +//! +//! [`EntropyRng`]: ../rand/rngs/struct.EntropyRng.html +//! [`RngCore`]: ../rand_core/trait.RngCore.html +//! [`try_fill_bytes`]: ../rand_core/trait.RngCore.html#method.tymethod.try_fill_bytes +//! [`ErrorKind::NotReady`]: ../rand_core/enum.ErrorKind.html#variant.NotReady +//! +//! [1]: http://man7.org/linux/man-pages/man2/getrandom.2.html +//! [2]: http://man7.org/linux/man-pages/man4/urandom.4.html +//! [3]: https://msdn.microsoft.com/en-us/library/windows/desktop/aa387694.aspx +//! [4]: https://developer.apple.com/documentation/security/1399291-secrandomcopybytes?language=objc +//! [5]: https://www.freebsd.org/cgi/man.cgi?query=random&sektion=4 +//! [6]: https://man.openbsd.org/getentropy.2 +//! [7]: http://netbsd.gw.com/cgi-bin/man-cgi?random+4+NetBSD-current +//! [8]: https://leaf.dragonflybsd.org/cgi/web-man?command=random§ion=4 +//! [9]: https://docs.oracle.com/cd/E88353_01/html/E37841/getrandom-2.html +//! [10]: https://docs.oracle.com/cd/E86824_01/html/E54777/random-7d.html +//! [11]: https://fuchsia.googlesource.com/zircon/+/HEAD/docs/syscalls/cprng_draw.md +//! [12]: https://github.com/redox-os/randd/blob/master/src/main.rs +//! [13]: https://github.com/NuxiNL/cloudabi/blob/v0.20/cloudabi.txt#L1826 +//! [14]: https://www.w3.org/TR/WebCryptoAPI/#Crypto-method-getRandomValues +//! [15]: https://nodejs.org/api/crypto.html#crypto_crypto_randombytes_size_callback +//! [16]: #support-for-webassembly-and-amsjs +#![doc(html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk.png", + html_favicon_url = "https://www.rust-lang.org/favicon.ico", + html_root_url = "https://rust-random.github.io/rand/")] +#![deny(missing_docs)] +#![deny(missing_debug_implementations)] +#![doc(test(attr(allow(unused_variables), deny(warnings))))] + +#![cfg_attr(feature = "stdweb", recursion_limit="128")] + +pub extern crate rand_core; +#[cfg(feature = "log")] +#[macro_use] extern crate log; + +// We have to do it here because we load macros +#[cfg(all(target_arch = "wasm32", not(target_os = "emscripten"), + feature = "wasm-bindgen"))] +extern crate wasm_bindgen; +#[cfg(all(target_arch = "wasm32", not(target_os = "emscripten"), + not(feature = "wasm-bindgen"), + feature = "stdweb"))] +#[macro_use] extern crate stdweb; + +#[cfg(target_env = "sgx")] +extern crate rdrand; + +#[cfg(not(feature = "log"))] +#[macro_use] +mod dummy_log; + +use std::fmt; +use rand_core::{CryptoRng, RngCore, Error, impls}; + +/// A random number generator that retrieves randomness straight from the +/// operating system. +#[derive(Clone)] +pub struct OsRng(imp::OsRng); + +impl fmt::Debug for OsRng { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +impl OsRng { + /// Create a new `OsRng`. + pub fn new() -> Result<OsRng, Error> { + imp::OsRng::new().map(OsRng) + } +} + +impl CryptoRng for OsRng {} + +impl RngCore for OsRng { + fn next_u32(&mut self) -> u32 { + impls::next_u32_via_fill(self) + } + + fn next_u64(&mut self) -> u64 { + impls::next_u64_via_fill(self) + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + use std::{time, thread}; + + // We cannot return Err(..), so we try to handle before panicking. + const MAX_RETRY_PERIOD: u32 = 10; // max 10s + const WAIT_DUR_MS: u32 = 100; // retry every 100ms + let wait_dur = time::Duration::from_millis(WAIT_DUR_MS as u64); + const RETRY_LIMIT: u32 = (MAX_RETRY_PERIOD * 1000) / WAIT_DUR_MS; + const TRANSIENT_RETRIES: u32 = 8; + let mut err_count = 0; + let mut error_logged = false; + + // Maybe block until the OS RNG is initialized + let mut read = 0; + if let Ok(n) = self.0.test_initialized(dest, true) { read = n }; + let dest = &mut dest[read..]; + + loop { + if let Err(e) = self.try_fill_bytes(dest) { + if err_count >= RETRY_LIMIT { + error!("OsRng failed too many times; last error: {}", e); + panic!("OsRng failed too many times; last error: {}", e); + } + + if e.kind.should_wait() { + if !error_logged { + warn!("OsRng failed; waiting up to {}s and retrying. Error: {}", + MAX_RETRY_PERIOD, e); + error_logged = true; + } + err_count += 1; + thread::sleep(wait_dur); + continue; + } else if e.kind.should_retry() { + if !error_logged { + warn!("OsRng failed; retrying up to {} times. Error: {}", + TRANSIENT_RETRIES, e); + error_logged = true; + } + err_count += (RETRY_LIMIT + TRANSIENT_RETRIES - 1) + / TRANSIENT_RETRIES; // round up + continue; + } else { + error!("OsRng failed: {}", e); + panic!("OsRng fatal error: {}", e); + } + } + + break; + } + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { + // Some systems do not support reading 0 random bytes. + // (And why waste a system call?) + if dest.len() == 0 { return Ok(()); } + + let read = self.0.test_initialized(dest, false)?; + let dest = &mut dest[read..]; + + let max = self.0.max_chunk_size(); + if dest.len() <= max { + trace!("OsRng: reading {} bytes via {}", + dest.len(), self.0.method_str()); + } else { + trace!("OsRng: reading {} bytes via {} in {} chunks of {} bytes", + dest.len(), self.0.method_str(), (dest.len() + max) / max, max); + } + for slice in dest.chunks_mut(max) { + self.0.fill_chunk(slice)?; + } + Ok(()) + } +} + +trait OsRngImpl where Self: Sized { + // Create a new `OsRng` platform interface. + fn new() -> Result<Self, Error>; + + // Fill a chunk with random bytes. + fn fill_chunk(&mut self, dest: &mut [u8]) -> Result<(), Error>; + + // Test whether the OS RNG is initialized. This method may not be possible + // to support cheaply (or at all) on all operating systems. + // + // If `blocking` is set, this will cause the OS the block execution until + // its RNG is initialized. + // + // Random values that are read while this are stored in `dest`, the amount + // of read bytes is returned. + fn test_initialized(&mut self, _dest: &mut [u8], _blocking: bool) + -> Result<usize, Error> { Ok(0) } + + // Maximum chunk size supported. + fn max_chunk_size(&self) -> usize { ::std::usize::MAX } + + // Name of the OS interface (used for logging). + fn method_str(&self) -> &'static str; +} + +#[cfg(any(target_os = "linux", target_os = "android", + target_os = "netbsd", target_os = "dragonfly", + target_os = "solaris", target_os = "redox", + target_os = "haiku", target_os = "emscripten"))] +mod random_device; + +macro_rules! mod_use { + ($cond:meta, $module:ident) => { + #[$cond] + mod $module; + #[$cond] + use $module as imp; + } +} + +mod_use!(cfg(target_os = "android"), linux_android); +mod_use!(cfg(target_os = "bitrig"), openbsd_bitrig); +mod_use!(cfg(target_os = "cloudabi"), cloudabi); +mod_use!(cfg(target_os = "dragonfly"), dragonfly_haiku_emscripten); +mod_use!(cfg(target_os = "emscripten"), dragonfly_haiku_emscripten); +mod_use!(cfg(target_os = "freebsd"), freebsd); +mod_use!(cfg(target_os = "fuchsia"), fuchsia); +mod_use!(cfg(target_os = "haiku"), dragonfly_haiku_emscripten); +mod_use!(cfg(target_os = "ios"), macos); +mod_use!(cfg(target_os = "linux"), linux_android); +mod_use!(cfg(target_os = "macos"), macos); +mod_use!(cfg(target_os = "netbsd"), netbsd); +mod_use!(cfg(target_os = "openbsd"), openbsd_bitrig); +mod_use!(cfg(target_os = "redox"), redox); +mod_use!(cfg(target_os = "solaris"), solaris); +mod_use!(cfg(windows), windows); +mod_use!(cfg(target_env = "sgx"), sgx); + +mod_use!( + cfg(all( + target_arch = "wasm32", + not(target_os = "emscripten"), + feature = "wasm-bindgen" + )), + wasm32_bindgen +); + +mod_use!( + cfg(all( + target_arch = "wasm32", + not(target_os = "emscripten"), + not(feature = "wasm-bindgen"), + feature = "stdweb", + )), + wasm32_stdweb +); + +/// Per #678 we use run-time failure where WASM bindings are missing +#[cfg(all( + target_arch = "wasm32", + not(target_os = "emscripten"), + not(feature = "wasm-bindgen"), + not(feature = "stdweb"), +))] +mod imp { + use rand_core::{Error, ErrorKind}; + use super::OsRngImpl; + + #[derive(Clone, Debug)] + pub struct OsRng; + + impl OsRngImpl for OsRng { + fn new() -> Result<OsRng, Error> { + Err(Error::new(ErrorKind::Unavailable, + "OsRng: support for wasm32 requires emscripten, stdweb or wasm-bindgen")) + } + + fn fill_chunk(&mut self, _dest: &mut [u8]) -> Result<(), Error> { + unimplemented!() + } + + fn method_str(&self) -> &'static str { unimplemented!() } + } +} + +#[cfg(not(any( + target_os = "android", + target_os = "bitrig", + target_os = "cloudabi", + target_os = "dragonfly", + target_os = "emscripten", + target_os = "freebsd", + target_os = "fuchsia", + target_os = "haiku", + target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "redox", + target_os = "solaris", + windows, + target_arch = "wasm32", + target_env = "sgx" +)))] +compile_error!("OS RNG support is not available for this platform"); + +// Due to rustwasm/wasm-bindgen#201 this can't be defined in the inner os +// modules, so hack around it for now and place it at the root. +#[cfg(all(feature = "wasm-bindgen", target_arch = "wasm32"))] +#[doc(hidden)] +#[allow(missing_debug_implementations)] +pub mod __wbg_shims { + + // `extern { type Foo; }` isn't supported on 1.22 syntactically, so use a + // macro to work around that. + macro_rules! rust_122_compat { + ($($t:tt)*) => ($($t)*) + } + + rust_122_compat! { + extern crate wasm_bindgen; + + pub use wasm_bindgen::prelude::*; + + #[wasm_bindgen] + extern "C" { + pub type Function; + #[wasm_bindgen(constructor)] + pub fn new(s: &str) -> Function; + #[wasm_bindgen(method)] + pub fn call(this: &Function, self_: &JsValue) -> JsValue; + + pub type This; + #[wasm_bindgen(method, getter, structural, js_name = self)] + pub fn self_(me: &This) -> JsValue; + #[wasm_bindgen(method, getter, structural)] + pub fn crypto(me: &This) -> JsValue; + + #[derive(Clone, Debug)] + pub type BrowserCrypto; + + // TODO: these `structural` annotations here ideally wouldn't be here to + // avoid a JS shim, but for now with feature detection they're + // unavoidable. + #[wasm_bindgen(method, js_name = getRandomValues, structural, getter)] + pub fn get_random_values_fn(me: &BrowserCrypto) -> JsValue; + #[wasm_bindgen(method, js_name = getRandomValues, structural)] + pub fn get_random_values(me: &BrowserCrypto, buf: &mut [u8]); + + #[wasm_bindgen(js_name = require)] + pub fn node_require(s: &str) -> NodeCrypto; + + #[derive(Clone, Debug)] + pub type NodeCrypto; + + #[wasm_bindgen(method, js_name = randomFillSync, structural)] + pub fn random_fill_sync(me: &NodeCrypto, buf: &mut [u8]); + } + } +} |