nist-rand 0.1.0

A high-assurance, FIPS 140-3 and NIST SP 800-90A/B/C compliant Cryptographically Secure Pseudorandom Number Generator (CSPRNG) with multi-source entropy blending.
Documentation
//! # nist-rand — SP 800-90A/B/C CSPRNG Implementation
//!
//! > **⚠️ DISCLAIMER:** This code is **NOT FIPS Verified** and has **NOT** been 
//! > officially audited by a NIST lab. Furthermore, this code does not receive 
//! > any formal security audits. It implements the best practices and algorithms 
//! > described by NIST SP 800-90A/B/C, but does not claim or provide official 
//! > compliance or certification. Use at your own risk.
//!
//! A high-assurance, production-ready Cryptographically Secure Pseudorandom Number
//! Generator (CSPRNG) that implements the architectural design and algorithms
//! described in **NIST SP 800-90A/B/C**.
//!
//! ## Architecture
//!
//! ```text
//!  ┌───────────────────────────────────────────────────────────────┐
//!  │                    SP 800-90C Blender                         │
//!  │  ┌──────────────┐  ┌────────────────┐  ┌──────────────────┐   │
//!  │  │ OS RNG       │  │ CPU Jitter     │  │ System State     │   │
//!  │  │ /dev/urandom │  │ (advance feat) │  │ /proc, TSC, ASLR │   │
//!  │  │ + 90B tests  │  │ Von Neumann    │  │ (advance feat)   │   │
//!  │  └──────┬───────┘  └───────┬────────┘  └────────┬─────────┘   │
//!  │         └──────────────────┴────────────────────┘             │
//!  │                            │ SHA3-512                         │
//!  └────────────────────────────┼──────────────────────────────────┘
//!                               │ 512-bit conditioned seed
//!  ┌────────────────────────────▼──────────────────────────────────┐
//!  │             Hash_DRBG (SP 800-90A § 10.1.1)                   │
//!  │                    SHA3-512 (FIPS 202)                        │
//!  │                reseed every 10 000 requests                   │
//!  └────────────────────────────┬──────────────────────────────────┘
//!//!                         fill() / NistRng
//! ```
//!
//! ## SP 800-90 Design Principles
//!
//! | Requirement | Standard | Implementation |
//! |-------------|----------|----------------|
//! | Entropy source health tests | SP 800-90B § 4.4 | `rng::HealthTests` (RCT + APT) |
//! | Multi-source entropy blending | SP 800-90C § 4.1 | `entropy::get_blended_entropy()` with SHA3-512 |
//! | Approved DRBG mechanism | SP 800-90A § 10.1.1 | `drbg::HashDrbg` |
//! | Approved hash function | FIPS 202 | SHA3-512 |
//! | Reseed interval enforcement | SP 800-90A Table 2 | Panic-on-overrun after 10 000 calls |
//! | Fail-safe on entropy failure | FIPS 140-3 § 9.2 (Aligns with) | `panic!` on health-test or reseed failure |
//!
//! ## Crate Features
//!
//! | Feature | Default | Description |
//! |---------|---------|-------------|
//! | `rand_core` | ✓ | Exposes [`NistRng`] implementing `rand_core` 0.10 traits |
//! | `advance` | ✓ | Adds CPU jitter, System state, and Hardware RNG (RDSEED/RDRAND) + extended 90B health tests |
//! | `build_separator` | ✓ | Embeds a random build-time salt (via `build.rs`) |
//! | `zeroize` | ✓ | Derives [`zeroize::ZeroizeOnDrop`] on `HashDrbg`; wraps all sensitive buffers in [`zeroize::Zeroizing`] so keying material is erased even on panic |
//! | `exp_simd_rng` | ✗ | Experimental: AVX-256 thermal jitter (implies `advance`) |
//! | `exp_network_rng` | ✗ | Experimental: UDP network-latency jitter (implies `advance`) |

#![cfg_attr(docsrs, feature(doc_cfg))]
#![warn(missing_debug_implementations, missing_docs, rust_2018_idioms)]
#![deny(clippy::undocumented_unsafe_blocks)]

/// Hash_DRBG (SP 800-90A § 10.1.1) backed by SHA3-512.
pub mod drbg;
/// Entropy sources and SP 800-90B continuous health tests.
pub mod entropy;
/// OS RNG reader and `HealthTests` state machine.
pub mod rng;

use drbg::HashDrbg;
use std::cell::RefCell;

// ────────────────────────────────────────────────────────────────────────────────
// Thread-local DRBG instance
// ────────────────────────────────────────────────────────────────────────────────

thread_local! {
    /// Per-thread `Hash_DRBG` instance, seeded lazily on first access.
    ///
    /// Using a thread-local avoids all locking overhead and data-race hazards.
    /// Each thread independently reseeds from the blended entropy pipeline;
    /// threads therefore contribute independent randomness to their outputs.
    static DRBG: RefCell<HashDrbg> = RefCell::new({
        let seed = entropy::get_blended_entropy();
        HashDrbg::instantiate(&seed)
    });
}

// ────────────────────────────────────────────────────────────────────────────────
// Public API
// ────────────────────────────────────────────────────────────────────────────────

/// Fills `dest` with cryptographically secure random bytes.
///
/// Bytes are produced by the thread-local SP 800-90A `Hash_DRBG` (SHA3-512).
/// When the DRBG's reseed counter is exhausted, fresh SP 800-90C-conditioned
/// entropy is gathered automatically before retrying.
///
/// # FIPS 140-3 Fail-Safe
///
/// This function **panics** if:
/// - The OS entropy source fails its SP 800-90B health tests, **or**
/// - Generation still fails after an automatic reseed (should never happen in a
///   correctly functioning environment).
///
/// Panicking is the correct FIPS 140-3 response to an entropy-source failure:
/// the module must enter an error state rather than produce predictable output.
///
/// # Example
///
/// ```rust
/// let mut key = [0u8; 32];
/// nist_rand::fill(&mut key);
/// assert!(key.iter().any(|&b| b != 0));
/// ```
pub fn fill(dest: &mut [u8]) {
    DRBG.with(|cell| {
        let mut drbg = cell.borrow_mut();

        if drbg.generate(dest) {
            return;
        }

        // Reseed counter exhausted — gather fresh entropy and retry.
        let seed = entropy::get_blended_entropy();
        drbg.reseed(&seed);

        dest.fill(0); // Prevent leaking any pre-existing caller stack data

        if !drbg.generate(dest) {
            panic!(
                "nist-rand: DRBG failed to generate output after automatic reseed. \
                 This indicates a critical entropy source failure."
            );
        }
    });
}

/// Forces an immediate reseed of the thread-local DRBG using fresh,
/// SP 800-90C-conditioned entropy.
///
/// This provides Forward Secrecy on demand: an attacker who compromises
/// the system state *after* this call cannot determine any outputs
/// generated *before* this call.
#[must_use = "this function only has side effects on the internal state"]
pub fn reseed() {
    DRBG.with(|cell| {
        let mut drbg = cell.borrow_mut();
        let seed = entropy::get_blended_entropy();
        drbg.reseed(&seed);
    });
}

/// Fills `dest` with cryptographically secure random bytes, with guaranteed
/// **Prediction Resistance** (SP 800-90A § 8.7.2).
///
/// This function gathers fresh physical entropy from the underlying OS/Jitter
/// sources and forces an immediate reseed of the thread-local DRBG **before**
/// generating the requested bytes.
///
/// Use this for ultra-sensitive material (e.g., long-term root keys or CA
/// private keys) where you want absolute assurance that the output is tied
/// to fresh physical entropy gathered at the exact moment of the request.
///
/// # Performance Warning
/// This is significantly slower than [`fill`] because it invokes the underlying
/// OS RNG and Jitter sources on every call. For most cryptographic purposes,
/// [`fill`] is completely secure and vastly faster.
pub fn fill_prediction_resistant(dest: &mut [u8]) {
    DRBG.with(|cell| {
        let mut drbg = cell.borrow_mut();

        // Force fresh entropy gathering and reseed BEFORE generation
        let seed = entropy::get_blended_entropy();
        drbg.reseed(&seed);

        if !drbg.generate(dest) {
            panic!(
                "nist-rand: DRBG failed to generate output with Prediction Resistance. \
                 This indicates a critical entropy source failure."
            );
        }
    });
}

// ────────────────────────────────────────────────────────────────────────────────
// Optional rand_core 0.10 integration
// ────────────────────────────────────────────────────────────────────────────────

#[cfg(feature = "rand_core")]
#[cfg_attr(docsrs, doc(cfg(feature = "rand_core")))]
pub use rand_core_impl::{NistRng, NistRngPredictionResistant};

#[cfg(feature = "rand_core")]
mod rand_core_impl {
    use super::fill;
    use core::convert::Infallible;
    use rand_core::{TryCryptoRng, TryRng};

    /// A `rand_core` 0.10-compatible wrapper around the nist-rand pipeline.
    ///
    /// Implements [`TryRng`]`<Error = `[`Infallible`]`>`, which automatically
    /// grants the blanket [`rand_core::Rng`] implementation, and
    /// [`TryCryptoRng`], which automatically grants the blanket
    /// [`rand_core::CryptoRng`] implementation.
    ///
    /// # Usage
    ///
    /// ```rust,ignore
    /// use nist_rand::NistRng;
    /// use rand_core::RngExt; // or rand::RngExt
    ///
    /// let mut rng = NistRng;
    /// let key: [u8; 32] = rng.random();
    /// ```
    ///
    /// # Thread Safety
    ///
    /// `NistRng` is a zero-sized type.  It is **not** `Sync` — each thread
    /// should hold its own instance.  The underlying DRBG state is thread-local.
    #[derive(Debug, Default)]
    pub struct NistRng;

    impl TryRng for NistRng {
        type Error = Infallible;

        #[inline]
        fn try_next_u32(&mut self) -> Result<u32, Infallible> {
            let mut buf = [0u8; 4];
            fill(&mut buf);
            Ok(u32::from_le_bytes(buf))
        }

        #[inline]
        fn try_next_u64(&mut self) -> Result<u64, Infallible> {
            let mut buf = [0u8; 8];
            fill(&mut buf);
            Ok(u64::from_le_bytes(buf))
        }

        #[inline]
        fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Infallible> {
            fill(dest);
            Ok(())
        }
    }

    /// Marks `NistRng` as a cryptographically secure source.
    ///
    /// The blanket `impl<R: TryCryptoRng<Error = Infallible>> CryptoRng for R`
    /// in `rand_core` then grants the full `CryptoRng` bound automatically.
    impl TryCryptoRng for NistRng {}

    /// A `rand_core` 0.10-compatible wrapper providing **Prediction Resistance**.
    ///
    /// Like [`NistRng`], but backed by [`fill_prediction_resistant`]. Every
    /// generation request forces a fresh gather of physical entropy before
    /// producing output.
    #[derive(Debug, Default)]
    pub struct NistRngPredictionResistant;

    impl TryRng for NistRngPredictionResistant {
        type Error = Infallible;

        #[inline]
        fn try_next_u32(&mut self) -> Result<u32, Infallible> {
            let mut buf = [0u8; 4];
            super::fill_prediction_resistant(&mut buf);
            Ok(u32::from_le_bytes(buf))
        }

        #[inline]
        fn try_next_u64(&mut self) -> Result<u64, Infallible> {
            let mut buf = [0u8; 8];
            super::fill_prediction_resistant(&mut buf);
            Ok(u64::from_le_bytes(buf))
        }

        #[inline]
        fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Infallible> {
            super::fill_prediction_resistant(dest);
            Ok(())
        }
    }

    impl TryCryptoRng for NistRngPredictionResistant {}
}

// ────────────────────────────────────────────────────────────────────────────────
// Tests
// ────────────────────────────────────────────────────────────────────────────────

#[cfg(test)]
mod tests {
    use super::*;
    use std::collections::HashSet;

    #[test]
    fn fill_basic_sanity() {
        let mut buf = [0u8; 128];
        fill(&mut buf);
        assert!(buf.iter().any(|&b| b != 0), "output should not be all-zero");
    }

    #[test]
    fn fill_uniqueness() {
        let mut seen = HashSet::new();
        for _ in 0..10 {
            let mut buf = [0u8; 32];
            fill(&mut buf);
            assert!(seen.insert(buf.to_vec()), "CRITICAL: duplicate DRBG output detected");
        }
    }

    #[test]
    fn fill_multi_block() {
        // 4 KiB is much larger than a single SHA3-512 block (64 bytes).
        let mut buf = [0u8; 4096];
        fill(&mut buf);
        assert!(buf.iter().any(|&b| b != 0));
        assert_ne!(&buf[..64], &buf[64..128], "consecutive blocks must differ");
    }
}