use crate::error;
pub trait SecureRandom: sealed::SecureRandom {
fn fill(&self, dest: &mut [u8]) -> Result<(), error::Unspecified>;
}
impl<T> SecureRandom for T
where
T: sealed::SecureRandom,
{
#[inline(always)]
fn fill(&self, dest: &mut [u8]) -> Result<(), error::Unspecified> {
self.fill_impl(dest)
}
}
pub struct Random<T: RandomlyConstructable>(T);
impl<T: RandomlyConstructable> Random<T> {
#[inline]
pub fn expose(self) -> T {
self.0
}
}
#[inline]
pub fn generate<T: RandomlyConstructable>(
rng: &dyn SecureRandom,
) -> Result<Random<T>, error::Unspecified>
where
T: RandomlyConstructable,
{
let mut r = T::zero();
rng.fill(r.as_mut_bytes())?;
Ok(Random(r))
}
pub(crate) mod sealed {
use crate::error;
pub trait SecureRandom: core::fmt::Debug {
fn fill_impl(&self, dest: &mut [u8]) -> Result<(), error::Unspecified>;
}
pub trait RandomlyConstructable: Sized {
fn zero() -> Self; fn as_mut_bytes(&mut self) -> &mut [u8]; }
macro_rules! impl_random_arrays {
[ $($len:expr)+ ] => {
$(
impl RandomlyConstructable for [u8; $len] {
#[inline]
fn zero() -> Self { [0; $len] }
#[inline]
fn as_mut_bytes(&mut self) -> &mut [u8] { &mut self[..] }
}
)+
}
}
impl_random_arrays![4 8 16 32 48 64];
}
pub trait RandomlyConstructable: self::sealed::RandomlyConstructable {}
impl<T> RandomlyConstructable for T where T: self::sealed::RandomlyConstructable {}
#[derive(Clone, Debug)]
pub struct SystemRandom(());
impl SystemRandom {
#[inline(always)]
pub fn new() -> Self {
Self(())
}
}
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 {}
#[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 = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
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<usize, error::Unspecified> {
use libc::c_long;
#[cfg(target_arch = "aarch64")]
const SYS_GETRANDOM: c_long = 278;
#[cfg(target_arch = "arm")]
const SYS_GETRANDOM: c_long = 384;
#[cfg(target_arch = "x86")]
const SYS_GETRANDOM: c_long = 355;
#[cfg(target_arch = "x86_64")]
const SYS_GETRANDOM: c_long = 318;
let chunk_len: c::size_t = dest.len();
let r = unsafe { libc::syscall(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 {
return Ok(0);
}
return Err(error::Unspecified);
}
Ok(r as usize)
}
}
#[cfg(all(
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<usize, error::Unspecified> {
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<usize, error::Unspecified> {
use winapi::shared::wtypesbase::ULONG;
assert!(core::mem::size_of::<usize>() >= core::mem::size_of::<ULONG>());
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(())
}
}
#[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 lazy_static::lazy_static;
lazy_static! {
static ref MECHANISM: Mechanism = {
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(
all(
any(target_os = "android", target_os = "linux"),
feature = "dev_urandom_fallback"
),
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "solaris"
))]
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 lazy_static::lazy_static;
lazy_static! {
static ref FILE: Result<std::fs::File, std::io::Error> =
std::fs::File::open("/dev/urandom");
}
match *FILE {
Ok(ref file) => {
use std::io::Read;
(&*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),
}
}
#[repr(C)]
struct SecRandomRef([u8; 0]);
#[link(name = "Security", kind = "framework")]
extern "C" {
static kSecRandomDefault: &'static SecRandomRef;
#[must_use]
fn SecRandomCopyBytes(
rnd: &'static 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);
}
}