⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/crypto/
random.rs

1//! Secure Random Number Generation
2//!
3//! Provides cryptographically secure random number generation using a
4//! ChaCha20-based CSPRNG seeded from hardware entropy sources.
5#![allow(dead_code)]
6//! ## Design
7//!
8//! The CSPRNG uses ChaCha20 in counter mode as the core PRG:
9//! - 256-bit key derived from entropy pool
10//! - 96-bit nonce (fixed per reseed)
11//! - 32-bit counter incremented for each 64-byte block
12//!
13//! Entropy sources (via `arch::entropy` abstraction):
14//! - Hardware RNG (RDRAND on x86_64, if available)
15//! - Timer-jitter entropy (architecture-independent fallback)
16//!
17//! Reseeding occurs every RESEED_INTERVAL calls to mix fresh entropy.
18
19use alloc::{vec, vec::Vec};
20
21use spin::Mutex;
22
23use super::CryptoResult;
24use crate::sync::once_lock::OnceLock;
25
26/// Number of generate calls between automatic reseeds
27const RESEED_INTERVAL: u64 = 4096;
28
29/// Secure random number generator backed by ChaCha20
30pub(crate) struct SecureRandom {
31    state: Mutex<RandomState>,
32}
33
34struct RandomState {
35    /// ChaCha20 key (256 bits)
36    key: [u8; 32],
37    /// ChaCha20 nonce (96 bits)
38    nonce: [u8; 12],
39    /// Block counter for ChaCha20
40    counter: u32,
41    /// Buffered keystream bytes (from the current ChaCha20 block)
42    buffer: [u8; 64],
43    /// Index into the buffer for the next byte to use
44    buffer_pos: usize,
45    /// Number of generate calls since last reseed
46    reseed_counter: u64,
47    /// Entropy pool for accumulating hardware entropy
48    entropy_pool: [u8; 32],
49    /// Entropy pool write index
50    pool_idx: usize,
51}
52
53impl SecureRandom {
54    /// Create new secure random number generator
55    pub(crate) fn new() -> CryptoResult<Self> {
56        // Initialize with hardware RNG or timer
57        let seed = Self::get_entropy()?;
58
59        // Use SHA-256 to derive initial key and nonce from entropy
60        let key_material = super::hash::sha256(&seed);
61        let nonce_input = {
62            let mut input = [0u8; 33];
63            input[..32].copy_from_slice(&seed);
64            input[32] = 0x01; // domain separation
65            input
66        };
67        let nonce_material = super::hash::sha256(&nonce_input);
68
69        let mut key = [0u8; 32];
70        key.copy_from_slice(key_material.as_bytes());
71
72        let mut nonce = [0u8; 12];
73        nonce.copy_from_slice(&nonce_material.as_bytes()[..12]);
74
75        // Generate first buffer block
76        let buffer = Self::chacha20_block_static(&key, &nonce, 0);
77
78        Ok(Self {
79            state: Mutex::new(RandomState {
80                key,
81                nonce,
82                counter: 1, // First block used for initial buffer
83                buffer,
84                buffer_pos: 0,
85                reseed_counter: 0,
86                entropy_pool: [0u8; 32],
87                pool_idx: 0,
88            }),
89        })
90    }
91
92    /// Generate random bytes
93    pub(crate) fn fill_bytes(&self, dest: &mut [u8]) -> CryptoResult<()> {
94        let mut state = self.state.lock();
95
96        // Check if reseed is needed
97        state.reseed_counter += 1;
98        if state.reseed_counter >= RESEED_INTERVAL {
99            Self::reseed_state(&mut state);
100        }
101
102        // Use index-based loop instead of iter_mut() to avoid AArch64 LLVM hang
103        let mut i = 0;
104        while i < dest.len() {
105            if state.buffer_pos >= 64 {
106                // Generate next block
107                state.buffer = Self::chacha20_block_static(&state.key, &state.nonce, state.counter);
108                state.counter = state.counter.wrapping_add(1);
109                state.buffer_pos = 0;
110
111                // Every 256 blocks, mix entropy into the state
112                if state.counter.is_multiple_of(256) {
113                    Self::mix_entropy_into_key(&mut state);
114                }
115            }
116            dest[i] = state.buffer[state.buffer_pos];
117            state.buffer_pos += 1;
118            i += 1;
119        }
120
121        Ok(())
122    }
123
124    /// Generate random u64
125    pub(crate) fn next_u64(&self) -> u64 {
126        let mut bytes = [0u8; 8];
127        if let Err(_e) = self.fill_bytes(&mut bytes) {
128            crate::println!("[CSPRNG] Warning: fill_bytes failed in next_u64: {:?}", _e);
129        }
130        u64::from_le_bytes(bytes)
131    }
132
133    /// Generate random u32
134    pub(crate) fn next_u32(&self) -> u32 {
135        let mut bytes = [0u8; 4];
136        if let Err(_e) = self.fill_bytes(&mut bytes) {
137            crate::println!("[CSPRNG] Warning: fill_bytes failed in next_u32: {:?}", _e);
138        }
139        u32::from_le_bytes(bytes)
140    }
141
142    // ========================================================================
143    // Private methods
144    // ========================================================================
145
146    /// ChaCha20 quarter round
147    #[inline]
148    fn qr(state: &mut [u32; 16], a: usize, b: usize, c: usize, d: usize) {
149        state[a] = state[a].wrapping_add(state[b]);
150        state[d] ^= state[a];
151        state[d] = state[d].rotate_left(16);
152
153        state[c] = state[c].wrapping_add(state[d]);
154        state[b] ^= state[c];
155        state[b] = state[b].rotate_left(12);
156
157        state[a] = state[a].wrapping_add(state[b]);
158        state[d] ^= state[a];
159        state[d] = state[d].rotate_left(8);
160
161        state[c] = state[c].wrapping_add(state[d]);
162        state[b] ^= state[c];
163        state[b] = state[b].rotate_left(7);
164    }
165
166    /// ChaCha20 block function (static method for use without &self)
167    fn chacha20_block_static(key: &[u8; 32], nonce: &[u8; 12], counter: u32) -> [u8; 64] {
168        let mut state: [u32; 16] = [
169            0x61707865,
170            0x3320646e,
171            0x79622d32,
172            0x6b206574, // "expand 32-byte k"
173            u32::from_le_bytes([key[0], key[1], key[2], key[3]]),
174            u32::from_le_bytes([key[4], key[5], key[6], key[7]]),
175            u32::from_le_bytes([key[8], key[9], key[10], key[11]]),
176            u32::from_le_bytes([key[12], key[13], key[14], key[15]]),
177            u32::from_le_bytes([key[16], key[17], key[18], key[19]]),
178            u32::from_le_bytes([key[20], key[21], key[22], key[23]]),
179            u32::from_le_bytes([key[24], key[25], key[26], key[27]]),
180            u32::from_le_bytes([key[28], key[29], key[30], key[31]]),
181            counter,
182            u32::from_le_bytes([nonce[0], nonce[1], nonce[2], nonce[3]]),
183            u32::from_le_bytes([nonce[4], nonce[5], nonce[6], nonce[7]]),
184            u32::from_le_bytes([nonce[8], nonce[9], nonce[10], nonce[11]]),
185        ];
186
187        let initial_state = state;
188
189        // 20 rounds (10 double rounds) per RFC 8439
190        let mut round = 0;
191        while round < 10 {
192            // Column rounds
193            Self::qr(&mut state, 0, 4, 8, 12);
194            Self::qr(&mut state, 1, 5, 9, 13);
195            Self::qr(&mut state, 2, 6, 10, 14);
196            Self::qr(&mut state, 3, 7, 11, 15);
197            // Diagonal rounds
198            Self::qr(&mut state, 0, 5, 10, 15);
199            Self::qr(&mut state, 1, 6, 11, 12);
200            Self::qr(&mut state, 2, 7, 8, 13);
201            Self::qr(&mut state, 3, 4, 9, 14);
202            round += 1;
203        }
204
205        // Add initial state
206        let mut i = 0;
207        while i < 16 {
208            state[i] = state[i].wrapping_add(initial_state[i]);
209            i += 1;
210        }
211
212        // Serialize to bytes (little-endian)
213        let mut output = [0u8; 64];
214        i = 0;
215        while i < 16 {
216            let bytes = state[i].to_le_bytes();
217            output[i * 4] = bytes[0];
218            output[i * 4 + 1] = bytes[1];
219            output[i * 4 + 2] = bytes[2];
220            output[i * 4 + 3] = bytes[3];
221            i += 1;
222        }
223        output
224    }
225
226    /// Collect entropy from hardware sources.
227    ///
228    /// Uses the `arch::entropy` abstraction to avoid architecture-specific code
229    /// in this module. Tries hardware RNG first (RDRAND on x86_64), then falls
230    /// back to timer-jitter entropy.
231    fn get_entropy() -> CryptoResult<[u8; 32]> {
232        use crate::arch::entropy;
233
234        let mut result = [0u8; 32];
235
236        // Try hardware RNG first (only succeeds on x86_64 with RDRAND)
237        if entropy::try_hardware_rng(&mut result) {
238            return Ok(result);
239        }
240
241        // Fall back to timer-jitter entropy (works on all architectures)
242        entropy::collect_timer_entropy(&mut result);
243        Ok(result)
244    }
245
246    /// Reseed the CSPRNG state with fresh entropy
247    fn reseed_state(state: &mut RandomState) {
248        use crate::arch::entropy;
249
250        state.reseed_counter = 0;
251
252        // Collect fresh entropy using the arch abstraction
253        let mut fresh_entropy = [0u8; 32];
254        if !entropy::try_hardware_rng(&mut fresh_entropy) {
255            entropy::collect_timer_entropy(&mut fresh_entropy);
256        }
257
258        // Mix fresh entropy with current key using SHA-256
259        let mut mix_input = [0u8; 64];
260        mix_input[..32].copy_from_slice(&state.key);
261        mix_input[32..].copy_from_slice(&fresh_entropy);
262        let new_key = super::hash::sha256(&mix_input);
263        state.key.copy_from_slice(new_key.as_bytes());
264
265        // Derive new nonce from counter and fresh entropy
266        let mut nonce_input = [0u8; 44];
267        nonce_input[..32].copy_from_slice(&fresh_entropy);
268        nonce_input[32..36].copy_from_slice(&state.counter.to_le_bytes());
269        nonce_input[36..44].copy_from_slice(&state.reseed_counter.to_le_bytes());
270        let nonce_hash = super::hash::sha256(&nonce_input);
271        state.nonce.copy_from_slice(&nonce_hash.as_bytes()[..12]);
272
273        // Reset counter and buffer
274        state.counter = 0;
275        state.buffer = Self::chacha20_block_static(&state.key, &state.nonce, 0);
276        state.counter = 1;
277        state.buffer_pos = 0;
278    }
279
280    /// Mix accumulated entropy pool into the key
281    fn mix_entropy_into_key(state: &mut RandomState) {
282        use crate::arch::entropy;
283
284        // Collect a fresh entropy sample into the pool
285        let sample = entropy::read_timestamp();
286
287        // Fold sample into entropy pool
288        let sample_bytes = sample.to_le_bytes();
289        let mut j = 0;
290        while j < 8 {
291            state.entropy_pool[(state.pool_idx + j) % 32] ^= sample_bytes[j];
292            j += 1;
293        }
294        state.pool_idx = (state.pool_idx + 8) % 32;
295
296        // Periodically mix pool into key (every 1024 blocks)
297        if state.counter.is_multiple_of(1024) {
298            let mut mix = [0u8; 64];
299            mix[..32].copy_from_slice(&state.key);
300            mix[32..].copy_from_slice(&state.entropy_pool);
301            let new_key = super::hash::sha256(&mix);
302            state.key.copy_from_slice(new_key.as_bytes());
303        }
304    }
305}
306
307impl Default for SecureRandom {
308    fn default() -> Self {
309        Self::new().expect("Failed to initialize SecureRandom")
310    }
311}
312
313/// Global secure random number generator
314static RNG_STORAGE: OnceLock<SecureRandom> = OnceLock::new();
315
316/// Initialize random number generator
317pub(crate) fn init() -> CryptoResult<()> {
318    let rng = SecureRandom::new()?;
319    let _ = RNG_STORAGE.set(rng);
320    Ok(())
321}
322
323/// Get global random number generator
324pub(crate) fn get_random() -> &'static SecureRandom {
325    RNG_STORAGE.get_or_init(|| SecureRandom::new().expect("Failed to create RNG"))
326}
327
328/// Generate random bytes (convenience function)
329pub(crate) fn random_bytes(count: usize) -> Vec<u8> {
330    let mut bytes = vec![0u8; count];
331
332    let rng = get_random();
333    if let Err(_e) = rng.fill_bytes(&mut bytes) {
334        crate::println!(
335            "[CSPRNG] Warning: fill_bytes failed in random_bytes: {:?}",
336            _e
337        );
338    }
339
340    bytes
341}
342
343#[cfg(test)]
344mod tests {
345    use super::*;
346
347    #[test]
348    fn test_random_generation() {
349        let rng = SecureRandom::new().unwrap();
350        let mut bytes1 = [0u8; 16];
351        let mut bytes2 = [0u8; 16];
352
353        rng.fill_bytes(&mut bytes1).unwrap();
354        rng.fill_bytes(&mut bytes2).unwrap();
355
356        // Random bytes should be different
357        assert_ne!(bytes1, bytes2);
358    }
359}