Skip to main content

ic_core/
entropy.rs

1//! Operating-system entropy acquisition.
2//!
3//! IronCrypto never uses raw OS bytes as key material directly. The OS
4//! source is treated as the *entropy input* to an SP 800-90A DRBG
5//! (`ic-drbg`), which is the construction FIPS 140-3 expects. This module only
6//! has to deliver full-entropy bytes and fail loudly when it cannot.
7//!
8//! Backends, chosen at compile time, with no third-party dependencies:
9//!
10//! | target | mechanism |
11//! |---|---|
12//! | Windows | `BCryptGenRandom` with the system-preferred RNG |
13//! | Unix | `/dev/urandom` |
14//! | other / `no_std` | [`ErrorKind::EntropyFailure`][crate::ErrorKind::EntropyFailure] |
15//!
16//! On a platform with no backend, supply your own entropy through
17//! [`Drbg::instantiate`][crate::traits::Drbg::instantiate].
18
19use crate::{traits::RandomSource, Result};
20
21/// The system entropy source.
22///
23/// ```no_run
24/// use ic_core::{entropy::OsEntropy, traits::RandomSource};
25/// let mut seed = [0u8; 48];
26/// OsEntropy.fill(&mut seed).expect("OS entropy unavailable");
27/// ```
28#[derive(Debug, Clone, Copy, Default)]
29pub struct OsEntropy;
30
31impl RandomSource for OsEntropy {
32    fn fill(&mut self, out: &mut [u8]) -> Result<()> {
33        fill_impl(out)
34    }
35}
36
37/// Fill `out` with bytes from the operating system entropy source.
38pub fn fill(out: &mut [u8]) -> Result<()> {
39    fill_impl(out)
40}
41
42#[cfg(all(feature = "std", windows))]
43fn fill_impl(out: &mut [u8]) -> Result<()> {
44    // CNG's system-preferred RNG. This is the same DRBG the Windows FIPS
45    // validated module exposes, reached without a handle via
46    // `BCRYPT_USE_SYSTEM_PREFERRED_RNG`.
47    const BCRYPT_USE_SYSTEM_PREFERRED_RNG: u32 = 0x0000_0002;
48
49    #[link(name = "bcrypt")]
50    extern "system" {
51        fn BCryptGenRandom(
52            hAlgorithm: *mut core::ffi::c_void,
53            pbBuffer: *mut u8,
54            cbBuffer: u32,
55            dwFlags: u32,
56        ) -> i32;
57    }
58
59    // `cbBuffer` is a u32, so large requests are issued in chunks.
60    for chunk in out.chunks_mut(u32::MAX as usize) {
61        if chunk.is_empty() {
62            continue;
63        }
64        // SAFETY: `chunk` is a valid, uniquely-borrowed buffer of `len` bytes,
65        // and a null algorithm handle is required by the flag we pass.
66        let status = unsafe {
67            BCryptGenRandom(
68                core::ptr::null_mut(),
69                chunk.as_mut_ptr(),
70                chunk.len() as u32,
71                BCRYPT_USE_SYSTEM_PREFERRED_RNG,
72            )
73        };
74        if status != 0 {
75            return Err(crate::err!(EntropyFailure, "BCryptGenRandom"));
76        }
77    }
78    Ok(())
79}
80
81#[cfg(all(feature = "std", unix))]
82fn fill_impl(out: &mut [u8]) -> Result<()> {
83    use std::io::Read;
84
85    if out.is_empty() {
86        return Ok(());
87    }
88    let mut f = std::fs::File::open("/dev/urandom")
89        .map_err(|_| crate::err!(EntropyFailure, "/dev/urandom open"))?;
90    f.read_exact(out)
91        .map_err(|_| crate::err!(EntropyFailure, "/dev/urandom read"))?;
92    Ok(())
93}
94
95#[cfg(not(all(feature = "std", any(windows, unix))))]
96fn fill_impl(_out: &mut [u8]) -> Result<()> {
97    Err(crate::err!(
98        EntropyFailure,
99        "no OS entropy backend for this target; seed the DRBG manually"
100    ))
101}
102
103#[cfg(all(test, feature = "std", any(windows, unix)))]
104mod tests {
105    use super::*;
106
107    #[test]
108    fn produces_distinct_nonzero_output() {
109        let mut a = [0u8; 32];
110        let mut b = [0u8; 32];
111        fill(&mut a).unwrap();
112        fill(&mut b).unwrap();
113        assert_ne!(a, [0u8; 32], "entropy source returned all zeroes");
114        assert_ne!(a, b, "entropy source repeated itself");
115    }
116
117    #[test]
118    fn empty_request_succeeds() {
119        fill(&mut []).unwrap();
120    }
121}