diff --git a/CHANGELOG.md b/CHANGELOG.md index f2f641a9d6..1ef3c3bbd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- (libcrux-hmac-drbg) [#1523](https://github.com/celabshq/libcrux/issues/1523): Allow configuring a custom reseed interval - [#1474](https://github.com/celabshq/libcrux/pull/1474): Add support for AES-CCM from `libcrux-aes`, rename feature `aes_gcm` to `aes_aead` - [#1382](https://github.com/celabshq/libcrux/pull/1382): Add support for HMAC-DRBG - (libcrux-secrets) [#1446](https://github.com/celabshq/libcrux/pull/1446): Integrate valgrind requests when cfg `valgrind_ct_test` is set @@ -37,6 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - (libcrux-sha3) [#1292](https://github.com/celabshq/libcrux/pull/1292): Add support for incremental CShake (https://github.com/kraemv) - (libcrux-hmac) [#1382](https://github.com/celabshq/libcrux/pull/1382): Add an incremental API + ## [0.0.4] (2026-05-13) ### Fixed diff --git a/crates/algorithms/hmac-drbg/src/lib.rs b/crates/algorithms/hmac-drbg/src/lib.rs index db85459fd0..71d438049b 100644 --- a/crates/algorithms/hmac-drbg/src/lib.rs +++ b/crates/algorithms/hmac-drbg/src/lib.rs @@ -109,6 +109,7 @@ pub struct HmacDrbg> { key: [u8; OUTLEN], v: [u8; OUTLEN], reseed_counter: u64, + reseed_interval: u64, /// All continuous health-test state, present only with feature `health-tests`. #[cfg(feature = "health-tests")] @@ -212,6 +213,7 @@ impl> HmacDrbg { key: [0x00u8; OUTLEN], v: [0x01u8; OUTLEN], reseed_counter: 1, + reseed_interval: RESEED_INTERVAL, #[cfg(feature = "health-tests")] health: HealthState::new(), }; @@ -287,7 +289,7 @@ impl> HmacDrbg { // #hax: requires output.len() > 0 // #hax: requires output.len() <= MAX_GENERATE_BYTES // #hax: requires true // additional_input has no length constraint - // #hax: requires self.reseed_counter <= RESEED_INTERVAL + // #hax: requires self.reseed_counter <= self.reseed_interval // #hax: ensures result.is_ok() ==> self.reseed_counter == old(self.reseed_counter) + 1 pub fn generate( &mut self, @@ -299,7 +301,7 @@ impl> HmacDrbg { return Err(GenerateError::HealthCheckFailed); } - if self.reseed_counter > RESEED_INTERVAL { + if self.reseed_counter > self.reseed_interval { return Err(GenerateError::ReseedRequired); } if output.is_empty() { @@ -350,9 +352,18 @@ impl> HmacDrbg { /// Callers can use this to proactively schedule a reseed before the counter /// is exhausted rather than discovering it on the next `generate` call. // - // #hax: ensures result == (self.reseed_counter > RESEED_INTERVAL) + // #hax: ensures result == (self.reseed_counter > self.reseed_interval) pub fn needs_reseed(&self) -> bool { - self.reseed_counter > RESEED_INTERVAL + self.reseed_counter > self.reseed_interval + } + + /// Set the reseed interval for this DRBG instance. + /// + /// The interval is clamped to a maximum of [`RESEED_INTERVAL`] to ensure + /// compliance with SP 800-90A. + pub fn set_reseed_interval(&mut self, interval: u64) { + let max = RESEED_INTERVAL; + self.reseed_interval = if interval > max { max } else { interval }; } /// Returns the current reseed counter. diff --git a/crates/algorithms/hmac-drbg/src/rand.rs b/crates/algorithms/hmac-drbg/src/rand.rs index 5f839a390d..b6774dc7a8 100644 --- a/crates/algorithms/hmac-drbg/src/rand.rs +++ b/crates/algorithms/hmac-drbg/src/rand.rs @@ -60,7 +60,7 @@ impl> rand::TryRng for HmacDrbg< /// Requests larger than [`MAX_GENERATE_BYTES`] are split into chunks so /// that arbitrarily-sized buffers can be filled without error. // - // #hax: requires self.reseed_counter + (dst.len() / MAX_GENERATE_BYTES) as u64 + 1 <= RESEED_INTERVAL + // #hax: requires self.reseed_counter + (dst.len() / MAX_GENERATE_BYTES) as u64 + 1 <= self.reseed_interval // #hax: ensures result.is_ok() ==> dst is fully written fn try_fill_bytes(&mut self, dst: &mut [u8]) -> Result<(), Self::Error> { let mut written = 0; @@ -121,6 +121,7 @@ impl> SeedableRng for HmacDrbg, ReseedRng: CryptoRng> Self { drbg, rng } } + /// Set the reseed interval for the underlying DRBG. + /// + /// The interval is clamped to a maximum of [`crate::RESEED_INTERVAL`]. + pub fn set_reseed_interval(&mut self, interval: u64) { + self.drbg.set_reseed_interval(interval); + } + /// Somewhat safely generates a bit of randomness: /// - ensures we don't pass in too much additional_input (none, in fact) /// - reseeds if needed diff --git a/crates/algorithms/hmac-drbg/src/tests.rs b/crates/algorithms/hmac-drbg/src/tests.rs index d436b0411d..27f98e8484 100644 --- a/crates/algorithms/hmac-drbg/src/tests.rs +++ b/crates/algorithms/hmac-drbg/src/tests.rs @@ -254,5 +254,26 @@ fn reseed_required_error() { fn needs_reseed_true_after_exhaustion() { let mut drbg = HmacDrbgSha256::new(&E32, &[0u8; 16], &[]).unwrap(); drbg.set_reseed_counter(RESEED_INTERVAL + 1); +} + +#[test] +fn custom_reseed_interval() { + let mut drbg = HmacDrbgSha256::new(&E32, &[0u8; 16], &[]).unwrap(); + drbg.set_reseed_interval(1024); + drbg.set_reseed_counter(1025); + let mut out = [0u8; 32]; + let err = drbg.generate(&mut out, &[]).unwrap_err(); + assert_eq!(err, GenerateError::ReseedRequired); + assert!(drbg.needs_reseed()); +} + +#[test] +fn custom_reseed_interval_clamping() { + let mut drbg = HmacDrbgSha256::new(&E32, &[0u8; 16], &[]).unwrap(); + drbg.set_reseed_interval(RESEED_INTERVAL + 100); + // It should be clamped to RESEED_INTERVAL + drbg.set_reseed_counter(RESEED_INTERVAL); + assert!(!drbg.needs_reseed()); + drbg.set_reseed_counter(RESEED_INTERVAL + 1); assert!(drbg.needs_reseed()); }