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}