Merge pull request #7 from dhardy/master

Add getrandom wrapper func and documentation
This commit is contained in:
Diggory Hardy 2019-02-20 08:58:29 +00:00 committed by GitHub
commit 10fdedd4cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 162 additions and 21 deletions

View File

@ -9,6 +9,11 @@ A Rust library to securely get random entropy. This crate derives its name from
Linux's `getrandom` function, but is cross platform, roughly supporting the same
set of platforms as Rust's `std` lib.
This is a low-level API. Most users should prefer a high-level random-number
library like [Rand] or a cryptography library.
[Rand]: https://crates.io/crates/rand
## Usage
@ -19,7 +24,25 @@ Add this to your `Cargo.toml`:
getrandom = "0.1"
```
TODO
Then invoke the `getrandom` function:
```rust
fn get_random_buf() -> Result<[u8; 32], getrandom::Error> {
let mut buf = [0u8; 32];
getrandom::getrandom(&mut buf)?;
buf
}
```
## Features
This library is `no_std` compatible on SGX but requires `std` on most platforms.
For WebAssembly (`wasm32`), Enscripten targets are supported directly; otherwise
one of the following features must be enabled:
- [`wasm-bindgen`](https://crates.io/crates/wasm_bindgen)
- [`stdweb`](https://crates.io/crates/stdweb)
# License

View File

@ -11,7 +11,7 @@ extern "C" {
fn cloudabi_sys_random_get(buf: *mut u8, len: usize) -> u16;
}
pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> {
pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
let errno = unsafe { cloudabi_sys_random_get(dest.as_ptr(), dest.len()) };
if errno == 0 {
Ok(())

View File

@ -15,7 +15,7 @@ use std::cell::RefCell;
thread_local!(static RNG_FILE: RefCell<Option<File>> = RefCell::new(None));
pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> {
pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
RNG_FILE.with(|f| {
use_init(f,
|| File::open("/dev/random").map_err(From::from),

View File

@ -10,6 +10,6 @@
//! `Err(UNAVAILABLE_ERROR)`
use super::UNAVAILABLE_ERROR;
pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> {
pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
Err(UNAVAILABLE_ERROR)
}

View File

@ -15,7 +15,7 @@ use super::utils::use_init;
thread_local!(static RNG_FILE: RefCell<Option<File>> = RefCell::new(None));
pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> {
pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
// `Crypto.getRandomValues` documents `dest` should be at most 65536
// bytes. `crypto.randomBytes` documents: "To minimize threadpool
// task length variation, partition large randomBytes requests when

View File

@ -12,18 +12,29 @@ use core::fmt;
#[cfg(not(target_env = "sgx"))]
use std::{io, error};
/// An unknown error.
pub const UNKNOWN_ERROR: Error = Error(unsafe {
NonZeroU32::new_unchecked(0x756e6b6e) // "unkn"
});
/// No generator is available.
pub const UNAVAILABLE_ERROR: Error = Error(unsafe {
NonZeroU32::new_unchecked(0x4e416e61) // "NAna"
});
/// The error type.
///
/// This type is small and no-std compatible.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Error(NonZeroU32);
impl Error {
/// Extract the error code.
///
/// This may equal one of the codes defined in this library or may be a
/// system error code.
///
/// One may attempt to format this error via the `Display` implementation.
pub fn code(&self) -> NonZeroU32 {
self.0
}
@ -51,9 +62,9 @@ impl From<io::Error> for Error {
}
#[cfg(not(target_env = "sgx"))]
impl Into<io::Error> for Error {
fn into(self) -> io::Error {
match self {
impl From<Error> for io::Error {
fn from(err: Error) -> Self {
match err {
UNKNOWN_ERROR => io::Error::new(io::ErrorKind::Other,
"getrandom error: unknown"),
UNAVAILABLE_ERROR => io::Error::new(io::ErrorKind::Other,

View File

@ -13,7 +13,7 @@ use super::Error;
use core::ptr;
use std::io;
pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> {
pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
let mib = [libc::CTL_KERN, libc::KERN_ARND];
// kern.arandom permits a maximum buffer size of 256 bytes
for chunk in dest.chunks_mut(256) {

View File

@ -11,7 +11,7 @@ extern crate fuchsia_cprng;
use super::Error;
pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> {
pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
fuchsia_cprng::cprng_draw(dest);
Ok(())
}

View File

@ -5,6 +5,92 @@
// <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.
//!
//! # 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`)
//! | SGX | RDRAND
//! | 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])
//!
//! Getrandom 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. If neither feature is enabled,
//! `getrandom` will always fail.
//!
//! ## 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; in these cases we always choose to block.
//!
//! 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. To avoid returning low-entropy bytes, we first read from
//! `/dev/random` and only switch to `/dev/urandom` once this has succeeded.
//!
//! # Error handling
//!
//! We always choose failure over returning insecure "random" bytes. In general,
//! on supported platforms, failure is unlikely, though not impossible. If an
//! error does occur, then it is likely that it will occur on every call to
//! `getrandom`, hence after the first successful call one can be reasonably
//! confident that no errors will occur.
//!
//! On unsupported platforms, `getrandom` always fails.
//!
//! [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
#![no_std]
#[cfg(not(target_env = "sgx"))]
@ -24,12 +110,17 @@ mod utils;
mod error;
pub use error::{Error, UNKNOWN_ERROR, UNAVAILABLE_ERROR};
// System-specific implementations.
//
// These should all provide getrandom_inner with the same signature as getrandom.
macro_rules! mod_use {
($cond:meta, $module:ident) => {
#[$cond]
mod $module;
#[$cond]
pub use $module::getrandom;
use $module::getrandom_inner;
}
}
@ -100,3 +191,19 @@ mod_use!(
))),
dummy
);
/// Fill `dest` with random bytes from the system's preferred random number
/// source.
///
/// This function returns an error on any failure, including partial reads. We
/// make no guarantees regarding the contents of `dest` on error.
///
/// Blocking is possible, at least during early boot; see module documentation.
///
/// In general, `getrandom` will be fast enough for interactive usage, though
/// significantly slower than a user-space CSPRNG; for the latter consider
/// [`rand::thread_rng`](https://docs.rs/rand/*/rand/fn.thread_rng.html).
pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> {
getrandom_inner(dest)
}

View File

@ -38,7 +38,7 @@ fn syscall_getrandom(dest: &mut [u8]) -> Result<(), io::Error> {
Ok(())
}
pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> {
pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
RNG_SOURCE.with(|f| {
use_init(f,
|| {

View File

@ -21,7 +21,7 @@ extern {
) -> c_int;
}
pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> {
pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
let ret = unsafe {
SecRandomCopyBytes(
kSecRandomDefault,

View File

@ -19,7 +19,7 @@ static RNG_INIT: AtomicBool = ATOMIC_BOOL_INIT;
thread_local!(static RNG_FILE: RefCell<Option<File>> = RefCell::new(None));
pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> {
pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
RNG_FILE.with(|f| {
use_init(f, || {
// read one byte from "/dev/random" to ensure that

View File

@ -12,7 +12,7 @@ extern crate libc;
use super::Error;
use std::io;
pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> {
pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
for chunk in dest.chunks_mut(256) {
let ret = unsafe {
libc::getentropy(

View File

@ -15,7 +15,7 @@ use std::cell::RefCell;
thread_local!(static RNG_FILE: RefCell<Option<File>> = RefCell::new(None));
pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> {
pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
RNG_FILE.with(|f| {
use_init(f,
|| File::open("rand:").map_err(From::from),

View File

@ -29,7 +29,7 @@ fn get_rand_u64() -> Result<u64, Error> {
Err(UNKNOWN_ERROR)
}
pub fn getrandom(mut dest: &mut [u8]) -> Result<(), Error> {
pub fn getrandom_inner(mut dest: &mut [u8]) -> Result<(), Error> {
while dest.len() >= 8 {
let (chunk, left) = {dest}.split_at_mut(8);
dest = left;

View File

@ -48,12 +48,12 @@ fn syscall_getrandom(dest: &mut [u8]) -> Result<(), Error> {
syscall(SYS_GETRANDOM, dest.as_mut_ptr(), dest.len(), 0)
};
if ret == -1 || ret != dest.len() as i64 {
return Err(io::Error::last_os_error().from());
return Err(io::Error::last_os_error().into());
}
Ok(())
}
pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> {
pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
// The documentation says 1024 is the maximum for getrandom
// and 1040 for /dev/random.
RNG_SOURCE.with(|f| {
@ -75,7 +75,7 @@ pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> {
},
}
})
}).map_err(|_| Error::Unknown)
})
}
fn is_getrandom_available() -> bool {

View File

@ -15,7 +15,7 @@ use self::winapi::um::winnt::PVOID;
use std::io;
use super::Error;
pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> {
pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
let ret = unsafe {
RtlGenRandom(dest.as_mut_ptr() as PVOID, dest.len() as ULONG)
};