use crate::audit::AuditLogger;
use crate::error::{CryptoError, Result};
use rand::{CryptoRng, RngCore, SeedableRng};
use rand_chacha::ChaCha20Rng;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use zeroize::Zeroize;
static HARDWARE_RNG_AVAILABLE: AtomicBool = AtomicBool::new(false);
static RDSEED_AVAILABLE: AtomicBool = AtomicBool::new(false);
const RDRAND_MAX_RETRIES: usize = 10;
#[inline]
pub fn detect_hardware_rng() {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
let has_rdrand = std::is_x86_feature_detected!("rdrand");
let has_rdseed = std::is_x86_feature_detected!("rdseed");
HARDWARE_RNG_AVAILABLE.store(has_rdrand, Ordering::Relaxed);
RDSEED_AVAILABLE.store(has_rdseed && has_rdrand, Ordering::Relaxed);
AuditLogger::log(
"HARDWARE_RNG_DETECTION",
None,
None,
if has_rdrand {
Ok(())
} else {
Err(CryptoError::HardwareAccelerationUnavailable(
"RDRAND not available".into(),
))
},
);
}
#[cfg(target_arch = "aarch64")]
{
#[cfg(feature = "cpu-aesni")]
{
let has_rng = cpufeatures::is_aarch64_feature_detected!("rng");
HARDWARE_RNG_AVAILABLE.store(has_rng, Ordering::Relaxed);
RDSEED_AVAILABLE.store(false, Ordering::Relaxed); }
#[cfg(not(feature = "cpu-aesni"))]
{
HARDWARE_RNG_AVAILABLE.store(false, Ordering::Relaxed);
RDSEED_AVAILABLE.store(false, Ordering::Relaxed);
}
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))]
{
HARDWARE_RNG_AVAILABLE.store(false, Ordering::Relaxed);
RDSEED_AVAILABLE.store(false, Ordering::Relaxed);
}
}
#[inline]
pub fn is_hardware_rng_available() -> bool {
HARDWARE_RNG_AVAILABLE.load(Ordering::Relaxed)
}
#[inline]
pub fn is_rdseed_available() -> bool {
RDSEED_AVAILABLE.load(Ordering::Relaxed)
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[inline]
fn read_rdrand_u64() -> Result<u64> {
for _ in 0..RDRAND_MAX_RETRIES {
let result = unsafe {
let mut val: u64 = core::mem::zeroed();
let status = core::arch::x86_64::_rdrand64_step(&mut val);
if status == 1 {
val
} else {
continue;
}
};
return Ok(result);
}
Err(CryptoError::InsufficientEntropy)
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
#[inline]
fn read_rdrand_u64() -> Result<u64> {
debug_assert!(false, "read_rdrand_u64 called on unsupported architecture");
Err(CryptoError::HardwareAccelerationUnavailable(
"RDRAND not supported on this architecture".into(),
))
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[inline]
fn read_rdseed_u64() -> Result<u64> {
for _ in 0..RDRAND_MAX_RETRIES {
let result = unsafe {
let mut val: u64 = core::mem::zeroed();
let status = core::arch::x86_64::_rdseed64_step(&mut val);
if status == 1 {
val
} else {
continue;
}
};
return Ok(result);
}
Err(CryptoError::InsufficientEntropy)
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
#[inline]
fn read_rdseed_u64() -> Result<u64> {
debug_assert!(false, "read_rdseed_u64 called on unsupported architecture");
Err(CryptoError::HardwareAccelerationUnavailable(
"RDSEED not supported on this architecture".into(),
))
}
#[inline]
pub fn hardware_fill_bytes(dest: &mut [u8]) -> Result<()> {
if !is_hardware_rng_available() {
return Err(CryptoError::HardwareAccelerationUnavailable(
"Hardware RNG not available".into(),
));
}
let (chunks, remainder) = dest.split_at_mut(dest.len() / 8 * 8);
for chunk in chunks.chunks_exact_mut(8) {
let value = read_rdrand_u64()?;
chunk.copy_from_slice(&value.to_le_bytes());
}
if !remainder.is_empty() {
let value = read_rdrand_u64()?;
let bytes = value.to_le_bytes();
remainder.copy_from_slice(&bytes[..remainder.len()]);
}
Ok(())
}
#[inline]
pub fn rdseed_fill_bytes(dest: &mut [u8]) -> Result<()> {
if !is_rdseed_available() {
return Err(CryptoError::HardwareAccelerationUnavailable(
"RDSEED not available".into(),
));
}
let (chunks, remainder) = dest.split_at_mut(dest.len() / 8 * 8);
for chunk in chunks.chunks_exact_mut(8) {
let value = read_rdseed_u64()?;
chunk.copy_from_slice(&value.to_le_bytes());
}
if !remainder.is_empty() {
let value = read_rdseed_u64()?;
let bytes = value.to_le_bytes();
remainder.copy_from_slice(&bytes[..remainder.len()]);
}
Ok(())
}
#[derive(Clone)]
pub struct HardwareRng {
csprng: Arc<Mutex<ChaCha20Rng>>,
use_hardware: bool,
}
impl HardwareRng {
pub fn new() -> Result<Self> {
let use_hardware = is_hardware_rng_available();
if use_hardware {
Ok(Self {
csprng: Arc::new(Mutex::new(ChaCha20Rng::from_entropy())),
use_hardware: true,
})
} else {
let mut seed = Self::get_software_seed()?;
let rng = ChaCha20Rng::from_seed(seed);
seed.zeroize();
Ok(Self {
csprng: Arc::new(Mutex::new(rng)),
use_hardware: false,
})
}
}
#[inline]
fn get_software_seed() -> Result<[u8; 32]> {
let mut seed = [0u8; 32];
getrandom::getrandom(&mut seed).map_err(|_| CryptoError::InsufficientEntropy)?;
Ok(seed)
}
#[inline]
pub fn fill(&self, dest: &mut [u8]) -> Result<()> {
if self.use_hardware {
hardware_fill_bytes(dest)?;
self.run_health_tests(dest)?;
Ok(())
} else {
let mut rng = self
.csprng
.lock()
.map_err(|_| CryptoError::MemoryProtectionFailed("RNG lock poisoned".into()))?;
rng.fill_bytes(dest);
self.run_health_tests(dest)?;
Ok(())
}
}
#[inline]
pub(crate) fn run_health_tests(&self, data: &[u8]) -> Result<()> {
if data.len() >= 16 {
let all_same = data.windows(2).all(|w| w[0] == w[1]);
if all_same {
AuditLogger::log(
"RNG_HEALTH_TEST_FAILURE",
None,
None,
Err(CryptoError::FipsError(
"RNG health test failed: output appears biased".into(),
)),
);
return Err(CryptoError::FipsError("RNG health test failed".into()));
}
}
Ok(())
}
}
impl Default for HardwareRng {
fn default() -> Self {
Self::new().unwrap_or_else(|_e| {
AuditLogger::log(
"HARDWARE_RNG_INIT_FAILURE",
None,
None,
Err(CryptoError::InsufficientEntropy),
);
let mut seed = [0u8; 32];
let _ = getrandom::getrandom(&mut seed);
let rng = ChaCha20Rng::from_seed(seed);
seed.zeroize();
Self {
csprng: Arc::new(Mutex::new(rng)),
use_hardware: false,
}
})
}
}
impl RngCore for HardwareRng {
#[inline]
fn next_u32(&mut self) -> u32 {
let mut buf = [0u8; 4];
if let Err(e) = self.fill(&mut buf) {
AuditLogger::log("RNG_FAILURE", None, None, Err(e));
panic!("Critical RNG failure: unable to generate random data");
}
u32::from_le_bytes(buf)
}
#[inline]
fn next_u64(&mut self) -> u64 {
let mut buf = [0u8; 8];
if let Err(e) = self.fill(&mut buf) {
AuditLogger::log("RNG_FAILURE", None, None, Err(e));
panic!("Critical RNG failure: unable to generate random data");
}
u64::from_le_bytes(buf)
}
#[inline]
fn fill_bytes(&mut self, dest: &mut [u8]) {
if let Err(e) = self.fill(dest) {
AuditLogger::log("RNG_FAILURE", None, None, Err(e));
panic!("Critical RNG failure: unable to fill bytes");
}
}
#[inline]
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> std::result::Result<(), rand::Error> {
self.fill(dest).map_err(|_e| {
AuditLogger::log(
"RNG_FAILURE",
None,
None,
Err(CryptoError::InsufficientEntropy),
);
rand::Error::new("Hardware RNG failed")
})
}
}
impl CryptoRng for HardwareRng {}
#[derive(Clone)]
pub struct BulkHardwareRng {
buffer: Vec<u8>,
hardware: bool,
}
impl BulkHardwareRng {
pub fn new(buffer_size: usize) -> Result<Self> {
if buffer_size == 0 {
return Err(CryptoError::InvalidParameter(
"Buffer size must be greater than 0".into(),
));
}
Ok(Self {
buffer: vec![0u8; buffer_size],
hardware: is_hardware_rng_available(),
})
}
#[inline]
pub fn fill(&mut self, dest: &mut [u8]) -> Result<()> {
let mut offset = 0;
while offset < dest.len() {
if self.hardware {
hardware_fill_bytes(&mut self.buffer)?;
} else {
getrandom::getrandom(&mut self.buffer)
.map_err(|_| CryptoError::InsufficientEntropy)?;
}
let copy_len = std::cmp::min(self.buffer.len(), dest.len() - offset);
dest[offset..offset + copy_len].copy_from_slice(&self.buffer[..copy_len]);
offset += copy_len;
}
Ok(())
}
#[inline]
pub fn fill_buffer(&mut self) -> Result<()> {
if self.hardware {
hardware_fill_bytes(&mut self.buffer)
} else {
getrandom::getrandom(&mut self.buffer).map_err(|_| CryptoError::InsufficientEntropy)
}
}
#[inline]
pub fn buffer_slice(&self) -> &[u8] {
&self.buffer
}
}
pub struct SeedGenerator {
entropy_pool: [u8; 64],
pool_filled: usize,
}
impl SeedGenerator {
pub fn new() -> Self {
Self {
entropy_pool: [0u8; 64],
pool_filled: 0,
}
}
#[inline]
fn add_hardware_entropy(&mut self) -> Result<()> {
if is_rdseed_available() {
let mut chunk = [0u8; 8];
let remaining = 64 - self.pool_filled;
for _ in 0..(remaining / 8) {
rdseed_fill_bytes(&mut chunk)?;
self.entropy_pool[self.pool_filled..self.pool_filled + 8].copy_from_slice(&chunk);
self.pool_filled += 8;
}
if self.pool_filled < 64 && !remaining.is_multiple_of(8) {
rdseed_fill_bytes(&mut chunk)?;
let remainder = remaining % 8;
self.entropy_pool[self.pool_filled..self.pool_filled + remainder]
.copy_from_slice(&chunk[..remainder]);
self.pool_filled += remainder;
}
} else if is_hardware_rng_available() {
let mut chunk = [0u8; 8];
let remaining = 64 - self.pool_filled;
for _ in 0..(remaining / 8) {
hardware_fill_bytes(&mut chunk)?;
self.entropy_pool[self.pool_filled..self.pool_filled + 8].copy_from_slice(&chunk);
self.pool_filled += 8;
}
if self.pool_filled < 64 && !remaining.is_multiple_of(8) {
hardware_fill_bytes(&mut chunk)?;
let remainder = remaining % 8;
self.entropy_pool[self.pool_filled..self.pool_filled + remainder]
.copy_from_slice(&chunk[..remainder]);
self.pool_filled += remainder;
}
} else {
return Err(CryptoError::HardwareAccelerationUnavailable(
"No hardware RNG available for seed generation".into(),
));
}
Ok(())
}
#[inline]
pub fn generate_seed(&mut self, size: usize) -> Result<Vec<u8>> {
if size < 32 {
return Err(CryptoError::InvalidParameter(
"Seed size must be at least 32 bytes for cryptographic use".into(),
));
}
if self.pool_filled < 64 {
self.add_hardware_entropy()?;
}
let mut seed = vec![0u8; size];
{
let mut rng = ChaCha20Rng::from_entropy();
rng.fill_bytes(&mut seed);
}
self.pool_filled = 0;
Ok(seed)
}
}
impl Default for SeedGenerator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hardware_rng_detection() {
detect_hardware_rng();
let _ = is_hardware_rng_available();
let _ = is_rdseed_available();
}
#[test]
fn test_rdrand_u64() {
if !is_hardware_rng_available() {
return;
}
let result = read_rdrand_u64();
assert!(result.is_ok());
let value = result.unwrap();
assert_ne!(value, 0); }
#[test]
fn test_hardware_fill_bytes() {
if !is_hardware_rng_available() {
return;
}
let mut buf = [0u8; 100];
let result = hardware_fill_bytes(&mut buf);
assert!(result.is_ok());
assert!(buf.iter().any(|&b| b != 0));
}
#[test]
fn test_hardware_fill_bytes_alignment() {
if !is_hardware_rng_available() {
return;
}
for size in [1, 2, 3, 4, 5, 7, 8, 15, 16, 17, 31, 32, 63, 64, 100] {
let mut buf = vec![0u8; size];
hardware_fill_bytes(&mut buf).expect("hardware_fill_bytes failed");
assert!(buf.iter().any(|&b| b != 0), "All zeros for size {}", size);
}
}
#[test]
fn test_hardware_rng_basic_operations() {
if !is_hardware_rng_available() {
return;
}
let mut rng = HardwareRng::new().expect("Failed to create HardwareRng");
let val_u32 = rng.next_u32();
assert_ne!(val_u32, 0);
let val_u64 = rng.next_u64();
assert_ne!(val_u64, 0);
let mut buf = [0u8; 32];
rng.fill_bytes(&mut buf);
assert!(buf.iter().any(|&b| b != 0));
let mut buf = [0u8; 32];
let result = rng.try_fill_bytes(&mut buf);
assert!(result.is_ok());
assert!(buf.iter().any(|&b| b != 0));
}
#[test]
fn test_bulk_hardware_rng() {
if !is_hardware_rng_available() {
return;
}
let mut rng = BulkHardwareRng::new(1024).expect("Failed to create BulkHardwareRng");
let mut dest = vec![0u8; 4096];
rng.fill(&mut dest).expect("Bulk fill failed");
assert!(dest.iter().any(|&b| b != 0));
}
#[test]
fn test_seed_generator() {
if !is_hardware_rng_available() {
return;
}
let mut generator = SeedGenerator::new();
let seed = generator.generate_seed(32).expect("Seed generation failed");
assert_eq!(seed.len(), 32);
assert!(seed.iter().any(|&b| b != 0));
}
#[test]
fn test_seed_generator_minimum_size() {
let mut generator = SeedGenerator::new();
let result = generator.generate_seed(16);
assert!(result.is_err());
}
#[test]
#[allow(unused_mut)]
fn test_health_tests() {
if !is_hardware_rng_available() {
return;
}
let mut rng = HardwareRng::new().expect("Failed to create HardwareRng");
let mut normal_data = [
0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x56, 0x78, 0x90, 0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x56,
0x78, 0x90, 0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x56, 0x78, 0x90, 0xAB, 0xCD, 0xEF, 0x12,
0x34, 0x56, 0x78, 0x90,
];
assert!(rng.run_health_tests(&normal_data).is_ok());
let same_data = [0xAB; 32];
assert!(rng.run_health_tests(&same_data).is_err());
let short_data = [0xCD; 8];
assert!(rng.run_health_tests(&short_data).is_ok());
}
#[test]
fn test_fallback_to_software() {
HARDWARE_RNG_AVAILABLE.store(false, Ordering::Relaxed);
RDSEED_AVAILABLE.store(false, Ordering::Relaxed);
let rng = HardwareRng::new();
assert!(rng.is_ok());
#[allow(unused_mut)]
if let Ok(mut rng) = rng {
let mut buf = [0u8; 32];
let result = rng.fill(&mut buf);
assert!(result.is_ok());
assert!(buf.iter().any(|&b| b != 0));
}
detect_hardware_rng();
}
#[test]
fn test_clone() {
if !is_hardware_rng_available() {
return;
}
let rng1 = HardwareRng::new().expect("Failed to create HardwareRng");
let _rng2 = rng1.clone();
let mut buf1 = [0u8; 32];
let _buf2 = [0u8; 32];
assert!(rng1.fill(&mut buf1).is_ok());
}
}