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;
6#[cfg(target_arch = "x86_64")]
7mod aes256_ni;
8pub mod ed25519;
9pub mod hkdf;
10pub mod hmac;
11pub mod identity;
12pub mod pkcs7;
13pub mod ratchet;
14pub mod sha256;
15#[cfg(target_arch = "x86_64")]
16mod sha256_x2;
17pub mod sha512;
18pub mod stamp_value;
19pub mod token;
20pub mod x25519;
21
22/// Trait for random number generation.
23/// Callers provide an implementation; in `std` builds this wraps OS randomness.
24pub trait Rng {
25    fn fill_bytes(&mut self, dest: &mut [u8]);
26}
27
28/// Deterministic RNG for testing.
29pub struct FixedRng {
30    bytes: alloc::vec::Vec<u8>,
31    pos: usize,
32}
33
34impl FixedRng {
35    pub fn new(bytes: &[u8]) -> Self {
36        Self {
37            bytes: bytes.to_vec(),
38            pos: 0,
39        }
40    }
41}
42
43impl Rng for FixedRng {
44    fn fill_bytes(&mut self, dest: &mut [u8]) {
45        for b in dest.iter_mut() {
46            *b = self.bytes[self.pos % self.bytes.len()];
47            self.pos += 1;
48        }
49    }
50}
51
52/// OS-backed RNG: the kernel's generator through getrandom(3) on Linux
53/// (served without a system call where the C library and kernel support it),
54/// `/dev/urandom` elsewhere, the hardware RNG on ESP-IDF.
55#[cfg(feature = "std")]
56pub struct OsRng;
57
58#[cfg(feature = "std")]
59impl Rng for OsRng {
60    fn fill_bytes(&mut self, dest: &mut [u8]) {
61        // ESP-IDF: use hardware RNG via esp_fill_random
62        #[cfg(target_os = "espidf")]
63        {
64            unsafe {
65                esp_idf_sys::esp_fill_random(
66                    dest.as_mut_ptr() as *mut core::ffi::c_void,
67                    dest.len(),
68                );
69            }
70        }
71        #[cfg(target_os = "linux")]
72        {
73            let mut rest = dest;
74            while !rest.is_empty() {
75                // SAFETY: `rest` is valid for writes of `rest.len()` bytes.
76                let n = unsafe { libc::getrandom(rest.as_mut_ptr().cast(), rest.len(), 0) };
77                if n < 0 {
78                    let error = std::io::Error::last_os_error();
79                    if error.kind() == std::io::ErrorKind::Interrupted {
80                        continue;
81                    }
82                    panic!("getrandom failed: {error}");
83                }
84                rest = &mut rest[n as usize..];
85            }
86        }
87        #[cfg(not(any(target_os = "espidf", target_os = "linux")))]
88        {
89            use std::io::Read;
90            // Opened once: opening it for every draw cost an open and a close
91            // per token IV and random hash. Reads through `&File` need no lock.
92            static URANDOM: std::sync::OnceLock<std::fs::File> = std::sync::OnceLock::new();
93            let mut f = URANDOM.get_or_init(|| {
94                std::fs::File::open("/dev/urandom").expect("Failed to open /dev/urandom")
95            });
96            f.read_exact(dest)
97                .expect("Failed to read from /dev/urandom");
98        }
99    }
100}
101
102#[cfg(all(test, feature = "std", not(target_os = "espidf")))]
103mod tests {
104    use super::{OsRng, Rng};
105
106    #[test]
107    fn os_rng_fills_large_and_empty_buffers() {
108        let mut empty = [0u8; 0];
109        OsRng.fill_bytes(&mut empty);
110        let mut large = std::vec![0u8; 1 << 20];
111        OsRng.fill_bytes(&mut large);
112        // Every 64 KiB window holds random bytes, so no part was skipped.
113        assert!(large
114            .chunks(1 << 16)
115            .all(|chunk| chunk.iter().any(|&b| b != 0)));
116    }
117
118    #[test]
119    fn os_rng_fills_buffers_from_concurrent_threads() {
120        let draws: std::vec::Vec<[u8; 32]> = (0..8)
121            .map(|_| {
122                std::thread::spawn(|| {
123                    let mut out = [0u8; 32];
124                    OsRng.fill_bytes(&mut out);
125                    out
126                })
127            })
128            .collect::<std::vec::Vec<_>>()
129            .into_iter()
130            .map(|thread| thread.join().unwrap())
131            .collect();
132        for (i, a) in draws.iter().enumerate() {
133            assert_ne!(a, &[0u8; 32]);
134            for b in &draws[i + 1..] {
135                assert_ne!(a, b);
136            }
137        }
138    }
139}
140
141/// Set up aws-lc once, before its first use: its CPU feature detection.
142#[cfg(all(
143    any(feature = "x25519-aws-lc", feature = "ed25519-aws-lc"),
144    any(target_arch = "x86_64", target_arch = "aarch64")
145))]
146pub(crate) fn aws_lc_init() {
147    static INIT: std::sync::Once = std::sync::Once::new();
148    // SAFETY: takes no arguments; sets up aws-lc's CPU feature detection.
149    INIT.call_once(|| unsafe { aws_lc_sys::CRYPTO_library_init() });
150}