Merge pull request #9212 from holtrop/rust-wc-random

Rust wrapper: add wolfssl::wolfcrypt::random module
This commit is contained in:
JacobBarthelmeh
2025-09-25 09:10:07 -06:00
committed by GitHub
12 changed files with 552 additions and 0 deletions

View File

@@ -28,3 +28,6 @@ jobs:
- name: Build Rust Wrapper
working-directory: wolfssl
run: make -C wrapper/rust
- name: Run Rust Wrapper Tests
working-directory: wolfssl
run: make -C wrapper/rust test

View File

@@ -3,6 +3,10 @@ all:
+$(MAKE) -C wolfssl-sys
+$(MAKE) -C wolfssl
.PHONY: test
test:
+$(MAKE) -C wolfssl test
.PHONY: clean
clean:
+$(MAKE) -C wolfssl-sys clean

View File

@@ -8,6 +8,10 @@ Then build the wolfssl Rust wrapper with:
make -C wrapper/rust
Run tests with:
make -C wrapper/rust test
## Repository Directory Structure
| Repository Directory | Description |

View File

@@ -13,4 +13,8 @@ EXTRA_DIST += wrapper/rust/wolfssl-sys/src/lib.rs
EXTRA_DIST += wrapper/rust/wolfssl/Cargo.lock
EXTRA_DIST += wrapper/rust/wolfssl/Cargo.toml
EXTRA_DIST += wrapper/rust/wolfssl/Makefile
EXTRA_DIST += wrapper/rust/wolfssl/build.rs
EXTRA_DIST += wrapper/rust/wolfssl/src/lib.rs
EXTRA_DIST += wrapper/rust/wolfssl/src/wolfcrypt.rs
EXTRA_DIST += wrapper/rust/wolfssl/src/wolfcrypt/random.rs
EXTRA_DIST += wrapper/rust/wolfssl/tests/test_random.rs

View File

@@ -2,6 +2,299 @@
# It is not intended for manual editing.
version = 4
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "bindgen"
version = "0.72.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
dependencies = [
"bitflags",
"cexpr",
"clang-sys",
"itertools",
"log",
"prettyplease",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"syn",
]
[[package]]
name = "bitflags"
version = "2.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
[[package]]
name = "cexpr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
"nom",
]
[[package]]
name = "cfg-if"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
[[package]]
name = "clang-sys"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "glob"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]]
name = "libc"
version = "0.2.175"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
[[package]]
name = "libloading"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
dependencies = [
"cfg-if",
"windows-targets",
]
[[package]]
name = "log"
version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
[[package]]
name = "memchr"
version = "2.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "prettyplease"
version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [
"proc-macro2",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
[[package]]
name = "rustc-hash"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "syn"
version = "2.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
[[package]]
name = "windows-link"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
[[package]]
name = "windows-targets"
version = "0.53.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
dependencies = [
"windows-link",
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
[[package]]
name = "windows_i686_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
[[package]]
name = "windows_i686_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
[[package]]
name = "wolfssl"
version = "0.1.0"
dependencies = [
"wolfssl-sys",
]
[[package]]
name = "wolfssl-sys"
version = "0.1.0"
dependencies = [
"bindgen",
]

View File

@@ -4,3 +4,4 @@ version = "0.1.0"
edition = "2024"
[dependencies]
wolfssl-sys = { path = "../wolfssl-sys" }

View File

@@ -1,6 +1,11 @@
.PHONY: all
all:
cargo build
cargo doc
.PHONY: test
test:
cargo test
.PHONY: clean
clean:

View File

@@ -0,0 +1,32 @@
use std::io::Result;
/// Perform crate build.
fn main() {
if let Err(e) = run_build() {
eprintln!("Build failed: {}", e);
std::process::exit(1);
}
}
/// Perform all build steps.
///
/// Returns `Ok(())` if successful, or an error if any step fails.
fn run_build() -> Result<()> {
setup_wolfssl_link()?;
Ok(())
}
/// Instruct cargo to link against wolfssl C library
///
/// Returns `Ok(())` if successful, or an error if any step fails.
fn setup_wolfssl_link() -> Result<()> {
let wrapper_dir = std::env::current_dir()?.display().to_string();
let wolfssl_base_dir = format!("{}/../../..", wrapper_dir);
let wolfssl_lib_dir = format!("{}/src/.libs", wolfssl_base_dir);
println!("cargo:rustc-link-search={}", wolfssl_lib_dir);
println!("cargo:rustc-link-lib=wolfssl");
println!("cargo:rustc-link-arg=-Wl,-rpath,{}", wolfssl_lib_dir);
Ok(())
}

View File

@@ -0,0 +1 @@
pub mod wolfcrypt;

View File

@@ -0,0 +1 @@
pub mod random;

View File

@@ -0,0 +1,146 @@
/*!
This crate provides a Rust wrapper for the wolfCrypt library's random number
generator (RNG).
It leverages the `wolfssl-sys` crate for low-level FFI bindings, encapsulating
the raw C functions in a memory-safe and easy-to-use Rust API.
The primary component is the `RNG` struct, which manages the lifecycle of a
wolfSSL `WC_RNG` object. It ensures proper initialization and deallocation.
# Examples
```rust
use wolfssl::wolfcrypt::random::RNG;
fn main() {
// Create a RNG instance.
let mut rng = RNG::new().expect("Failed to create RNG");
// Generate a single random byte value.
let byte = rng.generate_byte().expect("Failed to generate a single byte");
// Generate a random block.
let mut buffer = [0u32; 8];
rng.generate_block(&mut buffer).expect("Failed to generate a block");
}
```
*/
use wolfssl_sys as ws;
use std::mem::{size_of, MaybeUninit};
/// A cryptographically secure random number generator based on the wolfSSL
/// library.
///
/// This struct wraps the wolfssl `WC_RNG` type, providing a high-level API
/// for generating random bytes and blocks of data. The `Drop` implementation
/// ensures that the underlying wolfSSL RNG context is correctly freed when the
/// `RNG` struct goes out of scope, preventing memory leaks.
pub struct RNG {
wc_rng: ws::WC_RNG,
}
impl RNG {
/// Initialize a new `RNG` instance.
///
/// This function wraps the wolfssl library function `wc_InitRng`, which
/// performs the necessary initialization for the RNG context.
///
/// # Returns
///
/// A Result which is Ok(RNG) on success or an Err containing the wolfSSL
/// library return code on failure.
pub fn new() -> Result<Self, i32> {
let mut rng: MaybeUninit<RNG> = MaybeUninit::uninit();
let rc = unsafe { ws::wc_InitRng(&mut (*rng.as_mut_ptr()).wc_rng) };
if rc == 0 {
let rng = unsafe { rng.assume_init() };
Ok(rng)
} else {
Err(rc)
}
}
/// Initialize a new `RNG` instance and provide a nonce input.
///
/// This function wraps the wolfssl library function `wc_InitRngNonce`,
/// which performs the necessary initialization for the RNG context and
/// accepts a nonce input buffer.
///
/// # Returns
///
/// A Result which is Ok(RNG) on success or an Err containing the wolfSSL
/// library return code on failure.
pub fn new_with_nonce<T>(nonce: &mut [T]) -> Result<Self, i32> {
let ptr = nonce.as_mut_ptr() as *mut u8;
let size: u32 = (nonce.len() * size_of::<T>()) as u32;
let mut rng: MaybeUninit<RNG> = MaybeUninit::uninit();
let rc = unsafe {
ws::wc_InitRngNonce(&mut (*rng.as_mut_ptr()).wc_rng, ptr, size)
};
if rc == 0 {
let rng = unsafe { rng.assume_init() };
Ok(rng)
} else {
Err(rc)
}
}
/// Generate a single cryptographically secure random byte.
///
/// This method calls the `wc_RNG_GenerateByte` wolfSSL library function to
/// retrieve a random byte from the underlying wolfSSL RNG context.
///
/// # Returns
///
/// A `Result` which is `Ok(u8)` containing the random byte on success or
/// an `Err` with the wolfssl library return code on failure.
pub fn generate_byte(&mut self) -> Result<u8, i32> {
let mut b: u8 = 0;
let rc = unsafe { ws::wc_RNG_GenerateByte(&mut self.wc_rng, &mut b) };
if rc == 0 {
Ok(b)
} else {
Err(rc)
}
}
/// Fill a mutable slice with cryptographically secure random data.
///
/// This is a generic function that can fill a slice of any type `T` with
/// random bytes. It calculates the total size of the slice in bytes and
/// calls the underlying `wc_RNG_GenerateBlock` wolfssl library function.
///
/// # Parameters
///
/// * `buf`: A mutable slice of any type `T` to be filled with random data.
///
/// # Returns
///
/// A `Result` which is `Ok(())` on success or an `Err` with the wolfssl
/// library return code on failure.
pub fn generate_block<T>(&mut self, buf: &mut [T]) -> Result<(), i32> {
let ptr = buf.as_mut_ptr() as *mut u8;
let size: u32 = (buf.len() * size_of::<T>()) as u32;
let rc = unsafe { ws::wc_RNG_GenerateBlock(&mut self.wc_rng, ptr, size) };
if rc == 0 {
Ok(())
} else {
Err(rc)
}
}
}
impl Drop for RNG {
/// Safely free the underlying wolfSSL RNG context.
///
/// This calls the `wc_FreeRng` wolfssl library function.
///
/// The Rust Drop trait guarantees that this method is called when the RNG
/// struct goes out of scope, automatically cleaning up resources and
/// preventing memory leaks.
fn drop(&mut self) {
unsafe { ws::wc_FreeRng(&mut self.wc_rng); }
}
}

View File

@@ -0,0 +1,58 @@
use wolfssl::wolfcrypt::random::RNG;
// Test that RNG::new() returns successfully and that drop() does not panic.
#[test]
fn test_rng_new_and_drop() {
let _rng = RNG::new().expect("Failed to create RNG");
}
// Test that RNG::new_with_nonce() returns successfully and that drop() does
// not panic.
#[test]
fn test_rng_new_with_nonce_and_drop() {
let mut nonce = [1, 2, 3, 4];
let _rng = RNG::new_with_nonce(&mut nonce).expect("Failed to create RNG");
}
// Test that generate_byte() returns random values.
#[test]
fn test_rng_generate_byte() {
// Since a single 0x00 or 0xFF could occur occasionally, we'll combine four
// bytes into a u32 and make sure they aren't all 0x00 or all 0xFF.
let mut rng = RNG::new().expect("Failed to create RNG");
let mut v: u32 = 0;
for _i in 0..4 {
let byte = rng.generate_byte().expect("Failed to generate a single byte");
v = (v << 8) | (byte as u32);
}
assert_ne!(v, 0u32);
assert_ne!(v, 0xFFFF_FFFFu32);
}
// Test that generate_block works for a slice of u8.
#[test]
fn test_rng_generate_block_u8() {
let mut rng = RNG::new().expect("Failed to create RNG");
let mut buffer = [0u8; 32];
rng.generate_block(&mut buffer).expect("Failed to generate a block of bytes");
// Check if the buffer has been modified from its initial state.
let all_zeros = [0u8; 32];
assert_ne!(buffer, all_zeros);
}
// Test that generate_block works for a slice of u32.
#[test]
fn test_rng_generate_block_u32() {
let mut rng = RNG::new().expect("Failed to create RNG");
let mut buffer = [0u32; 8];
rng.generate_block(&mut buffer).expect("Failed to generate a block of u32");
// Check if the buffer has been modified.
let all_zeros = [0u32; 8];
assert_ne!(buffer, all_zeros);
// Check that the last u32 is populated so the size of the buffer was
// calculated properly.
assert_ne!(buffer[buffer.len() - 1], 0u32);
assert_ne!(buffer[buffer.len() - 1], 0xFFFF_FFFFu32);
}