Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add getrandom wrapper func and documentation #7

Merged
merged 4 commits into from
Feb 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/cloudabi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
Expand Down
2 changes: 1 addition & 1 deletion src/dragonfly_haiku.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
2 changes: 1 addition & 1 deletion src/dummy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
2 changes: 1 addition & 1 deletion src/emscripten.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 14 additions & 3 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion src/freebsd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion src/fuchsia.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
109 changes: 108 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"))]
Expand All @@ -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;
}
}

Expand Down Expand Up @@ -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)
}
2 changes: 1 addition & 1 deletion src/linux_android.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
|| {
Expand Down
2 changes: 1 addition & 1 deletion src/macos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion src/netbsd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/openbsd_bitrig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion src/redox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
2 changes: 1 addition & 1 deletion src/sgx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 3 additions & 3 deletions src/solaris.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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| {
Expand All @@ -75,7 +75,7 @@ pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> {
},
}
})
}).map_err(|_| Error::Unknown)
})
}

fn is_getrandom_available() -> bool {
Expand Down
2 changes: 1 addition & 1 deletion src/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
};
Expand Down