Merge pull request #7 from dhardy/master
Add getrandom wrapper func and documentation
This commit is contained in:
commit
10fdedd4cf
25
README.md
25
README.md
@ -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
|
||||
|
@ -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(())
|
||||
|
@ -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),
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
17
src/error.rs
17
src/error.rs
@ -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,
|
||||
|
@ -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) {
|
||||
|
@ -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
src/lib.rs
109
src/lib.rs
@ -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§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
|
||||
|
||||
#![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)
|
||||
}
|
||||
|
@ -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,
|
||||
|| {
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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),
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user