quantum_sign/drbg/
mod.rs

1#![forbid(unsafe_code)]
2
3//! SP 800-90A Rev.1 HMAC_DRBG(SHA-512) implementation.
4
5extern crate alloc;
6
7/// Pure Rust, zeroizes state on drop, and enforces a reseed interval.
8pub mod rand_adapter;
9
10use alloc::vec::Vec;
11use core::{cmp::min, fmt};
12use getrandom::getrandom;
13use hmac::{Hmac, Mac};
14use sha2::Sha512;
15use subtle::{Choice, ConstantTimeEq};
16use zeroize::{Zeroize, ZeroizeOnDrop};
17
18/// Errors that can occur during DRBG operation.
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum Error {
21    /// Generated output exceeds SP 800-90A per-request cap (64 KiB).
22    RequestTooLarge,
23    /// Reseed interval exhausted and new entropy is required.
24    ReseedRequired,
25    /// OS entropy source failed.
26    EntropyUnavailable,
27    /// Entropy health test failed (SP 800-90B).
28    EntropyHealthFailed,
29}
30
31impl fmt::Display for Error {
32    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33        write!(f, "{:?}", self)
34    }
35}
36
37impl std::error::Error for Error {}
38
39/// Default reseed interval recommended by SP 800-90A (Section 10.1.2).
40const DEFAULT_RESEED_INTERVAL: u64 = 1u64 << 48;
41const BLOCK_LEN: usize = 64;
42const MAX_REQUEST: usize = 65536; // 64 KiB cap per generate call.
43const DEFAULT_MAX_BYTES_BETWEEN_RESEED: u128 = 1u128 << 20;
44
45/// HMAC_DRBG with HMAC-SHA-512 backbone.
46#[derive(Zeroize, ZeroizeOnDrop)]
47pub struct HmacDrbg {
48    k: [u8; BLOCK_LEN],
49    v: [u8; BLOCK_LEN],
50    reseed_counter: u64,
51    reseed_interval: u64,
52    generated_bytes: u128,
53    max_bytes_between_reseed: u128,
54    last_entropy: Vec<u8>,
55}
56
57impl HmacDrbg {
58    /// Instantiate DRBG from entropy, nonce, and optional personalization string.
59    pub fn new(
60        entropy: &[u8],
61        nonce: &[u8],
62        personalization: Option<&[u8]>,
63    ) -> Result<Self, Error> {
64        Self::validate_entropy(entropy)?;
65
66        let mut seed = Vec::with_capacity(
67            entropy.len() + nonce.len() + personalization.map_or(0, |p| p.len()),
68        );
69        seed.extend_from_slice(entropy);
70        seed.extend_from_slice(nonce);
71        if let Some(pers) = personalization {
72            seed.extend_from_slice(pers);
73        }
74
75        let mut drbg = Self {
76            k: [0u8; BLOCK_LEN],
77            v: [0x01u8; BLOCK_LEN],
78            reseed_counter: 1,
79            reseed_interval: DEFAULT_RESEED_INTERVAL,
80            generated_bytes: 0,
81            max_bytes_between_reseed: DEFAULT_MAX_BYTES_BETWEEN_RESEED,
82            last_entropy: entropy.to_vec(),
83        };
84        drbg.update(Some(&seed));
85        seed.zeroize();
86        Ok(drbg)
87    }
88
89    /// Instantiate by sampling entropy+nonce from the operating system.
90    pub fn from_os(personalization: Option<&[u8]>) -> Result<Self, Error> {
91        let mut entropy = [0u8; 48];
92        let mut nonce = [0u8; 16];
93        getrandom(&mut entropy).map_err(|_| Error::EntropyUnavailable)?;
94        getrandom(&mut nonce).map_err(|_| Error::EntropyUnavailable)?;
95        Self::validate_entropy(&nonce)?;
96        let drbg = Self::new(&entropy, &nonce, personalization)?;
97        entropy.zeroize();
98        nonce.zeroize();
99        Ok(drbg)
100    }
101
102    /// Override the reseed interval (useful for tests or hardened policies).
103    pub fn set_reseed_interval(&mut self, interval: u64) {
104        self.reseed_interval = interval.max(1);
105    }
106
107    /// Override the byte budget requiring reseed.
108    pub fn set_max_bytes_between_reseed(&mut self, max_bytes: u128) {
109        self.max_bytes_between_reseed = max_bytes.max(1);
110    }
111
112    /// Reseed with fresh entropy and optional additional input.
113    pub fn reseed(&mut self, entropy: &[u8], additional_input: Option<&[u8]>) -> Result<(), Error> {
114        self.check_new_entropy(entropy)?;
115        let mut seed = Vec::with_capacity(entropy.len() + additional_input.map_or(0, |a| a.len()));
116        seed.extend_from_slice(entropy);
117        if let Some(ai) = additional_input {
118            seed.extend_from_slice(ai);
119        }
120        self.update(Some(&seed));
121        seed.zeroize();
122        self.last_entropy.clear();
123        self.last_entropy.extend_from_slice(entropy);
124        self.reseed_counter = 1;
125        self.generated_bytes = 0;
126        Ok(())
127    }
128
129    /// Generate output, optionally mixing in additional input (per SP 800-90A Sect. 10.1.2.5).
130    pub fn generate(
131        &mut self,
132        out: &mut [u8],
133        additional_input: Option<&[u8]>,
134    ) -> Result<(), Error> {
135        if out.len() > MAX_REQUEST {
136            return Err(Error::RequestTooLarge);
137        }
138        if self.reseed_counter > self.reseed_interval
139            || (self.generated_bytes + (out.len() as u128)) >= self.max_bytes_between_reseed
140        {
141            return Err(Error::ReseedRequired);
142        }
143        if let Some(ai) = additional_input {
144            self.update(Some(ai));
145        }
146        let mut generated = 0usize;
147        while generated < out.len() {
148            let mut mac = Hmac::<Sha512>::new_from_slice(&self.k).expect("hmac key len");
149            mac.update(&self.v);
150            self.v = mac.finalize().into_bytes().into();
151            let take = min(out.len() - generated, BLOCK_LEN);
152            out[generated..generated + take].copy_from_slice(&self.v[..take]);
153            generated += take;
154        }
155        self.update(additional_input);
156        self.reseed_counter = self.reseed_counter.saturating_add(1);
157        self.generated_bytes = self.generated_bytes.saturating_add(out.len() as u128);
158        Ok(())
159    }
160
161    fn update(&mut self, provided_data: Option<&[u8]>) {
162        let zero = [0x00u8];
163        let one = [0x01u8];
164
165        let mut mac = Hmac::<Sha512>::new_from_slice(&self.k).expect("hmac key len");
166        mac.update(&self.v);
167        mac.update(&zero);
168        if let Some(data) = provided_data {
169            mac.update(data);
170        }
171        self.k = mac.finalize().into_bytes().into();
172        let mut mac = Hmac::<Sha512>::new_from_slice(&self.k).expect("hmac key len");
173        mac.update(&self.v);
174        self.v = mac.finalize().into_bytes().into();
175
176        if provided_data.is_some() {
177            let mut mac = Hmac::<Sha512>::new_from_slice(&self.k).expect("hmac key len");
178            mac.update(&self.v);
179            mac.update(&one);
180            if let Some(data) = provided_data {
181                mac.update(data);
182            }
183            self.k = mac.finalize().into_bytes().into();
184            let mut mac = Hmac::<Sha512>::new_from_slice(&self.k).expect("hmac key len");
185            mac.update(&self.v);
186            self.v = mac.finalize().into_bytes().into();
187        }
188    }
189
190    fn validate_entropy(entropy: &[u8]) -> Result<(), Error> {
191        if entropy.len() < 16 {
192            return Err(Error::EntropyHealthFailed);
193        }
194        Ok(())
195    }
196
197    fn check_new_entropy(&self, new_entropy: &[u8]) -> Result<(), Error> {
198        if new_entropy.len() < 16 {
199            return Err(Error::EntropyHealthFailed);
200        }
201        let prefix = &self.last_entropy[..min(16, self.last_entropy.len())];
202        let new_prefix = &new_entropy[..min(16, new_entropy.len())];
203        if bool::from(prefix.ct_eq(new_prefix)) {
204            return Err(Error::EntropyHealthFailed);
205        }
206        Ok(())
207    }
208}
209
210impl ConstantTimeEq for HmacDrbg {
211    fn ct_eq(&self, other: &Self) -> Choice {
212        let mut c = self.k.ct_eq(&other.k) & self.v.ct_eq(&other.v);
213        c &= Choice::from((self.reseed_counter == other.reseed_counter) as u8);
214        c &= Choice::from((self.reseed_interval == other.reseed_interval) as u8);
215        c &= Choice::from((self.generated_bytes == other.generated_bytes) as u8);
216        c &= Choice::from((self.max_bytes_between_reseed == other.max_bytes_between_reseed) as u8);
217        if self.last_entropy.len() != other.last_entropy.len() {
218            return Choice::from(0);
219        }
220        let mut v = Choice::from(1);
221        for (a, b) in self.last_entropy.iter().zip(other.last_entropy.iter()) {
222            v &= Choice::from((*a == *b) as u8);
223        }
224        c & v
225    }
226}
227
228// (Removed unused AES-CTR DRBG scaffold test module to minimize dead code.)
229
230#[cfg(test)]
231mod tests {
232    use super::*;
233
234    fn entropy(seed: u8) -> [u8; 48] {
235        let mut out = [0u8; 48];
236        for (i, byte) in out.iter_mut().enumerate() {
237            *byte = seed.wrapping_add(i as u8);
238        }
239        out
240    }
241
242    fn nonce(seed: u8) -> [u8; 16] {
243        let mut out = [0u8; 16];
244        for (i, byte) in out.iter_mut().enumerate() {
245            *byte = seed.wrapping_add((i * 3) as u8);
246        }
247        out
248    }
249
250    #[test]
251    fn identical_seed_produces_identical_stream() {
252        let mut a = HmacDrbg::new(&entropy(1), &nonce(2), Some(b"p")).unwrap();
253        let mut b = HmacDrbg::new(&entropy(1), &nonce(2), Some(b"p")).unwrap();
254        let mut out_a = [0u8; 96];
255        let mut out_b = [0u8; 96];
256        a.generate(&mut out_a, None).unwrap();
257        b.generate(&mut out_b, None).unwrap();
258        assert_eq!(out_a, out_b);
259        assert!(bool::from(a.ct_eq(&b)));
260    }
261
262    #[test]
263    fn reseed_changes_output() {
264        let mut drbg = HmacDrbg::new(&entropy(3), &nonce(4), None).unwrap();
265        let mut first = [0u8; 64];
266        drbg.generate(&mut first, None).unwrap();
267        drbg.reseed(&entropy(9), None).unwrap();
268        let mut second = [0u8; 64];
269        drbg.generate(&mut second, None).unwrap();
270        assert_ne!(first, second);
271    }
272
273    #[test]
274    fn additional_input_affects_stream() {
275        let mut drbg = HmacDrbg::new(&entropy(5), &nonce(6), None).unwrap();
276        let mut buf1 = [0u8; 64];
277        let mut buf2 = [0u8; 64];
278        drbg.generate(&mut buf1, Some(b"ai1")).unwrap();
279        drbg.generate(&mut buf2, Some(b"ai2")).unwrap();
280        assert_ne!(buf1, buf2);
281    }
282
283    #[test]
284    fn request_too_large_fails() {
285        let mut drbg = HmacDrbg::new(&entropy(7), &nonce(8), None).unwrap();
286        let mut buf = vec![0u8; MAX_REQUEST + 1];
287        assert_eq!(drbg.generate(&mut buf, None), Err(Error::RequestTooLarge));
288    }
289
290    #[test]
291    fn reseed_interval_enforced() {
292        let mut drbg = HmacDrbg::new(&entropy(9), &nonce(10), None).unwrap();
293        drbg.set_reseed_interval(1);
294        let mut buf = [0u8; 32];
295        drbg.generate(&mut buf, None).unwrap();
296        assert_eq!(drbg.generate(&mut buf, None), Err(Error::ReseedRequired));
297    }
298    #[test]
299    fn repeated_entropy_fails_health() {
300        let mut drbg = HmacDrbg::new(&entropy(1), &nonce(2), None).unwrap();
301        assert!(matches!(
302            drbg.reseed(&entropy(1), None),
303            Err(Error::EntropyHealthFailed)
304        ));
305    }
306
307    #[test]
308    fn byte_budget_enforced() {
309        let mut drbg = HmacDrbg::new(&entropy(11), &nonce(12), None).unwrap();
310        drbg.set_max_bytes_between_reseed(64);
311        let mut buf = [0u8; 32];
312        drbg.generate(&mut buf, None).unwrap();
313        assert!(matches!(
314            drbg.generate(&mut buf, None),
315            Err(Error::ReseedRequired)
316        ));
317    }
318}