Skip to main content

hotl_platform/entropy/
mod.rs

1//! [`Entropy`] — bytes from the OS CSPRNG, or an error.
2
3use std::io;
4
5#[cfg(unix)]
6mod unix;
7#[cfg(unix)]
8pub use unix::UnixEntropy;
9#[cfg(unix)]
10pub type ActiveEntropy = UnixEntropy;
11
12#[cfg(windows)]
13mod windows;
14#[cfg(windows)]
15pub use windows::WindowsEntropy;
16#[cfg(windows)]
17pub type ActiveEntropy = WindowsEntropy;
18
19/// Cryptographically secure bytes.
20///
21/// CONTRACT: **never a PRNG fallback.** An adapter that cannot reach the OS
22/// CSPRNG returns an error; it does not degrade to something seeded from the
23/// clock. The one consumer that matters is the session token, and a guessable
24/// session token is a session anyone can drive.
25pub trait Entropy: crate::sealed::Sealed {
26    fn fill(&self, buf: &mut [u8]) -> io::Result<()>;
27
28    fn token_bytes<const N: usize>(&self) -> io::Result<[u8; N]> {
29        let mut buf = [0u8; N];
30        self.fill(&mut buf)?;
31        Ok(buf)
32    }
33}
34
35#[cfg(test)]
36pub(crate) fn assert_entropy_contract<E: Entropy>(e: &E) {
37    // `fill` fills the whole buffer, including the tail — a partial fill that
38    // left zeros would be the quiet version of the failure this trait forbids.
39    let mut buf = [0u8; 64];
40    e.fill(&mut buf).unwrap();
41    assert!(buf.iter().any(|&b| b != 0), "fill left the buffer zeroed");
42
43    let a: [u8; 32] = e.token_bytes().unwrap();
44    let b: [u8; 32] = e.token_bytes().unwrap();
45    assert_ne!(a, b, "two draws must differ");
46
47    // A zero-length request is not an error.
48    e.fill(&mut []).unwrap();
49}
50
51#[cfg(test)]
52mod tests {
53    #[test]
54    fn active_adapter_upholds_the_contract() {
55        super::assert_entropy_contract(&crate::ENTROPY);
56    }
57}