use crate::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum QrngSource {
Crypto4A,
DevQrandom,
SecureEnclave,
OsCsprng,
}
pub struct QrngProvider {
source: QrngSource,
}
impl QrngProvider {
pub fn new() -> Self {
let source = Self::detect_best_source();
Self { source }
}
pub fn with_source(source: QrngSource) -> Self {
Self { source }
}
fn detect_best_source() -> QrngSource {
#[cfg(not(target_arch = "wasm32"))]
{
if Self::is_crypto4a_available() {
return QrngSource::Crypto4A;
}
if std::path::Path::new("/dev/qrandom").exists() {
return QrngSource::DevQrandom;
}
}
QrngSource::OsCsprng
}
#[cfg(not(target_arch = "wasm32"))]
fn is_crypto4a_available() -> bool {
false
}
pub fn source(&self) -> QrngSource {
self.source
}
pub fn get_entropy(&self, bytes: usize) -> Result<Vec<u8>> {
match self.source {
#[cfg(not(target_arch = "wasm32"))]
QrngSource::Crypto4A => self.get_crypto4a_entropy(bytes),
#[cfg(not(target_arch = "wasm32"))]
QrngSource::DevQrandom => self.get_dev_qrandom_entropy(bytes),
#[cfg(not(target_arch = "wasm32"))]
QrngSource::SecureEnclave => self.get_secure_enclave_entropy(bytes),
QrngSource::OsCsprng => self.get_os_csprng_entropy(bytes),
#[cfg(target_arch = "wasm32")]
_ => self.get_os_csprng_entropy(bytes),
}
}
#[cfg(not(target_arch = "wasm32"))]
fn get_crypto4a_entropy(&self, _bytes: usize) -> Result<Vec<u8>> {
Err(Error::QrngUnavailable("Crypto4A not implemented".into()))
}
#[cfg(not(target_arch = "wasm32"))]
fn get_dev_qrandom_entropy(&self, bytes: usize) -> Result<Vec<u8>> {
use std::fs::File;
use std::io::Read;
let mut file = File::open("/dev/qrandom")
.map_err(|e| Error::QrngUnavailable(e.to_string()))?;
let mut entropy = vec![0u8; bytes];
file.read_exact(&mut entropy)
.map_err(|e| Error::QrngUnavailable(e.to_string()))?;
Ok(entropy)
}
#[cfg(not(target_arch = "wasm32"))]
fn get_secure_enclave_entropy(&self, bytes: usize) -> Result<Vec<u8>> {
self.get_os_csprng_entropy(bytes)
}
fn get_os_csprng_entropy(&self, bytes: usize) -> Result<Vec<u8>> {
use rand::RngCore;
let mut entropy = vec![0u8; bytes];
rand::thread_rng().fill_bytes(&mut entropy);
Ok(entropy)
}
}
impl Default for QrngProvider {
fn default() -> Self {
Self::new()
}
}
static QRNG: std::sync::OnceLock<QrngProvider> = std::sync::OnceLock::new();
pub fn get_entropy(bytes: usize) -> Result<Vec<u8>> {
QRNG.get_or_init(QrngProvider::new).get_entropy(bytes)
}
pub fn current_source() -> QrngSource {
QRNG.get_or_init(QrngProvider::new).source()
}
pub fn is_hardware_available() -> bool {
matches!(
current_source(),
QrngSource::Crypto4A | QrngSource::DevQrandom
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_entropy() {
let entropy = get_entropy(32).unwrap();
assert_eq!(entropy.len(), 32);
}
#[test]
fn test_entropy_uniqueness() {
let e1 = get_entropy(32).unwrap();
let e2 = get_entropy(32).unwrap();
assert_ne!(e1, e2);
}
#[test]
fn test_provider_source() {
let provider = QrngProvider::new();
assert!(matches!(
provider.source(),
QrngSource::OsCsprng | QrngSource::DevQrandom | QrngSource::Crypto4A
));
}
}