diff --git a/CHANGELOG.md b/CHANGELOG.md index 1aa63989734..c9d235c54a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,19 @@ A [separate changelog is kept for rand_core](rand_core/CHANGELOG.md). You may also find the [Update Guide](UPDATING.md) useful. +## [0.5.1] - Unreleased + +### Platform support and `OsRng` +- Remove blanket Unix implementation. (#484) +- Remove Wasm unimplemented stub. (#484) +- Dragonfly BSD: read from `/dev/random`. (#484) +- Bitrig: use `getentropy` like OpenBSD. (#484) +- Solaris: (untested) use `getrandom` if available, otherwise `/dev/random`. (#484) +- Emscripten, `stdweb`: split the read up in chunks. (#484) +- Emscripten, Haiku: don't do an extra blocking read from `/dev/random`. (#484) +- Linux, NetBSD, Solaris: read in blocking mode on first use in `fill_bytes`. (#484) +- Fuchsia, CloudABI: fix compilation (broken in Rand 0.5). (#484) + ## [0.5.0] - 2018-05-21 ### Crate features and organisation diff --git a/README.md b/README.md index d897387f994..1fe6fa56820 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,8 @@ optional features are available: - `log` enables some logging via the `log` crate. - `nightly` enables all unstable features (`i128_support`). - `serde1` enables serialization for some types, via Serde version 1. -- `stdweb` enables support for `OsRng` on WASM via stdweb. +- `stdweb` enables support for `OsRng` on `wasm-unknown-unknown` via `stdweb` + combined with `cargo-web`. `no_std` mode is activated by setting `default-features = false`; this removes functionality depending on `std`: diff --git a/src/rngs/os.rs b/src/rngs/os.rs index 2239d4577ea..01fdfb0c1f5 100644 --- a/src/rngs/os.rs +++ b/src/rngs/os.rs @@ -24,60 +24,96 @@ use rand_core::{CryptoRng, RngCore, Error, impls}; /// not entirely theoretical, for `OsRng` to fail. In such cases [`EntropyRng`] /// falls back on a good alternative entropy source. /// -/// `OsRng` usually does not block. On some systems, and notably virtual -/// machines, it may block very early in the init process, when the OS CSPRNG -/// has not yet been seeded. -/// /// `OsRng::new()` is guaranteed to be very cheap (after the first successful /// call), and will never consume more than one file handle per process. /// /// # Platform sources /// -/// - Linux, Android: reads from the `getrandom(2)` system call if available, -/// otherwise from `/dev/urandom`. -/// - macOS, iOS: calls `SecRandomCopyBytes`. -/// - Windows: calls `RtlGenRandom`. -/// - WASM (with `stdweb` feature): calls `window.crypto.getRandomValues` in -/// browsers, and in Node.js `require("crypto").randomBytes`. -/// - Emscripten: reads from emulated `/dev/urandom`, which maps to the same -/// interfaces as `stdweb`, but falls back to the insecure `Math.random()` if -/// unavailable. -/// - OpenBSD: calls `getentropy(2)`. -/// - FreeBSD: uses the `kern.arandom` `sysctl(2)` mib. -/// - Fuchsia: calls `cprng_draw`. -/// - Redox: reads from `rand:` device. -/// - CloudABI: calls `random_get`. -/// - Other Unix-like systems: reads directly from `/dev/urandom`. +/// | 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. +/// Unfortunately it falls back to the insecure `Math.random()` if a browser +/// doesn't support [`Crypto.getRandomValues`][12]. +/// +/// The bare Wasm target `wasm32-unknown-unknown` tries to call the javascript +/// methods directly, using `stdweb` in combination with `cargo-web`. +/// `wasm-bindgen` is not yet supported. +/// +/// ## Early boot /// -/// ## Notes on Unix `/dev/urandom` +/// 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. /// -/// Many Unix systems provide `/dev/random` as well as `/dev/urandom`. On all -/// modern systems these two interfaces offer identical quality, with the -/// difference that on some systems `/dev/random` may block. This is a dated -/// design, and `/dev/urandom` is preferred by cryptography experts. -/// See [Myths about urandom](https://www.2uo.de/myths-about-urandom/). +/// 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. /// -/// On some systems reading from `/dev/urandom` “may return data prior to the -/// entropy pool being initialized”. I.e., early in the boot process, and -/// especially on virtual machines, `/dev/urandom` may return data that is less -/// random. As a countermeasure we try to do a single read from `/dev/random` in -/// non-blocking mode. If the OS RNG is not yet properly seeded, we will get an -/// error. Because we keep one file descriptor to `/dev/urandom` open when -/// succesful, this is only a small one-time cost. +/// 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 /// -/// `OsRng` is extremely unlikely to fail if `OsRng::new()` was succesfull. But -/// in case it does fail, only [`try_fill_bytes`] is able to report the cause. -/// Depending on the error the other [`RngCore`] methods will retry several -/// times, and panic in case the error remains. +/// `OsRng` is extremely unlikely to fail if `OsRng::new()`, and one read from +/// it, where succesfull. But in case it does fail, only [`try_fill_bytes`] is +/// able to report the cause. Depending on the error the other [`RngCore`] +/// methods will retry several times, and panic in case the error remains. /// /// [`EntropyRng`]: struct.EntropyRng.html /// [`RngCore`]: ../trait.RngCore.html /// [`try_fill_bytes`]: ../trait.RngCore.html#method.tymethod.try_fill_bytes +/// [`ErrorKind::NotReady`]: ../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 -#[allow(unused)] // not used by all targets #[derive(Clone)] pub struct OsRng(imp::OsRng); @@ -117,6 +153,11 @@ impl RngCore for OsRng { 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 { @@ -153,29 +194,146 @@ impl RngCore for OsRng { } fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { - self.0.try_fill_bytes(dest) + // 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(()) } } -#[cfg(all(unix, - not(target_os = "cloudabi"), - not(target_os = "freebsd"), - not(target_os = "fuchsia"), - not(target_os = "ios"), - not(target_os = "macos"), - not(target_os = "openbsd"), - not(target_os = "redox")))] +trait OsRngImpl where Self: Sized { + // Create a new `OsRng` platform interface. + fn new() -> Result; + + // 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 { Ok(0) } + + // Maximum chunk size supported. + fn max_chunk_size(&self) -> usize { ::core::usize::MAX } + + // Name of the OS interface (used for logging). + fn method_str(&self) -> &'static str; +} + + + + +// Helper functions to read from a random device such as `/dev/urandom`. +// +// All instances use a single internal file handle, to prevent possible +// exhaustion of file descriptors. +#[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 { + use {Error, ErrorKind}; + use std::fs::File; + use std::io; + use std::io::Read; + use std::sync::{Once, Mutex, ONCE_INIT}; + + // TODO: remove outer Option when `Mutex::new(None)` is a constant expression + static mut READ_RNG_FILE: Option>> = None; + static READ_RNG_ONCE: Once = ONCE_INIT; + + #[allow(unused)] + pub fn open(path: &'static str, open_fn: F) -> Result<(), Error> + where F: Fn(&'static str) -> Result + { + READ_RNG_ONCE.call_once(|| { + unsafe { READ_RNG_FILE = Some(Mutex::new(None)) } + }); + + // We try opening the file outside the `call_once` fn because we cannot + // clone the error, thus we must retry on failure. + + let mutex = unsafe { READ_RNG_FILE.as_ref().unwrap() }; + let mut guard = mutex.lock().unwrap(); + if (*guard).is_none() { + info!("OsRng: opening random device {}", path); + let file = open_fn(path).map_err(map_err)?; + *guard = Some(file); + }; + Ok(()) + } + + pub fn read(dest: &mut [u8]) -> Result<(), Error> { + // We expect this function only to be used after `random_device::open` + // was succesful. Therefore we can assume that our memory was set with a + // valid object. + let mutex = unsafe { READ_RNG_FILE.as_ref().unwrap() }; + let mut guard = mutex.lock().unwrap(); + let file = (*guard).as_mut().unwrap(); + + // Use `std::io::read_exact`, which retries on `ErrorKind::Interrupted`. + file.read_exact(dest).map_err(|err| { + Error::with_cause(ErrorKind::Unavailable, + "error reading random device", err) + }) + + } + + pub fn map_err(err: io::Error) -> Error { + match err.kind() { + io::ErrorKind::Interrupted => + Error::new(ErrorKind::Transient, "interrupted"), + io::ErrorKind::WouldBlock => + Error::with_cause(ErrorKind::NotReady, + "OS RNG not yet seeded", err), + _ => Error::with_cause(ErrorKind::Unavailable, + "error while opening random device", err) + } + } +} + + +#[cfg(any(target_os = "linux", target_os = "android"))] mod imp { extern crate libc; + use {Error, ErrorKind}; - use std::fs::{OpenOptions, File}; - use std::os::unix::fs::OpenOptionsExt; + use super::random_device; + use super::OsRngImpl; + use std::io; use std::io::Read; - use std::sync::{Once, Mutex, ONCE_INIT}; + use std::fs::{File, OpenOptions}; + use std::os::unix::fs::OpenOptionsExt; + use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT, Ordering}; + use std::sync::{Once, ONCE_INIT}; #[derive(Clone, Debug)] - pub struct OsRng(OsRngMethod); + pub struct OsRng { + method: OsRngMethod, + initialized: bool, + } #[derive(Clone, Debug)] enum OsRngMethod { @@ -183,80 +341,107 @@ mod imp { RandomDevice, } - impl OsRng { - pub fn new() -> Result { + impl OsRngImpl for OsRng { + fn new() -> Result { if is_getrandom_available() { - return Ok(OsRng(OsRngMethod::GetRandom)); + return Ok(OsRng { method: OsRngMethod::GetRandom, + initialized: false }); } + random_device::open("/dev/urandom", &|p| File::open(p))?; + Ok(OsRng { method: OsRngMethod::RandomDevice, initialized: false }) + } - open_dev_random()?; - Ok(OsRng(OsRngMethod::RandomDevice)) + fn fill_chunk(&mut self, dest: &mut [u8]) -> Result<(), Error> { + match self.method { + OsRngMethod::GetRandom => getrandom_try_fill(dest, false), + OsRngMethod::RandomDevice => random_device::read(dest), + } } - pub fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { - match self.0 { - OsRngMethod::GetRandom => getrandom_try_fill(dest), - OsRngMethod::RandomDevice => dev_random_try_fill(dest), + fn test_initialized(&mut self, dest: &mut [u8], blocking: bool) + -> Result + { + static OS_RNG_INITIALIZED: AtomicBool = ATOMIC_BOOL_INIT; + if !self.initialized { + self.initialized = OS_RNG_INITIALIZED.load(Ordering::Relaxed); + } + if self.initialized { return Ok(0); } + + let result = match self.method { + OsRngMethod::GetRandom => { + getrandom_try_fill(dest, blocking)?; + Ok(dest.len()) + } + OsRngMethod::RandomDevice => { + info!("OsRng: testing random device /dev/random"); + let mut file = OpenOptions::new() + .read(true) + .custom_flags(if blocking { 0 } else { libc::O_NONBLOCK }) + .open("/dev/random") + .map_err(random_device::map_err)?; + file.read(&mut dest[..1]).map_err(random_device::map_err)?; + Ok(1) + } + }; + OS_RNG_INITIALIZED.store(true, Ordering::Relaxed); + self.initialized = true; + result + } + + fn method_str(&self) -> &'static str { + match self.method { + OsRngMethod::GetRandom => "getrandom", + OsRngMethod::RandomDevice => "/dev/urandom", } } } - #[cfg(all(any(target_os = "linux", target_os = "android"), - any(target_arch = "x86_64", target_arch = "x86", + #[cfg(target_arch = "x86_64")] + const NR_GETRANDOM: libc::c_long = 318; + #[cfg(target_arch = "x86")] + const NR_GETRANDOM: libc::c_long = 355; + #[cfg(target_arch = "arm")] + const NR_GETRANDOM: libc::c_long = 384; + #[cfg(target_arch = "aarch64")] + const NR_GETRANDOM: libc::c_long = 278; + #[cfg(target_arch = "s390x")] + const NR_GETRANDOM: libc::c_long = 349; + #[cfg(target_arch = "powerpc")] + const NR_GETRANDOM: libc::c_long = 359; + #[cfg(target_arch = "mips")] // old ABI + const NR_GETRANDOM: libc::c_long = 4353; + #[cfg(target_arch = "mips64")] + const NR_GETRANDOM: libc::c_long = 5313; + #[cfg(not(any(target_arch = "x86_64", target_arch = "x86", target_arch = "arm", target_arch = "aarch64", target_arch = "s390x", target_arch = "powerpc", target_arch = "mips", target_arch = "mips64")))] - fn getrandom(buf: &mut [u8]) -> libc::c_long { + const NR_GETRANDOM: libc::c_long = 0; + + fn getrandom(buf: &mut [u8], blocking: bool) -> libc::c_long { extern "C" { fn syscall(number: libc::c_long, ...) -> libc::c_long; } - - #[cfg(target_arch = "x86_64")] - const NR_GETRANDOM: libc::c_long = 318; - #[cfg(target_arch = "x86")] - const NR_GETRANDOM: libc::c_long = 355; - #[cfg(target_arch = "arm")] - const NR_GETRANDOM: libc::c_long = 384; - #[cfg(target_arch = "aarch64")] - const NR_GETRANDOM: libc::c_long = 278; - #[cfg(target_arch = "s390x")] - const NR_GETRANDOM: libc::c_long = 349; - #[cfg(target_arch = "powerpc")] - const NR_GETRANDOM: libc::c_long = 359; - #[cfg(target_arch = "mips")] // old ABI - const NR_GETRANDOM: libc::c_long = 4353; - #[cfg(target_arch = "mips64")] - const NR_GETRANDOM: libc::c_long = 5313; - const GRND_NONBLOCK: libc::c_uint = 0x0001; + if NR_GETRANDOM == 0 { return -1 }; + unsafe { - syscall(NR_GETRANDOM, buf.as_mut_ptr(), buf.len(), GRND_NONBLOCK) + syscall(NR_GETRANDOM, buf.as_mut_ptr(), buf.len(), + if blocking { 0 } else { GRND_NONBLOCK }) } } - #[cfg(not(all(any(target_os = "linux", target_os = "android"), - any(target_arch = "x86_64", target_arch = "x86", - target_arch = "arm", target_arch = "aarch64", - target_arch = "s390x", target_arch = "powerpc", - target_arch = "mips", target_arch = "mips64"))))] - fn getrandom(_buf: &mut [u8]) -> libc::c_long { -1 } - - fn getrandom_try_fill(dest: &mut [u8]) -> Result<(), Error> { - trace!("OsRng: reading {} bytes via getrandom", dest.len()); + fn getrandom_try_fill(dest: &mut [u8], blocking: bool) -> Result<(), Error> { let mut read = 0; - let len = dest.len(); - while read < len { - let result = getrandom(&mut dest[read..]); + while read < dest.len() { + let result = getrandom(&mut dest[read..], blocking); if result == -1 { let err = io::Error::last_os_error(); let kind = err.kind(); if kind == io::ErrorKind::Interrupted { continue; } else if kind == io::ErrorKind::WouldBlock { - // Potentially this would waste bytes, but since we use - // /dev/urandom blocking only happens if not initialised. - // Also, wasting the bytes in dest doesn't matter very much. return Err(Error::with_cause( ErrorKind::NotReady, "getrandom not ready", @@ -276,22 +461,16 @@ mod imp { Ok(()) } - #[cfg(all(any(target_os = "linux", target_os = "android"), - any(target_arch = "x86_64", target_arch = "x86", - target_arch = "arm", target_arch = "aarch64", - target_arch = "s390x", target_arch = "powerpc", - target_arch = "mips", target_arch = "mips64")))] fn is_getrandom_available() -> bool { - use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT, Ordering}; - use std::sync::{Once, ONCE_INIT}; - static CHECKER: Once = ONCE_INIT; static AVAILABLE: AtomicBool = ATOMIC_BOOL_INIT; + if NR_GETRANDOM == 0 { return false }; + CHECKER.call_once(|| { debug!("OsRng: testing getrandom"); let mut buf: [u8; 0] = []; - let result = getrandom(&mut buf); + let result = getrandom(&mut buf, false); let available = if result == -1 { let err = io::Error::last_os_error().raw_os_error(); err != Some(libc::ENOSYS) @@ -304,105 +483,278 @@ mod imp { AVAILABLE.load(Ordering::Relaxed) } +} - #[cfg(not(all(any(target_os = "linux", target_os = "android"), - any(target_arch = "x86_64", target_arch = "x86", - target_arch = "arm", target_arch = "aarch64", - target_arch = "s390x", target_arch = "powerpc", - target_arch = "mips", target_arch = "mips64"))))] - fn is_getrandom_available() -> bool { false } - // TODO: remove outer Option when `Mutex::new(None)` is a constant expression - static mut READ_RNG_FILE: Option>> = None; - static READ_RNG_ONCE: Once = ONCE_INIT; +#[cfg(target_os = "netbsd")] +mod imp { + use Error; + use super::random_device; + use super::OsRngImpl; - // Note: all instances use a single internal file handle, to prevent - // possible exhaustion of file descriptors. - // - // We do a single read from `/dev/random` in non-blocking mode. If the OS - // RNG is not yet properly seeded, we will get an error, instead of silently - // getting less random bytes, as `/dev/urandom` can return. Because we keep - // `/dev/urandom` open when succesful, this is only a small one-time cost. - fn open_dev_random() -> Result<(), Error> { - fn map_err(err: io::Error) -> Error { - match err.kind() { - io::ErrorKind::Interrupted => - Error::new(ErrorKind::Transient, "interrupted"), - io::ErrorKind::WouldBlock => - Error::with_cause(ErrorKind::NotReady, - "OS RNG not yet seeded", err), - _ => Error::with_cause(ErrorKind::Unavailable, - "error while opening random device", err) + use std::fs::File; + use std::io::Read; + use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT, Ordering}; + + #[derive(Clone, Debug)] + pub struct OsRng { initialized: bool } + + impl OsRngImpl for OsRng { + fn new() -> Result { + random_device::open("/dev/urandom", &|p| File::open(p))?; + Ok(OsRng { initialized: false }) + } + + fn fill_chunk(&mut self, dest: &mut [u8]) -> Result<(), Error> { + random_device::read(dest) + } + + // Read a single byte from `/dev/random` to determine if the OS RNG is + // already seeded. NetBSD always blocks if not yet ready. + fn test_initialized(&mut self, dest: &mut [u8], _blocking: bool) + -> Result + { + static OS_RNG_INITIALIZED: AtomicBool = ATOMIC_BOOL_INIT; + if !self.initialized { + self.initialized = OS_RNG_INITIALIZED.load(Ordering::Relaxed); } + if self.initialized { return Ok(0); } + + info!("OsRng: testing random device /dev/random"); + let mut file = + File::open("/dev/random").map_err(random_device::map_err)?; + file.read(&mut dest[..1]).map_err(random_device::map_err)?; + + OS_RNG_INITIALIZED.store(true, Ordering::Relaxed); + self.initialized = true; + Ok(1) } - READ_RNG_ONCE.call_once(|| { - unsafe { READ_RNG_FILE = Some(Mutex::new(None)) } - }); + fn method_str(&self) -> &'static str { "/dev/urandom" } + } +} - // We try opening the file outside the `call_once` fn because we cannot - // clone the error, thus we must retry on failure. - let mutex = unsafe { READ_RNG_FILE.as_ref().unwrap() }; - let mut guard = mutex.lock().unwrap(); - if (*guard).is_none() { - { - info!("OsRng: opening random device /dev/random"); - let mut file = OpenOptions::new() - .read(true) - .custom_flags(libc::O_NONBLOCK) - .open("/dev/random") - .map_err(map_err)?; - let mut buf = [0u8; 1]; - file.read_exact(&mut buf).map_err(map_err)?; +#[cfg(any(target_os = "dragonfly", + target_os = "haiku", + target_os = "emscripten"))] +mod imp { + use Error; + use super::random_device; + use super::OsRngImpl; + use std::fs::File; + + #[derive(Clone, Debug)] + pub struct OsRng(); + + impl OsRngImpl for OsRng { + fn new() -> Result { + random_device::open("/dev/random", &|p| File::open(p))?; + Ok(OsRng()) + } + + fn fill_chunk(&mut self, dest: &mut [u8]) -> Result<(), Error> { + random_device::read(dest) + } + + #[cfg(target_os = "emscripten")] + fn max_chunk_size(&self) -> usize { + // `Crypto.getRandomValues` documents `dest` should be at most 65536 + // bytes. `crypto.randomBytes` documents: "To minimize threadpool + // task length variation, partition large randomBytes requests when + // doing so as part of fulfilling a client request. + 65536 + } + + fn method_str(&self) -> &'static str { "/dev/random" } + } +} + + +// Read from `/dev/random`, with chunks of limited size (1040 bytes). +// `/dev/random` uses the Hash_DRBG with SHA512 algorithm from NIST SP 800-90A. +// `/dev/urandom` uses the FIPS 186-2 algorithm, which is considered less +// secure. We choose to read from `/dev/random`. +// +// Since Solaris 11.3 the `getrandom` syscall is available. To make sure we can +// compile on both Solaris and on OpenSolaris derivatives, that do not have the +// function, we do a direct syscall instead of calling a library function. +// +// We have no way to differentiate between Solaris, illumos, SmartOS, etc. +#[cfg(target_os = "solaris")] +mod imp { + extern crate libc; + + use {Error, ErrorKind}; + use super::random_device; + use super::OsRngImpl; + + use std::io; + use std::io::Read; + use std::fs::{File, OpenOptions}; + use std::os::unix::fs::OpenOptionsExt; + use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT, Ordering}; + + #[derive(Clone, Debug)] + pub struct OsRng { + method: OsRngMethod, + initialized: bool, + } + + #[derive(Clone, Debug)] + enum OsRngMethod { + GetRandom, + RandomDevice, + } + + impl OsRngImpl for OsRng { + fn new() -> Result { + if is_getrandom_available() { + return Ok(OsRng { method: OsRngMethod::GetRandom, + initialized: false }); } + let open = |p| OpenOptions::new() + .read(true) + .custom_flags(libc::O_NONBLOCK) + .open(p); + random_device::open("/dev/random", &open)?; + Ok(OsRng { method: OsRngMethod::RandomDevice, initialized: false }) + } - info!("OsRng: opening random device /dev/urandom"); - let file = File::open("/dev/urandom").map_err(map_err)?; - *guard = Some(file); - }; - Ok(()) + fn fill_chunk(&mut self, dest: &mut [u8]) -> Result<(), Error> { + match self.method { + OsRngMethod::GetRandom => getrandom_try_fill(dest, false), + OsRngMethod::RandomDevice => random_device::read(dest), + } + } + + fn test_initialized(&mut self, dest: &mut [u8], blocking: bool) + -> Result + { + static OS_RNG_INITIALIZED: AtomicBool = ATOMIC_BOOL_INIT; + if !self.initialized { + self.initialized = OS_RNG_INITIALIZED.load(Ordering::Relaxed); + } + if self.initialized { return Ok(0); } + + let chunk_len = ::core::cmp::min(1024, dest.len()); + let dest = &mut dest[..chunk_len]; + + match self.method { + OsRngMethod::GetRandom => getrandom_try_fill(dest, blocking)?, + OsRngMethod::RandomDevice => { + if blocking { + info!("OsRng: testing random device /dev/random"); + // We already have a non-blocking handle, but now need a + // blocking one. Not much choice except opening it twice + let mut file = File::open("/dev/random") + .map_err(random_device::map_err)?; + file.read(dest).map_err(random_device::map_err)?; + } else { + self.fill_chunk(dest)?; + } + } + }; + OS_RNG_INITIALIZED.store(true, Ordering::Relaxed); + self.initialized = true; + Ok(chunk_len) + } + + fn max_chunk_size(&self) -> usize { + // The documentation says 1024 is the maximum for getrandom, but + // 1040 for /dev/random. + 1024 + } + + fn method_str(&self) -> &'static str { + match self.method { + OsRngMethod::GetRandom => "getrandom", + OsRngMethod::RandomDevice => "/dev/random", + } + } } - fn dev_random_try_fill(dest: &mut [u8]) -> Result<(), Error> { - if dest.len() == 0 { return Ok(()); } - trace!("OsRng: reading {} bytes from random device", dest.len()); + fn getrandom(buf: &mut [u8], blocking: bool) -> libc::c_long { + extern "C" { + fn syscall(number: libc::c_long, ...) -> libc::c_long; + } - // We expect this function only to be used after `open_dev_random` was - // succesful. Therefore we can assume that our memory was set with a - // valid object. - let mutex = unsafe { READ_RNG_FILE.as_ref().unwrap() }; - let mut guard = mutex.lock().unwrap(); - let file = (*guard).as_mut().unwrap(); - // Use `std::io::read_exact`, which retries on `ErrorKind::Interrupted`. - file.read_exact(dest).map_err(|err| { - match err.kind() { - ::std::io::ErrorKind::WouldBlock => Error::with_cause( + const SYS_GETRANDOM: libc::c_long = 143; + const GRND_NONBLOCK: libc::c_uint = 0x0001; + const GRND_RANDOM: libc::c_uint = 0x0002; + + unsafe { + syscall(SYS_GETRANDOM, buf.as_mut_ptr(), buf.len(), + if blocking { 0 } else { GRND_NONBLOCK } | GRND_RANDOM) + } + } + + fn getrandom_try_fill(dest: &mut [u8], blocking: bool) -> Result<(), Error> { + let result = getrandom(dest, blocking); + if result == -1 || result == 0 { + let err = io::Error::last_os_error(); + let kind = err.kind(); + if kind == io::ErrorKind::WouldBlock { + return Err(Error::with_cause( ErrorKind::NotReady, - "reading from random device would block", err), - _ => Error::with_cause(ErrorKind::Unavailable, - "error reading random device", err) + "getrandom not ready", + err, + )); + } else { + return Err(Error::with_cause( + ErrorKind::Unavailable, + "unexpected getrandom error", + err, + )); } - }) + } else if result != dest.len() as i64 { + return Err(Error::new(ErrorKind::Unavailable, + "unexpected getrandom error")); + } + Ok(()) + } + + fn is_getrandom_available() -> bool { + use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT, Ordering}; + use std::sync::{Once, ONCE_INIT}; + + static CHECKER: Once = ONCE_INIT; + static AVAILABLE: AtomicBool = ATOMIC_BOOL_INIT; + + CHECKER.call_once(|| { + debug!("OsRng: testing getrandom"); + let mut buf: [u8; 0] = []; + let result = getrandom(&mut buf, false); + let available = if result == -1 { + let err = io::Error::last_os_error().raw_os_error(); + err != Some(libc::ENOSYS) + } else { + true + }; + AVAILABLE.store(available, Ordering::Relaxed); + info!("OsRng: using {}", if available { "getrandom" } else { "/dev/random" }); + }); + + AVAILABLE.load(Ordering::Relaxed) } } + #[cfg(target_os = "cloudabi")] mod imp { extern crate cloudabi; + use std::io; use {Error, ErrorKind}; + use super::OsRngImpl; #[derive(Clone, Debug)] pub struct OsRng; - impl OsRng { - pub fn new() -> Result { - Ok(OsRng) - } + impl OsRngImpl for OsRng { + fn new() -> Result { Ok(OsRng) } - pub fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { - trace!("OsRng: reading {} bytes via cloadabi::random_get", dest.len()); + fn fill_chunk(&mut self, dest: &mut [u8]) -> Result<(), Error> { let errno = unsafe { cloudabi::random_get(dest) }; if errno == cloudabi::errno::SUCCESS { Ok(()) @@ -412,19 +764,23 @@ mod imp { Err(Error::with_cause( ErrorKind::Unavailable, "random_get() system call failed", - io::Error::from_raw_os_error(errno), + io::Error::from_raw_os_error(errno as i32), )) } } + + fn method_str(&self) -> &'static str { "cloudabi::random_get" } } } + #[cfg(any(target_os = "macos", target_os = "ios"))] mod imp { extern crate libc; use {Error, ErrorKind}; - + use super::OsRngImpl; + use std::io; use self::libc::{c_int, size_t}; @@ -442,14 +798,14 @@ mod imp { count: size_t, bytes: *mut u8) -> c_int; } - impl OsRng { - pub fn new() -> Result { - Ok(OsRng) - } - pub fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { - trace!("OsRng: reading {} bytes via SecRandomCopyBytes", dest.len()); + impl OsRngImpl for OsRng { + fn new() -> Result { Ok(OsRng) } + + fn fill_chunk(&mut self, dest: &mut [u8]) -> Result<(), Error> { let ret = unsafe { - SecRandomCopyBytes(kSecRandomDefault, dest.len() as size_t, dest.as_mut_ptr()) + SecRandomCopyBytes(kSecRandomDefault, + dest.len() as size_t, + dest.as_mut_ptr()) }; if ret == -1 { Err(Error::with_cause( @@ -460,184 +816,157 @@ mod imp { Ok(()) } } + + fn method_str(&self) -> &'static str { "SecRandomCopyBytes" } } } + #[cfg(target_os = "freebsd")] mod imp { extern crate libc; use {Error, ErrorKind}; - + use super::OsRngImpl; + use std::ptr; use std::io; #[derive(Clone, Debug)] pub struct OsRng; - impl OsRng { - pub fn new() -> Result { - Ok(OsRng) - } - pub fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { + impl OsRngImpl for OsRng { + fn new() -> Result { Ok(OsRng) } + + fn fill_chunk(&mut self, dest: &mut [u8]) -> Result<(), Error> { let mib = [libc::CTL_KERN, libc::KERN_ARND]; - trace!("OsRng: reading {} bytes via kern.arandom", dest.len()); - // kern.arandom permits a maximum buffer size of 256 bytes - for s in dest.chunks_mut(256) { - let mut s_len = s.len(); - let ret = unsafe { - libc::sysctl(mib.as_ptr(), mib.len() as libc::c_uint, - s.as_mut_ptr() as *mut _, &mut s_len, - ptr::null(), 0) - }; - if ret == -1 || s_len != s.len() { - return Err(Error::with_cause( - ErrorKind::Unavailable, - "kern.arandom sysctl failed", - io::Error::last_os_error())); - } + let mut len = dest.len(); + let ret = unsafe { + libc::sysctl(mib.as_ptr(), mib.len() as libc::c_uint, + dest.as_mut_ptr() as *mut _, &mut len, + ptr::null(), 0) + }; + if ret == -1 || len != dest.len() { + return Err(Error::with_cause( + ErrorKind::Unavailable, + "kern.arandom sysctl failed", + io::Error::last_os_error())); } Ok(()) } + + fn max_chunk_size(&self) -> usize { 256 } + + fn method_str(&self) -> &'static str { "kern.arandom" } } } -#[cfg(target_os = "openbsd")] + +#[cfg(any(target_os = "openbsd", target_os = "bitrig"))] mod imp { extern crate libc; use {Error, ErrorKind}; - + use super::OsRngImpl; + use std::io; #[derive(Clone, Debug)] pub struct OsRng; - impl OsRng { - pub fn new() -> Result { - Ok(OsRng) - } - pub fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { - // getentropy(2) permits a maximum buffer size of 256 bytes - for s in dest.chunks_mut(256) { - trace!("OsRng: reading {} bytes via getentropy", s.len()); - let ret = unsafe { - libc::getentropy(s.as_mut_ptr() as *mut libc::c_void, s.len()) - }; - if ret == -1 { - return Err(Error::with_cause( - ErrorKind::Unavailable, - "getentropy failed", - io::Error::last_os_error())); - } + impl OsRngImpl for OsRng { + fn new() -> Result { Ok(OsRng) } + + fn fill_chunk(&mut self, dest: &mut [u8]) -> Result<(), Error> { + let ret = unsafe { + libc::getentropy(s.as_mut_ptr() as *mut libc::c_void, s.len()) + }; + if ret == -1 { + return Err(Error::with_cause( + ErrorKind::Unavailable, + "getentropy failed", + io::Error::last_os_error())); } Ok(()) } + + fn max_chunk_size(&self) -> usize { 256 } + + fn method_str(&self) -> &'static str { "getentropy" } } } + #[cfg(target_os = "redox")] mod imp { - use {Error, ErrorKind}; + use Error; + use super::random_device; + use super::OsRngImpl; use std::fs::File; - use std::io::Read; - use std::io::ErrorKind::*; - use std::sync::{Once, Mutex, ONCE_INIT}; #[derive(Clone, Debug)] pub struct OsRng(); - // TODO: remove outer Option when `Mutex::new(None)` is a constant expression - static mut READ_RNG_FILE: Option>> = None; - static READ_RNG_ONCE: Once = ONCE_INIT; - - impl OsRng { - pub fn new() -> Result { - READ_RNG_ONCE.call_once(|| { - unsafe { READ_RNG_FILE = Some(Mutex::new(None)) } - }); - - // We try opening the file outside the `call_once` fn because we cannot - // clone the error, thus we must retry on failure. - - let mutex = unsafe { READ_RNG_FILE.as_ref().unwrap() }; - let mut guard = mutex.lock().unwrap(); - if (*guard).is_none() { - info!("OsRng: opening random device 'rand:'"); - let file = File::open("rand:").map_err(|err| { - match err.kind() { - Interrupted => Error::new(ErrorKind::Transient, "interrupted"), - WouldBlock => Error::with_cause(ErrorKind::NotReady, - "opening random device would block", err), - _ => Error::with_cause(ErrorKind::Unavailable, - "error while opening random device", err) - } - })?; - *guard = Some(file); - }; + impl OsRngImpl for OsRng { + fn new() -> Result { + random_device::open("rand:", &|p| File::open(p))?; Ok(OsRng()) } - pub fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { - if dest.len() == 0 { return Ok(()); } - trace!("OsRng: reading {} bytes from random device", dest.len()); - - // Since we have an instance of Self, we can assume that our memory was - // set with a valid object. - let mutex = unsafe { READ_RNG_FILE.as_ref().unwrap() }; - let mut guard = mutex.lock().unwrap(); - let file = (*guard).as_mut().unwrap(); - // Use `std::io::read_exact`, which retries on `ErrorKind::Interrupted`. - file.read_exact(dest).map_err(|err| { - Error::with_cause(ErrorKind::Unavailable, - "error reading random device", err) - }) + fn fill_chunk(&mut self, dest: &mut [u8]) -> Result<(), Error> { + random_device::read(dest) } + + fn method_str(&self) -> &'static str { "'rand:'" } } } + #[cfg(target_os = "fuchsia")] mod imp { extern crate fuchsia_zircon; use {Error, ErrorKind}; - - use std::io; + use super::OsRngImpl; #[derive(Clone, Debug)] pub struct OsRng; - impl OsRng { - pub fn new() -> Result { - Ok(OsRng) - } - pub fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { - for s in dest.chunks_mut(fuchsia_zircon::sys::ZX_CPRNG_DRAW_MAX_LEN) { - trace!("OsRng: reading {} bytes via cprng_draw", s.len()); - let mut filled = 0; - while filled < s.len() { - match fuchsia_zircon::cprng_draw(&mut s[filled..]) { - Ok(actual) => filled += actual, - Err(e) => { - return Err(Error::with_cause( - ErrorKind::Unavailable, - "cprng_draw failed", - e)); - } - }; - } + impl OsRngImpl for OsRng { + fn new() -> Result { Ok(OsRng) } + + fn fill_chunk(&mut self, dest: &mut [u8]) -> Result<(), Error> { + let mut read = 0; + while read < dest.len() { + match fuchsia_zircon::cprng_draw(&mut dest[read..]) { + Ok(actual) => read += actual, + Err(e) => { + return Err(Error::with_cause( + ErrorKind::Unavailable, + "cprng_draw failed", + e.into_io_error())); + } + }; } Ok(()) } + + fn max_chunk_size(&self) -> usize { + fuchsia_zircon::sys::ZX_CPRNG_DRAW_MAX_LEN + } + + fn method_str(&self) -> &'static str { "cprng_draw" } } } + #[cfg(windows)] mod imp { extern crate winapi; use {Error, ErrorKind}; - + use super::OsRngImpl; + use std::io; use self::winapi::shared::minwindef::ULONG; @@ -647,52 +976,29 @@ mod imp { #[derive(Clone, Debug)] pub struct OsRng; - impl OsRng { - pub fn new() -> Result { - Ok(OsRng) - } - pub fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { - // RtlGenRandom takes an ULONG (u32) for the length so we need to - // split up the buffer. - for slice in dest.chunks_mut(::max_value() as usize) { - trace!("OsRng: reading {} bytes via RtlGenRandom", slice.len()); - let ret = unsafe { - RtlGenRandom(slice.as_mut_ptr() as PVOID, slice.len() as ULONG) - }; - if ret == 0 { - return Err(Error::with_cause( - ErrorKind::Unavailable, - "couldn't generate random bytes", - io::Error::last_os_error())); - } + impl OsRngImpl for OsRng { + fn new() -> Result { Ok(OsRng) } + + fn fill_chunk(&mut self, dest: &mut [u8]) -> Result<(), Error> { + let ret = unsafe { + RtlGenRandom(dest.as_mut_ptr() as PVOID, dest.len() as ULONG) + }; + if ret == 0 { + return Err(Error::with_cause( + ErrorKind::Unavailable, + "couldn't generate random bytes", + io::Error::last_os_error())); } Ok(()) } - } -} -#[cfg(all(target_arch = "wasm32", - not(target_os = "emscripten"), - not(feature = "stdweb")))] -mod imp { - use {Error, ErrorKind}; - - #[derive(Clone, Debug)] - pub struct OsRng; + fn max_chunk_size(&self) -> usize { ::max_value() as usize } - impl OsRng { - pub fn new() -> Result { - Err(Error::new(ErrorKind::Unavailable, - "not supported on WASM without stdweb")) - } - - pub fn try_fill_bytes(&mut self, _v: &mut [u8]) -> Result<(), Error> { - Err(Error::new(ErrorKind::Unavailable, - "not supported on WASM without stdweb")) - } + fn method_str(&self) -> &'static str { "RtlGenRandom" } } } + #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten"), feature = "stdweb"))] @@ -701,18 +1007,19 @@ mod imp { use stdweb::unstable::TryInto; use stdweb::web::error::Error as WebError; use {Error, ErrorKind}; + use super::OsRngImpl; #[derive(Clone, Debug)] - enum OsRngInner { + enum OsRngMethod { Browser, Node } #[derive(Clone, Debug)] - pub struct OsRng(OsRngInner); + pub struct OsRng(OsRngMethod); - impl OsRng { - pub fn new() -> Result { + impl OsRngImpl for OsRng { + fn new() -> Result { let result = js! { try { if ( @@ -736,8 +1043,8 @@ mod imp { if js!{ return @{ result.as_ref() }.success } == true { let ty = js!{ return @{ result }.ty }; - if ty == 1 { Ok(OsRng(OsRngInner::Browser)) } - else if ty == 2 { Ok(OsRng(OsRngInner::Node)) } + if ty == 1 { Ok(OsRng(OsRngMethod::Browser)) } + else if ty == 2 { Ok(OsRng(OsRngMethod::Node)) } else { unreachable!() } } else { let err: WebError = js!{ return @{ result }.error }.try_into().unwrap(); @@ -745,14 +1052,15 @@ mod imp { } } - pub fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { + + fn fill_chunk(&mut self, dest: &mut [u8]) -> Result<(), Error> { assert_eq!(mem::size_of::(), 4); let len = dest.len() as u32; let ptr = dest.as_mut_ptr() as i32; let result = match self.0 { - OsRngInner::Browser => js! { + OsRngMethod::Browser => js! { try { let array = new Uint8Array(@{ len }); window.crypto.getRandomValues(array); @@ -763,7 +1071,7 @@ mod imp { return { success: false, error: err }; } }, - OsRngInner::Node => js! { + OsRngMethod::Node => js! { try { let bytes = require("crypto").randomBytes(@{ len }); HEAPU8.set(new Uint8Array(bytes), @{ ptr }); @@ -782,9 +1090,19 @@ mod imp { Err(Error::with_cause(ErrorKind::Unexpected, "WASM Error", err)) } } + + fn max_chunk_size(&self) -> usize { 65536 } + + fn method_str(&self) -> &'static str { + match self.0 { + OsRngMethod::Browser => "Crypto.getRandomValues", + OsRngMethod::Node => "crypto.randomBytes", + } + } } } + #[cfg(test)] mod test { use RngCore; @@ -812,6 +1130,22 @@ mod test { assert!(n_diff_bits >= v1.len() as u32); } + #[test] + fn test_os_rng_empty() { + let mut r = OsRng::new().unwrap(); + + let mut empty = [0u8; 0]; + r.fill_bytes(&mut empty); + } + + #[test] + fn test_os_rng_huge() { + let mut r = OsRng::new().unwrap(); + + let mut huge = [0u8; 100_000]; + r.fill_bytes(&mut huge); + } + #[cfg(not(any(target_arch = "wasm32", target_arch = "asmjs")))] #[test] fn test_os_rng_tasks() {