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}