blob: 4f139d980908a3c49c782ee6d7e6a58d73241470 [file] [log] [blame]
// 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&section=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))))]
// for 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
);
#[cfg(all(
target_arch = "wasm32",
not(target_os = "emscripten"),
not(feature = "wasm-bindgen"),
not(feature = "stdweb"),
))]
compile_error!("enable either wasm_bindgen or stdweb feature");
#[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");