diff --git a/.github/workflows/rust-wrapper.yml b/.github/workflows/rust-wrapper.yml index 634df1d994a..de923792c2f 100644 --- a/.github/workflows/rust-wrapper.yml +++ b/.github/workflows/rust-wrapper.yml @@ -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 diff --git a/wrapper/rust/Makefile b/wrapper/rust/Makefile index ffe2ae8bbf3..8ac8e3e1a11 100644 --- a/wrapper/rust/Makefile +++ b/wrapper/rust/Makefile @@ -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 diff --git a/wrapper/rust/README.md b/wrapper/rust/README.md index 417645f52b6..a93d6e236a6 100644 --- a/wrapper/rust/README.md +++ b/wrapper/rust/README.md @@ -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 | diff --git a/wrapper/rust/include.am b/wrapper/rust/include.am index b70e866e4b3..dd033c65055 100644 --- a/wrapper/rust/include.am +++ b/wrapper/rust/include.am @@ -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 diff --git a/wrapper/rust/wolfssl/Cargo.lock b/wrapper/rust/wolfssl/Cargo.lock index 65491787114..d9a2ea87cdd 100644 --- a/wrapper/rust/wolfssl/Cargo.lock +++ b/wrapper/rust/wolfssl/Cargo.lock @@ -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", +] diff --git a/wrapper/rust/wolfssl/Cargo.toml b/wrapper/rust/wolfssl/Cargo.toml index 5a2b3a0e393..596cfc16d06 100644 --- a/wrapper/rust/wolfssl/Cargo.toml +++ b/wrapper/rust/wolfssl/Cargo.toml @@ -4,3 +4,4 @@ version = "0.1.0" edition = "2024" [dependencies] +wolfssl-sys = { path = "../wolfssl-sys" } diff --git a/wrapper/rust/wolfssl/Makefile b/wrapper/rust/wolfssl/Makefile index 6414a60f6ce..8bc689515f7 100644 --- a/wrapper/rust/wolfssl/Makefile +++ b/wrapper/rust/wolfssl/Makefile @@ -1,6 +1,11 @@ .PHONY: all all: cargo build + cargo doc + +.PHONY: test +test: + cargo test .PHONY: clean clean: diff --git a/wrapper/rust/wolfssl/build.rs b/wrapper/rust/wolfssl/build.rs new file mode 100644 index 00000000000..5720041412b --- /dev/null +++ b/wrapper/rust/wolfssl/build.rs @@ -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(()) +} diff --git a/wrapper/rust/wolfssl/src/lib.rs b/wrapper/rust/wolfssl/src/lib.rs index e69de29bb2d..701d3e0b6da 100644 --- a/wrapper/rust/wolfssl/src/lib.rs +++ b/wrapper/rust/wolfssl/src/lib.rs @@ -0,0 +1 @@ +pub mod wolfcrypt; diff --git a/wrapper/rust/wolfssl/src/wolfcrypt.rs b/wrapper/rust/wolfssl/src/wolfcrypt.rs new file mode 100644 index 00000000000..7bcbfe0e202 --- /dev/null +++ b/wrapper/rust/wolfssl/src/wolfcrypt.rs @@ -0,0 +1 @@ +pub mod random; diff --git a/wrapper/rust/wolfssl/src/wolfcrypt/random.rs b/wrapper/rust/wolfssl/src/wolfcrypt/random.rs new file mode 100644 index 00000000000..55f26015a2a --- /dev/null +++ b/wrapper/rust/wolfssl/src/wolfcrypt/random.rs @@ -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 { + let mut rng: MaybeUninit = 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(nonce: &mut [T]) -> Result { + let ptr = nonce.as_mut_ptr() as *mut u8; + let size: u32 = (nonce.len() * size_of::()) as u32; + let mut rng: MaybeUninit = 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 { + 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(&mut self, buf: &mut [T]) -> Result<(), i32> { + let ptr = buf.as_mut_ptr() as *mut u8; + let size: u32 = (buf.len() * size_of::()) 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); } + } +} diff --git a/wrapper/rust/wolfssl/tests/test_random.rs b/wrapper/rust/wolfssl/tests/test_random.rs new file mode 100644 index 00000000000..c5a9c5b7443 --- /dev/null +++ b/wrapper/rust/wolfssl/tests/test_random.rs @@ -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); +}