- Feature Name: system-random
- Start Date: 2018-12-21
- RFC PR: (leave this empty)
- Rust Issue: (leave this empty)
Summary
Add std::random::secure_random and custom error type.
Motivation
This RFC was proposed in Rand #648. Provision of a secure source of random numbers may be seen as a core Operating System service:
- this is OS-specific, yet all major OSes and many emulation/sandbox environments have at least one randomness source
- almost all
stdplatforms have some form of randomness source provided by the OS, while most other platforms do not have standard solutions (although they may have device-specific ones) - providing random data is simple relative to all the things that can be done with it
Currently both the Rand's OsRng and libstd's hashmap_random_keys
function (an internal detail used to implement RandomState) implement an
interface to random data provided by the current platform's interface. This is
a complex interface that must be duplicated since the hashmap_random_keys
function is not exposed from std (and not sufficently generic).
Additionally, this could be leveraged to provide uniform no_std support via
usage of lang_items.
Guide-level explanation
The std::random module provides the following function to access the current
platform's secure random interface:
/// Fill `buf` with random bytes from the platform's secure random number
/// source, or fail.
///
/// This interface is designed primarily with security in mind, and will fail if
/// there is no strong source of random data available. On failure no guarantees
/// are made regarding the contents of `buf`.
///
/// It is possible (though unlikely) that the implementation will block.
///
/// Note that the implementation may short-circuit on a zero-length buffer, so
/// to test initialisation you must request at least one byte.
pub fn secure_random(buf: &mut [u8]) -> Result<(), Error>This module is also available as core::random, though no_std builds require
the user or a third-party crate to provide the backend.
Reference-level explanation
De-duplication and implementation
The current system random interface implementations have some differences:
| std | Rand | |
|---|---|---|
| Unix | Use getrandom if available, otherwise use /dev/urandom |
Use getrandom if available, otherwise test reading a byte from /dev/random in non-blocking mode and if successful use /dev/urandom |
| NetBSD | As Unix | Test reading a byte from /dev/random in non-blocking mode and if successful use /dev/urandom |
| DragonFly / Haiku / Emscripten | As Unix | Use /dev/random |
| Solaris | As Unix | Use getrandom if available, otherwise use /dev/random in non-blocking mode |
| SGX | Use RDRAND ten times | No support |
| CloudABI | Use arc4random_buf via libc |
Use random_get via cloudabi crate |
| MacOS / iOS | Use SecRandomCopyBytes |
Use SecRandomCopyBytes |
| FreeBSD | Use kern.arandom |
Use kern.arandom |
| OpenBSD | Use getentropy |
Use getentropy |
| Redox | Return constants | Use rand: interface |
| Fuchsia | Use zx_cprng_draw |
Use fuchsia_zircon::cprng_draw |
| Windows | Use RtlGenRandom |
Use RtlGenRandom via winapi crate |
| WASM | Return constants | No support for WASM alone |
WASM + stdweb |
Use browser or Node interface | |
WASM + wasm-bindgen |
Use browser or Node interface |
Additionally, Rand's OsRng splits large buffers into chunks at the maximum
size supported by each system interface.
We propose to unify these into a single std::random::secure_random function,
then implement RandomState and OsRng as wrappers around this.
Blocking?
It is possible the random source may block (although we prefer /dev/urandom
over /dev/random). For example, this may happen if the RNG is not yet
initialised (which Rand's current implementation for Linux explicitly checks).
We have a choice here: in some cases where the source would block, we could
detect this and fail with a WouldBlock error code, although we cannot
guarantee the implementation will never block. Alternatively we could always
block. We could make this behaviour configurable.
Optionally, we could add a function like the following, which partially replaces the need for a non-blocking API:
/// Attempt to check, without blocking, whether the system random source is
/// ready for usage.
///
/// If this returns `Some(true)`, then calls to `secure_random` are likely to
/// succeed without blocking. If it returns `Some(false)`, they are likely to
/// block.
///
/// In case it is not possible to determine the status without blocking, this
/// function returns `None`.
fn is_ready() -> Option<bool>Error type
To enable this, we use a custom Error type, whose design is constrained to
enable no_std support. Since core::random::Error must be functional without
an allocator, we cannot use std::io::Error (which may use a Box). Since
std::random::Error must be the same type, we cannot copy the design of
rand_core::Error. We thus must pick a simple design, like the following:
pub enum ErrorKind {
NotReady, // optional status (see blocking section)
Unavailable,
}
pub struct Error {
pub msg: &'static str, // cannot be a String or have 'a parameter as in &'a str
pub kind: ErrorKind,
}Handling errors within std
The only (current) need for randomness within libstd is RandomState.
Since a source of secure entropy is not critical except to avoid DoS attacks on
certain public services, we think it acceptable that RandomState use a
constant value should secure_random return an error. This allows use of
HashMap on all platforms.
Should this also use a constant if secure_random would block?
no_std support
There are two challenges when std is not available:
- There may not be an allocator available. This is not an issue for
secure_randomitself, but does imply that thestd::io::Errortype cannot be used in its current form. - The platform may not have a (reliable) secure randomness source.
We propose that libcore provide an implementation of secure_random which
always fails, but that libstd uses lang items to override this with a real
implementation, depending on the platform. Users of no_std must provide an
implementation themselves (likely via a third-party crate) or avoid dependence
on this function.
WASM
WebAssembly targets web browsers, and these provide a JavaScript API to retrieve
secure random data (crypto.getRandomValues); alternatively, it may be possible
to use Node.js (crypto.randomFillSync). Since these are JavaScript functions,
some method of bridging Rust and JS is required; currently the Rand project
supports both wasm-bindgen and stdweb.
I am not sufficiently knowledgable to make a recommendation for what std
should do here; e.g. it could use wasm-bindgen or simply not implement
secure_random on WASM. Help please.
Drawbacks
It is worth pointing out that std's current hashmap_random_keys is only
dependend on to provide randomisation of hash functions as protection against
DoS attacks on public services. Because of this, it is not considered
security-essential, especially on platforms which are unlikely to be used as a
web server. In light of this, the current implementations for Redox and WASM
simply use hard-coded constants. This would not be acceptable for the interface
proposed here, which may make supporting some platforms harder (e.g. while
Rand's OsRng does have support for WASM, it has been a frequent source
of issues) — although the API does allow for explicit failure.
Unresolved questions
See the blocking section above and note under error handling.
Rationale and alternatives
We could choose between multiple names for the interface, depending on what
we choose to emphasise: secure_random, system_random, os_random.
Personally I prefer system_random except for the possible association with
C#'s insecure generator.
We could use a struct instead of a plain function to objectify the
configuration. This allows the simple SystemRandom::new().fill_buf(&mut buf)
vs SystemRandom::nonblocking().fill_buf(&mut buf). However, this appears
needlessly complex, although it does have some precedent in std:
File::open(path) vs OpenOptions::new().read(true).open(path). This may also
require a different backend for no_std implementation via lang_items.
Optionally we could add a function like random::is_ready() -> bool to test
whether the system RNG is available and has been initialized. There is little
motivation though since the user can simply try reading a byte.
There is scope for supporting an insecure randomness source (via a separate function, a parameter, or even an error code indicating that the buffer has been filled with insecure random data).
We could alternatively provide an Rng trait and implement that, however this
is likely best left to an external crate. The RngCore trait arrived at in
Rand is complex in order to provide an optimal interface to multiple different
users.
Alternatively, we could not do this; the Rand crate is already widely used and
the new rand_os crate allows use of (almost) only a system-random wrapper.
This does not solve the duplicate implementation issue or solve the problem of
a user-provided alternative with no_std configurations (though the latter has
other solutions).
Prior art
This RFC is motivated by Rand #648 and Rand #643.
We can compare with other languages, though this has limited utility:
- C advocates use of
srand(time(0))for insecure seeding but has no equivalent to this proposal - C++ has
std::random_deviceas a non-deterministic random number generator, where the specification allows implementation by a local PRNG, and implementations may or may not be secure (or may even be deterministic in buggy cases) - Python has a
os.urandom(size)which wraps the OS randomness source (in blocking mode only) and is intended for cryptographic usage - Modern browsers support the JS API
crypto.getRandomValues(array); - Java has more APIs than anyone could want
- C# has
System.Randomwhich is not secure andRNGCryptoServiceProvider
Unresolved questions
Details of the Error type
Future possibilities
(empty section)