From 2da0c89b382f1a20f222cb8f3747077803632daa Mon Sep 17 00:00:00 2001 From: Reda Chouk Date: Tue, 27 Oct 2026 14:25:12 +0100 Subject: [PATCH] Rust wrapper: implement the rand_core 0.9 RNG traits behind a rand_core_0_9 feature --- wrapper/rust/wolfssl-wolfcrypt/CHANGELOG.md | 3 + wrapper/rust/wolfssl-wolfcrypt/Cargo.lock | 7 +++ wrapper/rust/wolfssl-wolfcrypt/Cargo.toml | 2 + wrapper/rust/wolfssl-wolfcrypt/Makefile | 2 +- wrapper/rust/wolfssl-wolfcrypt/README.md | 7 +++ wrapper/rust/wolfssl-wolfcrypt/src/random.rs | 54 ++++++++++++++-- .../wolfssl-wolfcrypt/tests/test_random.rs | 63 +++++++++++++++++++ 7 files changed, 132 insertions(+), 6 deletions(-) diff --git a/wrapper/rust/wolfssl-wolfcrypt/CHANGELOG.md b/wrapper/rust/wolfssl-wolfcrypt/CHANGELOG.md index 4282a8069ca..d2f0840e3d9 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/CHANGELOG.md +++ b/wrapper/rust/wolfssl-wolfcrypt/CHANGELOG.md @@ -40,6 +40,9 @@ Breaking changes: New features: +- Add the rand_core_0_9 feature: RNG implements the rand_core 0.9 + TryRngCore and TryCryptoRng traits, alongside the rand_core 0.10 ones, for + users of the rand 0.9 ecosystem - Add Curve25519Key::generate_shared_rng() to generate a key from an RNG shared between keys via Rc (requires the alloc feature) - Add HMAC::copy(), a fallible equivalent of HMAC::clone() diff --git a/wrapper/rust/wolfssl-wolfcrypt/Cargo.lock b/wrapper/rust/wolfssl-wolfcrypt/Cargo.lock index 718cbfe56e9..434ce46c606 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/Cargo.lock +++ b/wrapper/rust/wolfssl-wolfcrypt/Cargo.lock @@ -330,6 +330,12 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" + [[package]] name = "rand_core" version = "0.10.1" @@ -431,6 +437,7 @@ dependencies = [ "num-traits", "password-hash", "rand_core 0.10.1", + "rand_core 0.9.5", "regex", "signature", "zeroize", diff --git a/wrapper/rust/wolfssl-wolfcrypt/Cargo.toml b/wrapper/rust/wolfssl-wolfcrypt/Cargo.toml index e77b2fd8c19..ef0e274bd59 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/Cargo.toml +++ b/wrapper/rust/wolfssl-wolfcrypt/Cargo.toml @@ -14,6 +14,7 @@ links = "wolfssl" [features] alloc = [] rand_core = ["dep:rand_core"] +rand_core_0_9 = ["dep:rand_core_0_9"] aead = ["dep:aead"] cipher = ["dep:cipher"] mac = ["digest/mac"] @@ -24,6 +25,7 @@ kem = ["dep:kem", "hybrid-array/extra-sizes"] [dependencies] rand_core = { version = "0.10", optional = true, default-features = false } +rand_core_0_9 = { package = "rand_core", version = "0.9", optional = true, default-features = false } aead = { version = "0.5", optional = true, default-features = false } cipher = { version = "0.5", optional = true, default-features = false } digest = { version = "0.11", optional = true, default-features = false, features = ["block-api"] } diff --git a/wrapper/rust/wolfssl-wolfcrypt/Makefile b/wrapper/rust/wolfssl-wolfcrypt/Makefile index 0b7eac1d47e..bac420285bc 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/Makefile +++ b/wrapper/rust/wolfssl-wolfcrypt/Makefile @@ -1,4 +1,4 @@ -FEATURES := alloc,rand_core,aead,cipher,digest,mac,signature,password-hash,kem +FEATURES := alloc,rand_core,rand_core_0_9,aead,cipher,digest,mac,signature,password-hash,kem CARGO_FEATURE_FLAGS := --features $(FEATURES) .PHONY: all diff --git a/wrapper/rust/wolfssl-wolfcrypt/README.md b/wrapper/rust/wolfssl-wolfcrypt/README.md index 2fd8aeb8eed..62d56875458 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/README.md +++ b/wrapper/rust/wolfssl-wolfcrypt/README.md @@ -74,6 +74,7 @@ default. | `cipher` | `cipher` | AES (aes) | | `aead` | `aead` | AES-GCM/CCM/EAX (aes), ChaCha20-Poly1305 | | `rand_core` | `rand_core` | RNG (random) | +| `rand_core_0_9` | `rand_core` 0.9 | RNG (random) | | `kem` | `kem` | ML-KEM (mlkem_kem) | | `password-hash` | `password-hash` | PBKDF2 (pbkdf2_password_hash), scrypt (scrypt_password_hash) | @@ -95,6 +96,12 @@ let mut buf = [0u8; 32]; rng.fill_bytes(&mut buf); ``` +For projects still on the `rand` 0.9 ecosystem, the `rand_core_0_9` feature +implements the equivalent `rand_core` 0.9 traits, `TryRngCore` and +`TryCryptoRng`, with the same `RngError`. Wrap the RNG in the 0.9 +`UnwrapErr` to get `RngCore` and `CryptoRng`. Both features can be enabled at +once. + Enable features in your `Cargo.toml`, for example: ``` diff --git a/wrapper/rust/wolfssl-wolfcrypt/src/random.rs b/wrapper/rust/wolfssl-wolfcrypt/src/random.rs index 575b1baf271..9690746d25b 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/src/random.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/src/random.rs @@ -423,11 +423,11 @@ impl RNG { /// It carries the wolfSSL library error code of the failed /// `wc_RNG_GenerateBlock()` call, so that entropy, reseed and hardware /// failures are reported to the caller instead of terminating the process. -#[cfg(feature = "rand_core")] +#[cfg(any(feature = "rand_core", feature = "rand_core_0_9"))] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct RngError(i32); -#[cfg(feature = "rand_core")] +#[cfg(any(feature = "rand_core", feature = "rand_core_0_9"))] impl RngError { /// Return the wolfSSL library error code that caused the failure. pub fn code(&self) -> i32 { @@ -435,17 +435,17 @@ impl RngError { } } -#[cfg(feature = "rand_core")] +#[cfg(any(feature = "rand_core", feature = "rand_core_0_9"))] impl core::fmt::Display for RngError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "wolfSSL RNG failure (error code {})", self.0) } } -#[cfg(feature = "rand_core")] +#[cfg(any(feature = "rand_core", feature = "rand_core_0_9"))] impl core::error::Error for RngError {} -#[cfg(feature = "rand_core")] +#[cfg(any(feature = "rand_core", feature = "rand_core_0_9"))] impl From for i32 { fn from(err: RngError) -> i32 { err.0 @@ -496,6 +496,50 @@ impl rand_core::TryRng for RNG { #[cfg(feature = "rand_core")] impl rand_core::TryCryptoRng for RNG {} +/// Implement `rand_core` 0.9's `TryRngCore` for `RNG`, for callers still on +/// the `rand` 0.9 ecosystem. +/// +/// This mirrors the `rand_core` 0.10 `TryRng` implementation, failures are +/// returned as an [`RngError`]. Wrap the RNG in the 0.9 `UnwrapErr` adapter +/// to get the infallible `RngCore` and `CryptoRng` traits, which panic on RNG +/// failure. +/// +/// ```rust +/// #![cfg(feature = "rand_core_0_9")] +/// use rand_core_0_9::{RngCore, UnwrapErr}; +/// use wolfssl_wolfcrypt::random::RNG; +/// +/// let mut rng = UnwrapErr(RNG::new().expect("Failed to create RNG")); +/// let mut buf = [0u8; 32]; +/// // Panics on RNG failure. +/// rng.fill_bytes(&mut buf); +/// ``` +#[cfg(feature = "rand_core_0_9")] +impl rand_core_0_9::TryRngCore for RNG { + type Error = RngError; + + fn try_next_u32(&mut self) -> Result { + let mut buf = [0u8; 4]; + self.generate_block(&mut buf).map_err(RngError)?; + Ok(u32::from_le_bytes(buf)) + } + + fn try_next_u64(&mut self) -> Result { + let mut buf = [0u8; 8]; + self.generate_block(&mut buf).map_err(RngError)?; + Ok(u64::from_le_bytes(buf)) + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> { + self.generate_block(dest).map_err(RngError) + } +} + +/// Mark `RNG` as a cryptographically secure random number generator for +/// `rand_core` 0.9. +#[cfg(feature = "rand_core_0_9")] +impl rand_core_0_9::TryCryptoRng for RNG {} + /// `wc_rng_new_ex()`, or on libraries without it (FIPS v5) the older /// `wc_rng_new()`, which takes no device ID, so any device ID is refused there. /// diff --git a/wrapper/rust/wolfssl-wolfcrypt/tests/test_random.rs b/wrapper/rust/wolfssl-wolfcrypt/tests/test_random.rs index 158235dda9e..1829239a332 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/tests/test_random.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/tests/test_random.rs @@ -197,3 +197,66 @@ fn test_rng_try_rng_error_type() { let rng = RNG::new().expect("Failed to create RNG"); requires_rng_error(&rng); } + +#[test] +#[cfg(feature = "rand_core_0_9")] +fn test_rng_rand_core_0_9_fill_bytes() { + use rand_core_0_9::{RngCore, UnwrapErr}; + let mut rng = UnwrapErr(RNG::new().expect("Failed to create RNG")); + let mut buf = [0u8; 32]; + rng.fill_bytes(&mut buf); + assert_ne!(buf, [0u8; 32]); +} + +#[test] +#[cfg(feature = "rand_core_0_9")] +fn test_rng_rand_core_0_9_try_fill_bytes() { + use rand_core_0_9::TryRngCore; + let mut rng = RNG::new().expect("Failed to create RNG"); + let mut buf = [0u8; 32]; + rng.try_fill_bytes(&mut buf).expect("Failed to try_fill_bytes"); + assert_ne!(buf, [0u8; 32]); +} + +#[test] +#[cfg(feature = "rand_core_0_9")] +fn test_rng_rand_core_0_9_next_u32() { + use rand_core_0_9::{RngCore, UnwrapErr}; + let mut rng = UnwrapErr(RNG::new().expect("Failed to create RNG")); + // Generate several values and verify they aren't all zero. + let v: u64 = (0..4).map(|_| rng.next_u32() as u64).sum(); + assert_ne!(v, 0); +} + +#[test] +#[cfg(feature = "rand_core_0_9")] +fn test_rng_rand_core_0_9_next_u64() { + use rand_core_0_9::{RngCore, UnwrapErr}; + let mut rng = UnwrapErr(RNG::new().expect("Failed to create RNG")); + // Generate two values and verify they aren't all ones. + let v1 = rng.next_u64(); + let v2 = rng.next_u64(); + assert_ne!(v1 & v2, u64::MAX); +} + +/// Wrapped in the 0.9 `UnwrapErr`, the RNG satisfies the 0.9 `CryptoRng` +/// contract, which is what `rand` 0.9 APIs require. +#[test] +#[cfg(feature = "rand_core_0_9")] +fn test_rng_rand_core_0_9_unwrap_err_is_crypto_rng() { + use rand_core_0_9::{CryptoRng, UnwrapErr}; + fn requires_crypto_rng(_: &R) {} + let rng = UnwrapErr(RNG::new().expect("Failed to create RNG")); + requires_crypto_rng(&rng); +} + +/// The 0.9 `TryRngCore` must stay fallible, like the 0.10 `TryRng`. +#[test] +#[cfg(feature = "rand_core_0_9")] +fn test_rng_rand_core_0_9_error_type() { + use rand_core_0_9::{TryCryptoRng, TryRngCore}; + use wolfssl_wolfcrypt::random::RngError; + fn requires_rng_error>(_: &R) {} + let rng = RNG::new().expect("Failed to create RNG"); + requires_rng_error(&rng); +}