1use crate::{CryptoError, Result};
4
5const ENTROPY_SAMPLE_SIZE: usize = 32;
6
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct EntropyHealthReport {
9 pub source: &'static str,
10 pub sample_size: usize,
11}
12
13pub fn entropy_health_check() -> Result<EntropyHealthReport> {
19 let mut sample_a = [0u8; ENTROPY_SAMPLE_SIZE];
20 let mut sample_b = [0u8; ENTROPY_SAMPLE_SIZE];
21
22 getrandom::getrandom(&mut sample_a)
23 .map_err(|e| CryptoError::KeyError(format!("OS entropy unavailable: {e}")))?;
24 getrandom::getrandom(&mut sample_b)
25 .map_err(|e| CryptoError::KeyError(format!("OS entropy unavailable: {e}")))?;
26
27 if sample_a.iter().all(|b| *b == 0) && sample_b.iter().all(|b| *b == 0) {
28 return Err(CryptoError::KeyError(
29 "Entropy self-test failed: degenerate all-zero samples".to_string(),
30 ));
31 }
32
33 if sample_a == sample_b {
34 return Err(CryptoError::KeyError(
35 "Entropy self-test failed: identical consecutive RNG samples".to_string(),
36 ));
37 }
38
39 Ok(EntropyHealthReport {
40 source: "os_rng",
41 sample_size: ENTROPY_SAMPLE_SIZE,
42 })
43}
44
45#[cfg(test)]
46mod tests {
47 use super::*;
48
49 #[test]
50 fn test_entropy_health_check_reports_ok() {
51 let report = entropy_health_check().expect("os entropy health check");
52 assert_eq!(report.source, "os_rng");
53 assert_eq!(report.sample_size, ENTROPY_SAMPLE_SIZE);
54 }
55}