qs_drbg/
lib.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::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
37#[cfg(feature = "std")]
38impl std::error::Error for Error {}
39
40/// Default reseed interval recommended by SP 800-90A (Section 10.1.2).
41const DEFAULT_RESEED_INTERVAL: u64 = 1u64 << 48;
42const BLOCK_LEN: usize = 64;
43const MAX_REQUEST: usize = 65536; // 64 KiB cap per generate call.
44const DEFAULT_MAX_BYTES_BETWEEN_RESEED: u128 = 1u128 << 20;
45
46/// HMAC_DRBG with HMAC-SHA-512 backbone.
47#[derive(Zeroize, ZeroizeOnDrop)]
48pub struct HmacDrbg {
49    k: [u8; BLOCK_LEN],
50    v: [u8; BLOCK_LEN],
51    reseed_counter: u64,
52    reseed_interval: u64,
53    generated_bytes: u128,
54    max_bytes_between_reseed: u128,
55    last_entropy: Vec<u8>,
56}
57
58impl HmacDrbg {
59    /// Instantiate DRBG from entropy, nonce, and optional personalization string.
60    pub fn new(
61        entropy: &[u8],
62        nonce: &[u8],
63        personalization: Option<&[u8]>,
64    ) -> Result<Self, Error> {
65        Self::validate_entropy(entropy)?;
66
67        let mut seed = Vec::with_capacity(
68            entropy.len() + nonce.len() + personalization.map_or(0, |p| p.len()),
69        );
70        seed.extend_from_slice(entropy);
71        seed.extend_from_slice(nonce);
72        if let Some(pers) = personalization {
73            seed.extend_from_slice(pers);
74        }
75
76        let mut drbg = Self {
77            k: [0u8; BLOCK_LEN],
78            v: [0x01u8; BLOCK_LEN],
79            reseed_counter: 1,
80            reseed_interval: DEFAULT_RESEED_INTERVAL,
81            generated_bytes: 0,
82            max_bytes_between_reseed: DEFAULT_MAX_BYTES_BETWEEN_RESEED,
83            last_entropy: entropy.to_vec(),
84        };
85        drbg.update(Some(&seed));
86        seed.zeroize();
87        Ok(drbg)
88    }
89
90    /// Instantiate by sampling entropy+nonce from the operating system.
91    pub fn from_os(personalization: Option<&[u8]>) -> Result<Self, Error> {
92        let mut entropy = [0u8; 48];
93        let mut nonce = [0u8; 16];
94        getrandom(&mut entropy).map_err(|_| Error::EntropyUnavailable)?;
95        getrandom(&mut nonce).map_err(|_| Error::EntropyUnavailable)?;
96        Self::validate_entropy(&nonce)?;
97        let drbg = Self::new(&entropy, &nonce, personalization)?;
98        entropy.zeroize();
99        nonce.zeroize();
100        Ok(drbg)
101    }
102
103    /// Override the reseed interval (useful for tests or hardened policies).
104    pub fn set_reseed_interval(&mut self, interval: u64) {
105        self.reseed_interval = interval.max(1);
106    }
107
108    /// Override the byte budget requiring reseed.
109    pub fn set_max_bytes_between_reseed(&mut self, max_bytes: u128) {
110        self.max_bytes_between_reseed = max_bytes.max(1);
111    }
112
113    /// Reseed with fresh entropy and optional additional input.
114    pub fn reseed(&mut self, entropy: &[u8], additional_input: Option<&[u8]>) -> Result<(), Error> {
115        self.check_new_entropy(entropy)?;
116        let mut seed = Vec::with_capacity(entropy.len() + additional_input.map_or(0, |a| a.len()));
117        seed.extend_from_slice(entropy);
118        if let Some(ai) = additional_input {
119            seed.extend_from_slice(ai);
120        }
121        self.update(Some(&seed));
122        seed.zeroize();
123        self.last_entropy.clear();
124        self.last_entropy.extend_from_slice(entropy);
125        self.reseed_counter = 1;
126        self.generated_bytes = 0;
127        Ok(())
128    }
129
130    /// Generate pseudorandom bytes. Optional `additional_input` mixes per-request data.
131    pub fn generate(
132        &mut self,
133        out: &mut [u8],
134        additional_input: Option<&[u8]>,
135    ) -> Result<(), Error> {
136        if out.len() > MAX_REQUEST {
137            return Err(Error::RequestTooLarge);
138        }
139        if self.reseed_counter > self.reseed_interval {
140            return Err(Error::ReseedRequired);
141        }
142        if self.generated_bytes + out.len() as u128 >= self.max_bytes_between_reseed {
143            return Err(Error::ReseedRequired);
144        }
145
146        if let Some(ai) = additional_input {
147            if !ai.is_empty() {
148                self.update(Some(ai));
149            }
150        }
151
152        let mut produced = 0usize;
153        while produced < out.len() {
154            self.v = self.hmac(&self.v);
155            let take = min(BLOCK_LEN, out.len() - produced);
156            out[produced..produced + take].copy_from_slice(&self.v[..take]);
157            produced += take;
158        }
159
160        // Post-generation update (Section 10.1.2.5).
161        if let Some(ai) = additional_input {
162            if !ai.is_empty() {
163                self.update(Some(ai));
164            } else {
165                self.update(None);
166            }
167        } else {
168            self.update(None);
169        }
170
171        self.reseed_counter = self.reseed_counter.saturating_add(1);
172        self.generated_bytes = self.generated_bytes.saturating_add(out.len() as u128);
173        Ok(())
174    }
175
176    /// Compare internal state with another DRBG (useful for tests).
177    pub fn ct_eq(&self, other: &Self) -> subtle::Choice {
178        let mut acc = self.k.ct_eq(&other.k);
179        acc &= self.v.ct_eq(&other.v);
180        if self.reseed_counter == other.reseed_counter
181            && self.reseed_interval == other.reseed_interval
182            && self.generated_bytes == other.generated_bytes
183            && self.max_bytes_between_reseed == other.max_bytes_between_reseed
184        {
185            acc &= subtle::Choice::from(1);
186        } else {
187            acc &= subtle::Choice::from(0);
188        }
189
190        if self.last_entropy.len() == other.last_entropy.len() {
191            acc &= self
192                .last_entropy
193                .as_slice()
194                .ct_eq(other.last_entropy.as_slice());
195        } else {
196            acc &= subtle::Choice::from(0);
197        }
198        acc
199    }
200
201    fn validate_entropy(entropy: &[u8]) -> Result<(), Error> {
202        if entropy.is_empty() {
203            return Err(Error::EntropyHealthFailed);
204        }
205        if entropy.iter().all(|&b| b == entropy[0]) {
206            return Err(Error::EntropyHealthFailed);
207        }
208        Ok(())
209    }
210
211    fn check_new_entropy(&self, entropy: &[u8]) -> Result<(), Error> {
212        Self::validate_entropy(entropy)?;
213        if !self.last_entropy.is_empty() && entropy == self.last_entropy.as_slice() {
214            return Err(Error::EntropyHealthFailed);
215        }
216        Ok(())
217    }
218    fn hmac(&self, data: &[u8]) -> [u8; BLOCK_LEN] {
219        let mut mac = Hmac::<Sha512>::new_from_slice(&self.k).expect("HMAC key length valid");
220        mac.update(data);
221        let out = mac.finalize().into_bytes();
222        let mut buf = [0u8; BLOCK_LEN];
223        buf.copy_from_slice(&out);
224        buf
225    }
226
227    fn update(&mut self, provided_data: Option<&[u8]>) {
228        // Step (a): K = HMAC(K, V || 0x00 || provided)
229        // Step (b): V = HMAC(K, V)
230        let mut buf = Vec::with_capacity(self.v.len() + 1 + provided_data.map_or(0, |d| d.len()));
231        buf.extend_from_slice(&self.v);
232        buf.push(0x00);
233        if let Some(data) = provided_data {
234            buf.extend_from_slice(data);
235        }
236        let new_k = {
237            let mut mac = Hmac::<Sha512>::new_from_slice(&self.k).expect("HMAC key length valid");
238            mac.update(&buf);
239            mac.finalize().into_bytes()
240        };
241        self.k.copy_from_slice(&new_k);
242        self.v = self.hmac(&self.v);
243        buf.zeroize();
244
245        if let Some(data) = provided_data {
246            let mut buf = Vec::with_capacity(self.v.len() + 1 + data.len());
247            buf.extend_from_slice(&self.v);
248            buf.push(0x01);
249            buf.extend_from_slice(data);
250            let new_k = {
251                let mut mac =
252                    Hmac::<Sha512>::new_from_slice(&self.k).expect("HMAC key length valid");
253                mac.update(&buf);
254                mac.finalize().into_bytes()
255            };
256            self.k.copy_from_slice(&new_k);
257            self.v = self.hmac(&self.v);
258            buf.zeroize();
259        }
260    }
261}
262
263#[cfg(feature = "aes")]
264mod aes_ctr_drbg {
265    use super::Error;
266    use aes::Aes256;
267    use ctr::cipher::{KeyIvInit, StreamCipher};
268    use zeroize::{Zeroize, ZeroizeOnDrop};
269
270    type Ctr128 = ctr::Ctr128BE<Aes256>;
271
272    /// AES-256-CTR_DRBG scaffold (update/derivation per SP 800-90A still TODO).
273    #[derive(Zeroize, ZeroizeOnDrop)]
274    pub struct AesCtrDrbg {
275        key: [u8; 32],
276        v: [u8; 16],
277        reseed_counter: u64,
278        reseed_interval: u64,
279    }
280
281    impl AesCtrDrbg {
282        pub fn new(seed_key: &[u8; 32], seed_v: &[u8; 16]) -> Self {
283            Self {
284                key: *seed_key,
285                v: *seed_v,
286                reseed_counter: 1,
287                reseed_interval: super::DEFAULT_RESEED_INTERVAL,
288            }
289        }
290
291        pub fn generate(&mut self, out: &mut [u8]) -> Result<(), Error> {
292            if self.reseed_counter > self.reseed_interval {
293                return Err(Error::ReseedRequired);
294            }
295            let mut cipher = Ctr128::new((&self.key).into(), (&self.v).into());
296            cipher.apply_keystream(out);
297            // Increment counter by number of blocks consumed.
298            let blocks = (out.len() as u128 + 15) / 16;
299            let ctr = u128::from_be_bytes(self.v).wrapping_add(blocks);
300            self.v = ctr.to_be_bytes();
301            self.reseed_counter = self.reseed_counter.saturating_add(1);
302            Ok(())
303        }
304    }
305}
306
307#[cfg(test)]
308mod tests {
309    use super::*;
310
311    fn entropy(seed: u8) -> [u8; 48] {
312        let mut out = [0u8; 48];
313        for (i, byte) in out.iter_mut().enumerate() {
314            *byte = seed.wrapping_add(i as u8);
315        }
316        out
317    }
318
319    fn nonce(seed: u8) -> [u8; 16] {
320        let mut out = [0u8; 16];
321        for (i, byte) in out.iter_mut().enumerate() {
322            *byte = seed.wrapping_add((i * 3) as u8);
323        }
324        out
325    }
326
327    #[test]
328    fn identical_seed_produces_identical_stream() {
329        let mut a = HmacDrbg::new(&entropy(1), &nonce(2), Some(b"p")).unwrap();
330        let mut b = HmacDrbg::new(&entropy(1), &nonce(2), Some(b"p")).unwrap();
331        let mut out_a = [0u8; 96];
332        let mut out_b = [0u8; 96];
333        a.generate(&mut out_a, None).unwrap();
334        b.generate(&mut out_b, None).unwrap();
335        assert_eq!(out_a, out_b);
336        assert!(bool::from(a.ct_eq(&b)));
337    }
338
339    #[test]
340    fn reseed_changes_output() {
341        let mut drbg = HmacDrbg::new(&entropy(3), &nonce(4), None).unwrap();
342        let mut first = [0u8; 64];
343        drbg.generate(&mut first, None).unwrap();
344        drbg.reseed(&entropy(9), None).unwrap();
345        let mut second = [0u8; 64];
346        drbg.generate(&mut second, None).unwrap();
347        assert_ne!(first, second);
348    }
349
350    #[test]
351    fn additional_input_affects_stream() {
352        let mut drbg = HmacDrbg::new(&entropy(5), &nonce(6), None).unwrap();
353        let mut buf1 = [0u8; 64];
354        let mut buf2 = [0u8; 64];
355        drbg.generate(&mut buf1, Some(b"ai1")).unwrap();
356        drbg.generate(&mut buf2, Some(b"ai2")).unwrap();
357        assert_ne!(buf1, buf2);
358    }
359
360    #[test]
361    fn request_too_large_fails() {
362        let mut drbg = HmacDrbg::new(&entropy(7), &nonce(8), None).unwrap();
363        let mut buf = vec![0u8; MAX_REQUEST + 1];
364        assert_eq!(drbg.generate(&mut buf, None), Err(Error::RequestTooLarge));
365    }
366
367    #[test]
368    fn reseed_interval_enforced() {
369        let mut drbg = HmacDrbg::new(&entropy(9), &nonce(10), None).unwrap();
370        drbg.set_reseed_interval(1);
371        let mut buf = [0u8; 32];
372        drbg.generate(&mut buf, None).unwrap();
373        assert_eq!(drbg.generate(&mut buf, None), Err(Error::ReseedRequired));
374    }
375    #[test]
376    fn repeated_entropy_fails_health() {
377        let mut drbg = HmacDrbg::new(&entropy(1), &nonce(2), None).unwrap();
378        assert!(matches!(
379            drbg.reseed(&entropy(1), None),
380            Err(Error::EntropyHealthFailed)
381        ));
382    }
383
384    #[test]
385    fn byte_budget_enforced() {
386        let mut drbg = HmacDrbg::new(&entropy(11), &nonce(12), None).unwrap();
387        drbg.set_max_bytes_between_reseed(64);
388        let mut buf = [0u8; 32];
389        drbg.generate(&mut buf, None).unwrap();
390        assert!(matches!(
391            drbg.generate(&mut buf, None),
392            Err(Error::ReseedRequired)
393        ));
394    }
395}