use alloc::string::{String, ToString};
pub fn fill_os_random(output: &mut [u8]) -> Result<(), String> {
#[cfg(target_os = "windows")]
{
fill_windows(output)
}
#[cfg(not(target_os = "windows"))]
{
fill_unix_urandom(output)
}
}
#[cfg(target_os = "windows")]
fn fill_windows(output: &mut [u8]) -> Result<(), String> {
use alloc::format;
use core::ffi::c_void;
use std::os::raw::c_ulong;
const BCRYPT_USE_SYSTEM_PREFERRED_RNG: c_ulong = 0x0000_0002;
#[link(name = "bcrypt")]
extern "system" {
fn BCryptGenRandom(
h_algorithm: *const c_void,
pb_buffer: *mut u8,
cb_buffer: c_ulong,
dw_flags: c_ulong,
) -> i32;
}
let length = c_ulong::try_from(output.len())
.map_err(|_| "requested random buffer exceeds 32-bit length".to_string())?;
let status = unsafe {
BCryptGenRandom(
core::ptr::null(),
output.as_mut_ptr(),
length,
BCRYPT_USE_SYSTEM_PREFERRED_RNG,
)
};
if status == 0 {
Ok(())
} else {
Err(format!("BCryptGenRandom failed with status {status:#x}"))
}
}
#[cfg(not(target_os = "windows"))]
fn fill_unix_urandom(output: &mut [u8]) -> Result<(), String> {
use std::fs::File;
use std::io::Read;
let mut file = File::open("/dev/urandom").map_err(|e| e.to_string())?;
file.read_exact(output).map_err(|e| e.to_string())
}
pub fn wipe(bytes: &mut [u8]) {
for byte in bytes.iter_mut() {
unsafe { core::ptr::write_volatile(byte, 0) };
}
}
pub struct Zeroizing {
inner: alloc::vec::Vec<u8>,
}
impl Zeroizing {
pub fn new(inner: alloc::vec::Vec<u8>) -> Self {
Self { inner }
}
pub fn as_slice(&self) -> &[u8] {
self.inner.as_slice()
}
}
impl core::ops::Deref for Zeroizing {
type Target = [u8];
fn deref(&self) -> &[u8] {
self.inner.as_slice()
}
}
impl Drop for Zeroizing {
fn drop(&mut self) {
wipe(self.inner.as_mut_slice());
}
}
pub trait Zeroize {
fn zeroize(&mut self);
}
impl Zeroize for [u8; 32] {
fn zeroize(&mut self) {
wipe(self);
}
}
impl Zeroize for [u8] {
fn zeroize(&mut self) {
wipe(self);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fills_and_distinguishes() {
let mut a = [0_u8; 32];
let mut b = [0_u8; 32];
fill_os_random(&mut a).unwrap();
fill_os_random(&mut b).unwrap();
assert_ne!(a, b);
assert!(a.iter().any(|&byte| byte != 0));
}
#[test]
fn wipe_zeroes_memory() {
let mut bytes = [0xAB; 64];
wipe(&mut bytes);
assert!(bytes.iter().all(|&byte| byte == 0));
}
#[test]
fn zeroizing_wipes_on_drop() {
let mut probe = [0xCD; 32];
{
let buffer = alloc::vec::Vec::from(&probe[..]);
let _wrapped = Zeroizing::new(buffer);
}
probe.zeroize();
assert!(probe.iter().all(|&byte| byte == 0));
}
}