diff --git a/Cargo.toml b/Cargo.toml index 56e8f48fd6..05d397ab78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -161,6 +161,7 @@ all-features = true name = "ring" [dependencies] +getrandom = { version = "0.2.8" } untrusted = { version = "0.9" } [target.'cfg(any(target_arch = "x86",target_arch = "x86_64", all(any(target_arch = "aarch64", target_arch = "arm"), any(target_os = "android", target_os = "fuchsia", target_os = "linux", target_os = "windows"))))'.dependencies] @@ -168,15 +169,8 @@ spin = { version = "0.9.2", default-features = false, features = ["once"] } [target.'cfg(any(target_os = "android", target_os = "linux"))'.dependencies] libc = { version = "0.2.100", default-features = false } -once_cell = { version = "1.8.0", default-features = false, features=["std"], optional = true } -[target.'cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "illumos", target_os = "netbsd", target_os = "openbsd", target_os = "redox", target_os = "solaris"))'.dependencies] -once_cell = { version = "1.8.0", default-features = false, features=["std"] } - -[target.'cfg(all(target_arch = "wasm32", target_vendor = "unknown", target_os = "unknown", target_env = ""))'.dependencies] -web-sys = { version = "0.3.51", default-features = false, features = ["Crypto", "Window"], optional = true } - -[target.'cfg(target_os = "windows")'.dependencies] +[target.'cfg(all(target_arch = "aarch64", target_os = "windows"))'.dependencies] winapi = { version = "0.3.9", default-features = false, features = ["ntsecapi", "wtypesbase", "processthreadsapi"] } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] @@ -195,11 +189,11 @@ criterion = { version = "0.4", default-features = false } # These features are documented in the top-level module's documentation. default = ["alloc", "dev_urandom_fallback"] alloc = [] -dev_urandom_fallback = ["once_cell"] +dev_urandom_fallback = [] slow_tests = [] std = ["alloc"] test_logging = [] -wasm32_unknown_unknown_js = ["web-sys"] +wasm32_unknown_unknown_js = ["getrandom/js"] # XXX: debug = false because of https://github.com/rust-lang/rust/issues/34122 diff --git a/mk/install-build-tools.sh b/mk/install-build-tools.sh index 8c0c37ad9e..1475bc4529 100755 --- a/mk/install-build-tools.sh +++ b/mk/install-build-tools.sh @@ -73,9 +73,7 @@ case $target in use_clang=1 ;; --target=wasm32-unknown-unknown) - # The version of wasm-bindgen-cli must match the wasm-bindgen version. - wasm_bindgen_version=$(cargo metadata --format-version 1 | jq -r '.packages | map(select( .name == "wasm-bindgen")) | map(.version) | .[0]') - cargo install wasm-bindgen-cli --vers "$wasm_bindgen_version" --bin wasm-bindgen-test-runner + cargo install wasm-bindgen-cli --bin wasm-bindgen-test-runner use_clang=1 ;; --target=*) diff --git a/src/lib.rs b/src/lib.rs index e4240c74e2..0ccc8a2498 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,14 +22,6 @@ //! Description //! alloc (default) //! Enable features that require use of the heap, RSA in particular. -//! dev_urandom_fallback (default) -//! This is only applicable to Linux. On Linux, by default, -//! ring::rand::SystemRandom will fall back to reading -//! from /dev/urandom if the getrandom() -//! syscall isn't supported at runtime. When the -//! dev_urandom_fallback feature is disabled, such -//! fallbacks will not occur. See the documentation for -//! rand::SystemRandom for more details. //! std //! Enable features that use libstd, in particular //! std::error::Error integration. Implies `alloc`. @@ -37,7 +29,8 @@ //! When this feature is enabled, for the wasm32-unknown-unknown target, //! Web APIs will be used to implement features like `ring::rand` that //! require an operating environment of some kind. This has no effect -//! for any other target. +//! for any other target. This enables the `getrandom` crate's `js` +//! feature. //! // When running mk/package.sh, don't actually build any code. diff --git a/src/rand.rs b/src/rand.rs index bd8b98ae3c..495ec60b30 100644 --- a/src/rand.rs +++ b/src/rand.rs @@ -95,6 +95,11 @@ impl RandomlyConstructable for T where T: self::sealed::RandomlyConstructable /// A secure random number generator where the random values come directly /// from the operating system. /// +/// "Directly from the operating system" here presently means "whatever the +/// `getrandom` crate does" but that may change in the future. That roughly +/// means calling libc's `getrandom` function or whatever is analogous to that; +/// see the `getrandom` crate's documentation for more info. +/// /// A single `SystemRandom` may be shared across multiple threads safely. /// /// `new()` is guaranteed to always succeed and to have low latency; it won't @@ -103,34 +108,6 @@ impl RandomlyConstructable for T where T: self::sealed::RandomlyConstructable /// initialization is deferred to it. Therefore, it may be a good idea to call /// `fill()` once at a non-latency-sensitive time to minimize latency for /// future calls. -/// -/// On Linux (including Android), `fill()` will use the [`getrandom`] syscall. -/// If the kernel is too old to support `getrandom` then by default `fill()` -/// falls back to reading from `/dev/urandom`. This decision is made the first -/// time `fill` *succeeds*. The fallback to `/dev/urandom` can be disabled by -/// disabling the `dev_urandom_fallback` default feature; this should be done -/// whenever the target system is known to support `getrandom`. When -/// `/dev/urandom` is used, a file handle for `/dev/urandom` won't be opened -/// until `fill` is called; `SystemRandom::new()` will not open `/dev/urandom` -/// or do other potentially-high-latency things. The file handle will never be -/// closed, until the operating system closes it at process shutdown. All -/// instances of `SystemRandom` will share a single file handle. To properly -/// implement seccomp filtering when the `dev_urandom_fallback` default feature -/// is disabled, allow `getrandom` through. When the fallback is enabled, allow -/// file opening, `getrandom`, and `read` up until the first call to `fill()` -/// succeeds; after that, allow `getrandom` and `read`. -/// -/// On macOS and iOS, `fill()` is implemented using `SecRandomCopyBytes`. -/// -/// On wasm32-unknown-unknown when the "wasm32_unknown_unknown_js" feature is -/// enabled, `fill()` is implemented using `window.crypto.getRandomValues()`. -/// It must be used in a context where the global object is a `Window`; i.e. it -/// must not be used in a Worker or a non-browser context. -/// -/// On Windows, `fill` is implemented using the platform's API for secure -/// random number generation. -/// -/// [`getrandom`]: http://man7.org/linux/man-pages/man2/getrandom.2.html #[derive(Clone, Debug)] pub struct SystemRandom(()); @@ -142,270 +119,33 @@ impl SystemRandom { } } -impl sealed::SecureRandom for SystemRandom { - #[inline(always)] - fn fill_impl(&self, dest: &mut [u8]) -> Result<(), error::Unspecified> { - fill_impl(dest) - } -} - impl crate::sealed::Sealed for SystemRandom {} +// Use the `getrandom` crate whenever it is using the environment's (operating +// system's) CSPRNG. Avoid using it on targets where it uses the `rdrand` +// implementation. #[cfg(any( - all( - any(target_os = "android", target_os = "linux"), - not(feature = "dev_urandom_fallback") - ), - target_arch = "wasm32", - windows -))] -use self::sysrand::fill as fill_impl; - -#[cfg(all( - any(target_os = "android", target_os = "linux"), - feature = "dev_urandom_fallback" -))] -use self::sysrand_or_urandom::fill as fill_impl; - -#[cfg(any( + target_os = "android", target_os = "dragonfly", target_os = "freebsd", target_os = "illumos", + target_os = "ios", + target_os = "linux", + target_os = "macos", target_os = "netbsd", target_os = "openbsd", target_os = "redox", target_os = "solaris", -))] -use self::urandom::fill as fill_impl; - -#[cfg(any(target_os = "macos", target_os = "ios"))] -use self::darwin::fill as fill_impl; - -#[cfg(any(target_os = "fuchsia"))] -use self::fuchsia::fill as fill_impl; - -#[cfg(any(target_os = "android", target_os = "linux"))] -mod sysrand_chunk { - use crate::{c, error}; - - #[inline] - pub fn chunk(dest: &mut [u8]) -> Result { - let chunk_len: c::size_t = dest.len(); - let r = unsafe { libc::syscall(libc::SYS_getrandom, dest.as_mut_ptr(), chunk_len, 0) }; - if r < 0 { - let errno; - - #[cfg(target_os = "linux")] - { - errno = unsafe { *libc::__errno_location() }; - } - - #[cfg(target_os = "android")] - { - errno = unsafe { *libc::__errno() }; - } - - if errno == libc::EINTR { - // If an interrupt occurs while getrandom() is blocking to wait - // for the entropy pool, then EINTR is returned. Returning 0 - // will cause the caller to try again. - return Ok(0); - } - return Err(error::Unspecified); - } - Ok(r as usize) - } -} - -#[cfg(all( - feature = "wasm32_unknown_unknown_js", - target_arch = "wasm32", - target_vendor = "unknown", - target_os = "unknown", - target_env = "", -))] -mod sysrand_chunk { - use crate::error; - - pub fn chunk(mut dest: &mut [u8]) -> Result { - // This limit is specified in - // https://www.w3.org/TR/WebCryptoAPI/#Crypto-method-getRandomValues. - const MAX_LEN: usize = 65_536; - if dest.len() > MAX_LEN { - dest = &mut dest[..MAX_LEN]; - }; - - let _ = web_sys::window() - .ok_or(error::Unspecified)? - .crypto() - .map_err(|_| error::Unspecified)? - .get_random_values_with_u8_array(dest) - .map_err(|_| error::Unspecified)?; - - Ok(dest.len()) - } -} - -#[cfg(windows)] -mod sysrand_chunk { - use crate::{error, polyfill}; - - #[inline] - pub fn chunk(dest: &mut [u8]) -> Result { - use winapi::shared::wtypesbase::ULONG; - - assert!(core::mem::size_of::() >= core::mem::size_of::()); - let len = core::cmp::min(dest.len(), polyfill::usize_from_u32(ULONG::max_value())); - let result = unsafe { - winapi::um::ntsecapi::RtlGenRandom( - dest.as_mut_ptr() as *mut winapi::ctypes::c_void, - len as ULONG, - ) - }; - if result == 0 { - return Err(error::Unspecified); - } - - Ok(len) - } -} - -#[cfg(any( - target_os = "android", - target_os = "linux", - target_arch = "wasm32", - windows -))] -mod sysrand { - use super::sysrand_chunk::chunk; - use crate::error; - - pub fn fill(dest: &mut [u8]) -> Result<(), error::Unspecified> { - let mut read_len = 0; - while read_len < dest.len() { - let chunk_len = chunk(&mut dest[read_len..])?; - read_len += chunk_len; - } - Ok(()) - } -} - -// Keep the `cfg` conditions in sync with the conditions in lib.rs. -#[cfg(all( - any(target_os = "android", target_os = "linux"), - feature = "dev_urandom_fallback" -))] -mod sysrand_or_urandom { - use crate::error; - - enum Mechanism { - Sysrand, - DevURandom, - } - - #[inline] - pub fn fill(dest: &mut [u8]) -> Result<(), error::Unspecified> { - use once_cell::sync::Lazy; - static MECHANISM: Lazy = Lazy::new(|| { - let mut dummy = [0u8; 1]; - if super::sysrand_chunk::chunk(&mut dummy[..]).is_err() { - Mechanism::DevURandom - } else { - Mechanism::Sysrand - } - }); - - match *MECHANISM { - Mechanism::Sysrand => super::sysrand::fill(dest), - Mechanism::DevURandom => super::urandom::fill(dest), - } - } -} - -#[cfg(any( + target_os = "windows", all( - any(target_os = "android", target_os = "linux"), - feature = "dev_urandom_fallback" + feature = "wasm32_unknown_unknown_js", + target_arch = "wasm32", + target_os = "unknown", ), - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd", - target_os = "redox", - target_os = "solaris", - target_os = "illumos" ))] -mod urandom { - use crate::error; - - #[cfg_attr(any(target_os = "android", target_os = "linux"), cold, inline(never))] - pub fn fill(dest: &mut [u8]) -> Result<(), error::Unspecified> { - extern crate std; - - use once_cell::sync::Lazy; - - static FILE: Lazy> = - Lazy::new(|| std::fs::File::open("/dev/urandom")); - - match *FILE { - Ok(ref file) => { - use std::io::Read; - #[allow(clippy::borrow_deref_ref)] - (&*file).read_exact(dest).map_err(|_| error::Unspecified) - } - Err(_) => Err(error::Unspecified), - } - } -} - -#[cfg(any(target_os = "macos", target_os = "ios"))] -mod darwin { - use crate::{c, error}; - - pub fn fill(dest: &mut [u8]) -> Result<(), error::Unspecified> { - let r = unsafe { SecRandomCopyBytes(kSecRandomDefault, dest.len(), dest.as_mut_ptr()) }; - match r { - 0 => Ok(()), - _ => Err(error::Unspecified), - } - } - - // XXX: This is emulating an opaque type with a non-opaque type. TODO: Fix - // this when - // https://github.com/rust-lang/rfcs/pull/1861#issuecomment-274613536 is - // resolved. - // - // Defined in `SecRandom.h` - #[repr(C)] - struct __SecRandom([u8; 0]); - type SecRandomRef = *const __SecRandom; - - // https://developer.apple.com/documentation/security/ksecrandomdefault - // says "This constant is a synonym for NULL." - #[allow(non_upper_case_globals)] - const kSecRandomDefault: SecRandomRef = core::ptr::null(); - - #[link(name = "Security", kind = "framework")] - extern "C" { - // For now `rnd` must be `kSecRandomDefault`. - #[must_use] - fn SecRandomCopyBytes(rnd: SecRandomRef, count: c::size_t, bytes: *mut u8) -> c::int; - } -} - -#[cfg(any(target_os = "fuchsia"))] -mod fuchsia { - use crate::error; - - pub fn fill(dest: &mut [u8]) -> Result<(), error::Unspecified> { - unsafe { - zx_cprng_draw(dest.as_mut_ptr(), dest.len()); - } - Ok(()) - } - - #[link(name = "zircon")] - extern "C" { - fn zx_cprng_draw(buffer: *mut u8, length: usize); +impl sealed::SecureRandom for SystemRandom { + #[inline(always)] + fn fill_impl(&self, dest: &mut [u8]) -> Result<(), error::Unspecified> { + getrandom::getrandom(dest).map_err(|_| error::Unspecified) } }