Skip to main content

key_vault/decoy/
random.rs

1//! [`RandomDecoy`] — pure CSPRNG-derived decoy bytes.
2
3use alloc::borrow::Cow;
4use alloc::vec;
5use alloc::vec::Vec;
6
7use super::DecoyStrategy;
8use crate::Result;
9use crate::error::Error;
10use crate::fetcher::RawKey;
11
12/// Decoy strategy that produces uniformly-random bytes from the OS CSPRNG.
13///
14/// **Threat profile.** `RandomDecoy` is the fastest of the three built-in
15/// strategies and the easiest to reason about, but it is also the *weakest*:
16/// the byte distribution it produces is uniformly random, which is
17/// distinguishable from key material that has visible structure (DER
18/// envelopes, ASCII-armored data, PEM markers, MAC-based keys with header
19/// bytes, etc.). For maximum indistinguishability prefer
20/// [`SelfReferenceDecoy`](super::SelfReferenceDecoy).
21///
22/// Use `RandomDecoy` when:
23///
24/// - The keys you store are already uniformly random (256-bit symmetric keys
25///   from a CSPRNG, for example) — there is nothing to distinguish.
26/// - You want the lowest decoy-generation cost on the hot path.
27///
28/// # Examples
29///
30/// ```
31/// use key_vault::decoy::{DecoyStrategy, RandomDecoy};
32/// use key_vault::RawKey;
33///
34/// let key = RawKey::new(b"some key material".to_vec());
35/// let decoy = RandomDecoy.generate(&key, 32).unwrap();
36/// assert_eq!(decoy.len(), 32);
37/// ```
38#[derive(Debug, Default, Clone, Copy)]
39pub struct RandomDecoy;
40
41impl DecoyStrategy for RandomDecoy {
42    fn generate(&self, _key: &RawKey, output_len: usize) -> Result<Vec<u8>> {
43        if output_len == 0 {
44            return Ok(Vec::new());
45        }
46        let mut buf = vec![0u8; output_len];
47        getrandom::getrandom(&mut buf).map_err(|_| Error::Internal("OS RNG failed"))?;
48        Ok(buf)
49    }
50
51    fn describe(&self) -> Cow<'_, str> {
52        Cow::Borrowed("random")
53    }
54}
55
56#[cfg(test)]
57#[allow(
58    clippy::unwrap_used,
59    clippy::expect_used,
60    clippy::cast_possible_truncation,
61    clippy::cast_sign_loss
62)]
63mod tests {
64    use super::*;
65
66    fn raw(bytes: &[u8]) -> RawKey {
67        RawKey::new(bytes.to_vec())
68    }
69
70    #[test]
71    fn produces_requested_length() {
72        let key = raw(b"anything");
73        for n in [0usize, 1, 7, 32, 256, 4096] {
74            let out = RandomDecoy.generate(&key, n).unwrap();
75            assert_eq!(out.len(), n, "wrong length for n = {n}");
76        }
77    }
78
79    #[test]
80    fn two_calls_produce_different_bytes() {
81        let key = raw(b"k");
82        let a = RandomDecoy.generate(&key, 64).unwrap();
83        let b = RandomDecoy.generate(&key, 64).unwrap();
84        // Pure CSPRNG output collision at 64 bytes is astronomically
85        // improbable. A match indicates broken randomness.
86        assert_ne!(a, b);
87    }
88
89    #[test]
90    fn describe_returns_random() {
91        assert_eq!(RandomDecoy.describe(), "random");
92    }
93}