nist_rand/lib.rs
1//! # nist-rand — SP 800-90A/B/C CSPRNG Implementation
2//!
3//! > **⚠️ DISCLAIMER:** This code is **NOT FIPS Verified** and has **NOT** been
4//! > officially audited by a NIST lab. Furthermore, this code does not receive
5//! > any formal security audits. It implements the best practices and algorithms
6//! > described by NIST SP 800-90A/B/C, but does not claim or provide official
7//! > compliance or certification. Use at your own risk.
8//!
9//! A high-assurance, production-ready Cryptographically Secure Pseudorandom Number
10//! Generator (CSPRNG) that implements the architectural design and algorithms
11//! described in **NIST SP 800-90A/B/C**.
12//!
13//! ## Architecture
14//!
15//! ```text
16//! ┌───────────────────────────────────────────────────────────────┐
17//! │ SP 800-90C Blender │
18//! │ ┌──────────────┐ ┌────────────────┐ ┌──────────────────┐ │
19//! │ │ OS RNG │ │ CPU Jitter │ │ System State │ │
20//! │ │ /dev/urandom │ │ (advance feat) │ │ /proc, TSC, ASLR │ │
21//! │ │ + 90B tests │ │ Von Neumann │ │ (advance feat) │ │
22//! │ └──────┬───────┘ └───────┬────────┘ └────────┬─────────┘ │
23//! │ └──────────────────┴────────────────────┘ │
24//! │ │ SHA3-512 │
25//! └────────────────────────────┼──────────────────────────────────┘
26//! │ 512-bit conditioned seed
27//! ┌────────────────────────────▼──────────────────────────────────┐
28//! │ Hash_DRBG (SP 800-90A § 10.1.1) │
29//! │ SHA3-512 (FIPS 202) │
30//! │ reseed every 10 000 requests │
31//! └────────────────────────────┬──────────────────────────────────┘
32//! │
33//! fill() / NistRng
34//! ```
35//!
36//! ## SP 800-90 Design Principles
37//!
38//! | Requirement | Standard | Implementation |
39//! |-------------|----------|----------------|
40//! | Entropy source health tests | SP 800-90B § 4.4 | `rng::HealthTests` (RCT + APT) |
41//! | Multi-source entropy blending | SP 800-90C § 4.1 | `entropy::get_blended_entropy()` with SHA3-512 |
42//! | Approved DRBG mechanism | SP 800-90A § 10.1.1 | `drbg::HashDrbg` |
43//! | Approved hash function | FIPS 202 | SHA3-512 |
44//! | Reseed interval enforcement | SP 800-90A Table 2 | Panic-on-overrun after 10 000 calls |
45//! | Fail-safe on entropy failure | FIPS 140-3 § 9.2 (Aligns with) | `panic!` on health-test or reseed failure |
46//!
47//! ## Crate Features
48//!
49//! | Feature | Default | Description |
50//! |---------|---------|-------------|
51//! | `rand_core` | ✓ | Exposes [`NistRng`] implementing `rand_core` 0.10 traits |
52//! | `advance` | ✓ | Adds CPU jitter, System state, and Hardware RNG (RDSEED/RDRAND) + extended 90B health tests |
53//! | `build_separator` | ✓ | Embeds a random build-time salt (via `build.rs`) |
54//! | `zeroize` | ✓ | Derives [`zeroize::ZeroizeOnDrop`] on `HashDrbg`; wraps all sensitive buffers in [`zeroize::Zeroizing`] so keying material is erased even on panic |
55//! | `exp_simd_rng` | ✗ | Experimental: AVX-256 thermal jitter (implies `advance`) |
56//! | `exp_network_rng` | ✗ | Experimental: UDP network-latency jitter (implies `advance`) |
57
58#![cfg_attr(docsrs, feature(doc_cfg))]
59#![warn(missing_debug_implementations, missing_docs, rust_2018_idioms)]
60#![deny(clippy::undocumented_unsafe_blocks)]
61
62/// Hash_DRBG (SP 800-90A § 10.1.1) backed by SHA3-512.
63pub mod drbg;
64/// Entropy sources and SP 800-90B continuous health tests.
65pub mod entropy;
66/// OS RNG reader and `HealthTests` state machine.
67pub mod rng;
68
69use drbg::HashDrbg;
70use std::cell::RefCell;
71
72// ────────────────────────────────────────────────────────────────────────────────
73// Thread-local DRBG instance
74// ────────────────────────────────────────────────────────────────────────────────
75
76thread_local! {
77 /// Per-thread `Hash_DRBG` instance, seeded lazily on first access.
78 ///
79 /// Using a thread-local avoids all locking overhead and data-race hazards.
80 /// Each thread independently reseeds from the blended entropy pipeline;
81 /// threads therefore contribute independent randomness to their outputs.
82 static DRBG: RefCell<HashDrbg> = RefCell::new({
83 let seed = entropy::get_blended_entropy();
84 HashDrbg::instantiate(&seed)
85 });
86}
87
88// ────────────────────────────────────────────────────────────────────────────────
89// Public API
90// ────────────────────────────────────────────────────────────────────────────────
91
92/// Fills `dest` with cryptographically secure random bytes.
93///
94/// Bytes are produced by the thread-local SP 800-90A `Hash_DRBG` (SHA3-512).
95/// When the DRBG's reseed counter is exhausted, fresh SP 800-90C-conditioned
96/// entropy is gathered automatically before retrying.
97///
98/// # FIPS 140-3 Fail-Safe
99///
100/// This function **panics** if:
101/// - The OS entropy source fails its SP 800-90B health tests, **or**
102/// - Generation still fails after an automatic reseed (should never happen in a
103/// correctly functioning environment).
104///
105/// Panicking is the correct FIPS 140-3 response to an entropy-source failure:
106/// the module must enter an error state rather than produce predictable output.
107///
108/// # Example
109///
110/// ```rust
111/// let mut key = [0u8; 32];
112/// nist_rand::fill(&mut key);
113/// assert!(key.iter().any(|&b| b != 0));
114/// ```
115pub fn fill(dest: &mut [u8]) {
116 DRBG.with(|cell| {
117 let mut drbg = cell.borrow_mut();
118
119 if drbg.generate(dest) {
120 return;
121 }
122
123 // Reseed counter exhausted — gather fresh entropy and retry.
124 let seed = entropy::get_blended_entropy();
125 drbg.reseed(&seed);
126
127 dest.fill(0); // Prevent leaking any pre-existing caller stack data
128
129 if !drbg.generate(dest) {
130 panic!(
131 "nist-rand: DRBG failed to generate output after automatic reseed. \
132 This indicates a critical entropy source failure."
133 );
134 }
135 });
136}
137
138/// Forces an immediate reseed of the thread-local DRBG using fresh,
139/// SP 800-90C-conditioned entropy.
140///
141/// This provides Forward Secrecy on demand: an attacker who compromises
142/// the system state *after* this call cannot determine any outputs
143/// generated *before* this call.
144#[must_use = "this function only has side effects on the internal state"]
145pub fn reseed() {
146 DRBG.with(|cell| {
147 let mut drbg = cell.borrow_mut();
148 let seed = entropy::get_blended_entropy();
149 drbg.reseed(&seed);
150 });
151}
152
153/// Fills `dest` with cryptographically secure random bytes, with guaranteed
154/// **Prediction Resistance** (SP 800-90A § 8.7.2).
155///
156/// This function gathers fresh physical entropy from the underlying OS/Jitter
157/// sources and forces an immediate reseed of the thread-local DRBG **before**
158/// generating the requested bytes.
159///
160/// Use this for ultra-sensitive material (e.g., long-term root keys or CA
161/// private keys) where you want absolute assurance that the output is tied
162/// to fresh physical entropy gathered at the exact moment of the request.
163///
164/// # Performance Warning
165/// This is significantly slower than [`fill`] because it invokes the underlying
166/// OS RNG and Jitter sources on every call. For most cryptographic purposes,
167/// [`fill`] is completely secure and vastly faster.
168pub fn fill_prediction_resistant(dest: &mut [u8]) {
169 DRBG.with(|cell| {
170 let mut drbg = cell.borrow_mut();
171
172 // Force fresh entropy gathering and reseed BEFORE generation
173 let seed = entropy::get_blended_entropy();
174 drbg.reseed(&seed);
175
176 if !drbg.generate(dest) {
177 panic!(
178 "nist-rand: DRBG failed to generate output with Prediction Resistance. \
179 This indicates a critical entropy source failure."
180 );
181 }
182 });
183}
184
185// ────────────────────────────────────────────────────────────────────────────────
186// Optional rand_core 0.10 integration
187// ────────────────────────────────────────────────────────────────────────────────
188
189#[cfg(feature = "rand_core")]
190#[cfg_attr(docsrs, doc(cfg(feature = "rand_core")))]
191pub use rand_core_impl::{NistRng, NistRngPredictionResistant};
192
193#[cfg(feature = "rand_core")]
194mod rand_core_impl {
195 use super::fill;
196 use core::convert::Infallible;
197 use rand_core::{TryCryptoRng, TryRng};
198
199 /// A `rand_core` 0.10-compatible wrapper around the nist-rand pipeline.
200 ///
201 /// Implements [`TryRng`]`<Error = `[`Infallible`]`>`, which automatically
202 /// grants the blanket [`rand_core::Rng`] implementation, and
203 /// [`TryCryptoRng`], which automatically grants the blanket
204 /// [`rand_core::CryptoRng`] implementation.
205 ///
206 /// # Usage
207 ///
208 /// ```rust,ignore
209 /// use nist_rand::NistRng;
210 /// use rand_core::RngExt; // or rand::RngExt
211 ///
212 /// let mut rng = NistRng;
213 /// let key: [u8; 32] = rng.random();
214 /// ```
215 ///
216 /// # Thread Safety
217 ///
218 /// `NistRng` is a zero-sized type. It is **not** `Sync` — each thread
219 /// should hold its own instance. The underlying DRBG state is thread-local.
220 #[derive(Debug, Default)]
221 pub struct NistRng;
222
223 impl TryRng for NistRng {
224 type Error = Infallible;
225
226 #[inline]
227 fn try_next_u32(&mut self) -> Result<u32, Infallible> {
228 let mut buf = [0u8; 4];
229 fill(&mut buf);
230 Ok(u32::from_le_bytes(buf))
231 }
232
233 #[inline]
234 fn try_next_u64(&mut self) -> Result<u64, Infallible> {
235 let mut buf = [0u8; 8];
236 fill(&mut buf);
237 Ok(u64::from_le_bytes(buf))
238 }
239
240 #[inline]
241 fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Infallible> {
242 fill(dest);
243 Ok(())
244 }
245 }
246
247 /// Marks `NistRng` as a cryptographically secure source.
248 ///
249 /// The blanket `impl<R: TryCryptoRng<Error = Infallible>> CryptoRng for R`
250 /// in `rand_core` then grants the full `CryptoRng` bound automatically.
251 impl TryCryptoRng for NistRng {}
252
253 /// A `rand_core` 0.10-compatible wrapper providing **Prediction Resistance**.
254 ///
255 /// Like [`NistRng`], but backed by [`fill_prediction_resistant`]. Every
256 /// generation request forces a fresh gather of physical entropy before
257 /// producing output.
258 #[derive(Debug, Default)]
259 pub struct NistRngPredictionResistant;
260
261 impl TryRng for NistRngPredictionResistant {
262 type Error = Infallible;
263
264 #[inline]
265 fn try_next_u32(&mut self) -> Result<u32, Infallible> {
266 let mut buf = [0u8; 4];
267 super::fill_prediction_resistant(&mut buf);
268 Ok(u32::from_le_bytes(buf))
269 }
270
271 #[inline]
272 fn try_next_u64(&mut self) -> Result<u64, Infallible> {
273 let mut buf = [0u8; 8];
274 super::fill_prediction_resistant(&mut buf);
275 Ok(u64::from_le_bytes(buf))
276 }
277
278 #[inline]
279 fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Infallible> {
280 super::fill_prediction_resistant(dest);
281 Ok(())
282 }
283 }
284
285 impl TryCryptoRng for NistRngPredictionResistant {}
286}
287
288// ────────────────────────────────────────────────────────────────────────────────
289// Tests
290// ────────────────────────────────────────────────────────────────────────────────
291
292#[cfg(test)]
293mod tests {
294 use super::*;
295 use std::collections::HashSet;
296
297 #[test]
298 fn fill_basic_sanity() {
299 let mut buf = [0u8; 128];
300 fill(&mut buf);
301 assert!(buf.iter().any(|&b| b != 0), "output should not be all-zero");
302 }
303
304 #[test]
305 fn fill_uniqueness() {
306 let mut seen = HashSet::new();
307 for _ in 0..10 {
308 let mut buf = [0u8; 32];
309 fill(&mut buf);
310 assert!(seen.insert(buf.to_vec()), "CRITICAL: duplicate DRBG output detected");
311 }
312 }
313
314 #[test]
315 fn fill_multi_block() {
316 // 4 KiB is much larger than a single SHA3-512 block (64 bytes).
317 let mut buf = [0u8; 4096];
318 fill(&mut buf);
319 assert!(buf.iter().any(|&b| b != 0));
320 assert_ne!(&buf[..64], &buf[64..128], "consecutive blocks must differ");
321 }
322}