Skip to main content

rns_crypto/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2extern crate alloc;
3
4pub mod aes128;
5pub mod aes256;
6pub mod ed25519;
7pub mod hkdf;
8pub mod hmac;
9pub mod identity;
10pub mod pkcs7;
11pub mod ratchet;
12pub mod sha256;
13pub mod sha512;
14pub mod token;
15pub mod x25519;
16
17/// Trait for random number generation.
18/// Callers provide an implementation; in `std` builds this wraps OS randomness.
19pub trait Rng {
20    fn fill_bytes(&mut self, dest: &mut [u8]);
21}
22
23/// Deterministic RNG for testing.
24pub struct FixedRng {
25    bytes: alloc::vec::Vec<u8>,
26    pos: usize,
27}
28
29impl FixedRng {
30    pub fn new(bytes: &[u8]) -> Self {
31        Self {
32            bytes: bytes.to_vec(),
33            pos: 0,
34        }
35    }
36}
37
38impl Rng for FixedRng {
39    fn fill_bytes(&mut self, dest: &mut [u8]) {
40        for b in dest.iter_mut() {
41            *b = self.bytes[self.pos % self.bytes.len()];
42            self.pos += 1;
43        }
44    }
45}
46
47/// OS-backed RNG using getrandom(2) syscall on Linux.
48#[cfg(feature = "std")]
49pub struct OsRng;
50
51#[cfg(feature = "std")]
52impl Rng for OsRng {
53    fn fill_bytes(&mut self, dest: &mut [u8]) {
54        // ESP-IDF: use hardware RNG via esp_fill_random
55        #[cfg(target_os = "espidf")]
56        {
57            unsafe {
58                esp_idf_sys::esp_fill_random(
59                    dest.as_mut_ptr() as *mut core::ffi::c_void,
60                    dest.len(),
61                );
62            }
63        }
64        #[cfg(not(target_os = "espidf"))]
65        {
66            use std::io::Read;
67            // Opened once: opening it for every draw cost an open and a close
68            // per token IV and random hash. Reads through `&File` need no lock.
69            static URANDOM: std::sync::OnceLock<std::fs::File> = std::sync::OnceLock::new();
70            let mut f = URANDOM.get_or_init(|| {
71                std::fs::File::open("/dev/urandom").expect("Failed to open /dev/urandom")
72            });
73            f.read_exact(dest)
74                .expect("Failed to read from /dev/urandom");
75        }
76    }
77}
78
79#[cfg(all(test, feature = "std", not(target_os = "espidf")))]
80mod tests {
81    use super::{OsRng, Rng};
82
83    #[test]
84    fn os_rng_fills_buffers_from_concurrent_threads() {
85        let draws: std::vec::Vec<[u8; 32]> = (0..8)
86            .map(|_| {
87                std::thread::spawn(|| {
88                    let mut out = [0u8; 32];
89                    OsRng.fill_bytes(&mut out);
90                    out
91                })
92            })
93            .collect::<std::vec::Vec<_>>()
94            .into_iter()
95            .map(|thread| thread.join().unwrap())
96            .collect();
97        for (i, a) in draws.iter().enumerate() {
98            assert_ne!(a, &[0u8; 32]);
99            for b in &draws[i + 1..] {
100                assert_ne!(a, b);
101            }
102        }
103    }
104}