From 07acf8d33d9f0ca7757ac002fc763c8bd66b0512 Mon Sep 17 00:00:00 2001 From: Josh Holtrop Date: Mon, 9 Mar 2026 15:11:40 -0400 Subject: [PATCH] Rust wrapper: add rand_core trait support --- wrapper/rust/wolfssl-wolfcrypt/Cargo.lock | 7 +++ wrapper/rust/wolfssl-wolfcrypt/Cargo.toml | 4 ++ wrapper/rust/wolfssl-wolfcrypt/Makefile | 13 +++-- wrapper/rust/wolfssl-wolfcrypt/src/random.rs | 28 +++++++++++ .../wolfssl-wolfcrypt/tests/test_random.rs | 50 +++++++++++++++++++ 5 files changed, 97 insertions(+), 5 deletions(-) diff --git a/wrapper/rust/wolfssl-wolfcrypt/Cargo.lock b/wrapper/rust/wolfssl-wolfcrypt/Cargo.lock index cd58000817..b91ba13718 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/Cargo.lock +++ b/wrapper/rust/wolfssl-wolfcrypt/Cargo.lock @@ -156,6 +156,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand_core" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" + [[package]] name = "regex" version = "1.11.2" @@ -290,5 +296,6 @@ name = "wolfssl-wolfcrypt" version = "1.2.0" dependencies = [ "bindgen", + "rand_core", "regex", ] diff --git a/wrapper/rust/wolfssl-wolfcrypt/Cargo.toml b/wrapper/rust/wolfssl-wolfcrypt/Cargo.toml index 61dee2bd28..ed59c271c8 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/Cargo.toml +++ b/wrapper/rust/wolfssl-wolfcrypt/Cargo.toml @@ -12,6 +12,10 @@ readme = "README.md" [features] std = [] +rand_core = ["dep:rand_core"] + +[dependencies] +rand_core = { version = "0.10", optional = true, default-features = false } [build-dependencies] bindgen = "0.72.1" diff --git a/wrapper/rust/wolfssl-wolfcrypt/Makefile b/wrapper/rust/wolfssl-wolfcrypt/Makefile index 7cda418962..c534814c0e 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/Makefile +++ b/wrapper/rust/wolfssl-wolfcrypt/Makefile @@ -1,16 +1,19 @@ +FEATURES := rand_core +CARGO_FEATURE_FLAGS := --features $(FEATURES) + .PHONY: all all: - cargo build - cargo clippy - cargo doc + cargo build $(CARGO_FEATURE_FLAGS) + cargo clippy $(CARGO_FEATURE_FLAGS) + cargo doc $(CARGO_FEATURE_FLAGS) .PHONY: test test: - cargo test -- --test-threads=1 + cargo test $(CARGO_FEATURE_FLAGS) -- --test-threads=1 .PHONY: testfips testfips: - cargo test --lib --bins --tests -- --test-threads=1 + cargo test $(CARGO_FEATURE_FLAGS) --lib --bins --tests -- --test-threads=1 .PHONY: clean clean: diff --git a/wrapper/rust/wolfssl-wolfcrypt/src/random.rs b/wrapper/rust/wolfssl-wolfcrypt/src/random.rs index fcfa5a4d2f..7d3bb8a79a 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/src/random.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/src/random.rs @@ -380,6 +380,34 @@ impl RNG { } } +/// Implement `rand_core::TryRng` for `RNG`, allowing it to be used anywhere +/// a standard Rust RNG is expected. +/// +/// `Error` is set to `Infallible` so that the blanket impls for `Rng` and +/// `CryptoRng` apply automatically. wolfSSL RNG failures cause a panic, which +/// is consistent with the infallible contract. +#[cfg(feature = "rand_core")] +impl rand_core::TryRng for RNG { + type Error = core::convert::Infallible; + + fn try_next_u32(&mut self) -> Result { + rand_core::utils::next_word_via_fill(self) + } + + fn try_next_u64(&mut self) -> Result { + rand_core::utils::next_word_via_fill(self) + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> { + self.generate_block(dest).expect("RNG failure"); + Ok(()) + } +} + +/// Mark `RNG` as a cryptographically secure random number generator. +#[cfg(feature = "rand_core")] +impl rand_core::TryCryptoRng for RNG {} + impl Drop for RNG { /// Safely free the underlying wolfSSL RNG context. /// diff --git a/wrapper/rust/wolfssl-wolfcrypt/tests/test_random.rs b/wrapper/rust/wolfssl-wolfcrypt/tests/test_random.rs index 4b94ff091a..bf7f1536a7 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/tests/test_random.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/tests/test_random.rs @@ -97,3 +97,53 @@ fn test_rng_reseed() { let seed = [1u8, 2, 3, 4]; rng.reseed(&seed).expect("Error with reseed()"); } + +#[test] +#[cfg(feature = "rand_core")] +fn test_rng_rand_core_fill_bytes() { + use rand_core::Rng; + let mut rng = 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")] +fn test_rng_rand_core_try_fill_bytes() { + use rand_core::TryRng; + 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")] +fn test_rng_rand_core_next_u32() { + use rand_core::Rng; + let mut rng = 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")] +fn test_rng_rand_core_next_u64() { + use rand_core::Rng; + let mut rng = 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); +} + +#[test] +#[cfg(feature = "rand_core")] +fn test_rng_is_crypto_rng() { + use rand_core::CryptoRng; + fn requires_crypto_rng(_: &R) {} + let rng = RNG::new().expect("Failed to create RNG"); + requires_crypto_rng(&rng); +}