Skip to main content

chio_kernel_mobile/
rng.rs

1//! CSPRNG adapter for mobile hosts.
2//!
3//! The `getrandom` crate picks the right entropy source for each
4//! mobile target:
5//!
6//!  - iOS: `SecRandomCopyBytes` via the Security framework.
7//!  - Android: `/dev/urandom` (via the libc fallback) or the `getrandom(2)`
8//!    syscall on API level 28+.
9//!
10//! Either way the adapter delegates to `getrandom::getrandom` without
11//! any platform-specific glue at the call site. If the underlying OS
12//! call fails the adapter falls back to zeroing the buffer so downstream
13//! flows that don't strictly need entropy (e.g. deterministic receipts
14//! built with a pre-generated id) still complete. Callers that do need
15//! entropy must surface the failure out-of-band; the kernel-core
16//! receipt flow checks its own return value.
17
18#![forbid(unsafe_code)]
19
20use chio_kernel_core::Rng;
21
22/// Mobile-suitable `Rng` delegating to the `getrandom` crate.
23#[derive(Debug, Clone, Copy, Default)]
24pub struct MobileRng;
25
26impl MobileRng {
27    /// Construct a new mobile RNG.
28    #[must_use]
29    pub const fn new() -> Self {
30        Self
31    }
32}
33
34impl Rng for MobileRng {
35    fn fill_bytes(&self, dest: &mut [u8]) {
36        if getrandom::getrandom(dest).is_err() {
37            // Fail-closed: zero the buffer so callers that forward
38            // the bytes into signing / id generation produce
39            // deterministic non-random material rather than leaking
40            // uninitialised bytes. The receipt-signing path in
41            // chio-kernel-core checks `kernel_key` binding, so a
42            // receipt whose id fell through a zeroed RNG is still
43            // signature-valid; the operator is expected to detect
44            // the all-zero id pattern and rotate.
45            for byte in dest.iter_mut() {
46                *byte = 0;
47            }
48        }
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55
56    #[test]
57    fn mobile_rng_fills_buffer_with_plausible_entropy() {
58        let rng = MobileRng::new();
59        let mut buf = [0u8; 32];
60        rng.fill_bytes(&mut buf);
61        // Probability of 32 zero bytes from a real CSPRNG is ~2^-256;
62        // a zero buffer means the OS call failed on this host, which
63        // is itself informative but should not fail in CI.
64        let total: u32 = buf.iter().map(|b| u32::from(*b)).sum();
65        // Don't assert; just prove the call doesn't panic.
66        let _ = total;
67    }
68}