Rust crate updates 2026-05-05#10402
Conversation
There was a problem hiding this comment.
Pull request overview
This PR updates the wolfssl-wolfcrypt Rust wrapper to (1) improve RNG lifetime safety across FFI consumers, (2) add RustCrypto trait integrations for BLAKE2 digest/MAC, and (3) extend AEAD support with AES-192 CCM/GCM wrappers, along with corresponding test updates.
Changes:
- Refactors
RNGto own a C-heapWC_RNG*and updates RNG-taking APIs to accept&RNG(plus newset_shared_rng(Arc<RNG>)for consumers that store an RNG pointer internally). - Adds RustCrypto
digest::Digestwrappers (blake2_digest) anddigest::Macwrappers (blake2_mac) for BLAKE2b/BLAKE2s. - Adds AEAD wrappers and tests for AES-192-GCM and AES-192-CCM, and adds
Clonesupport for HMAC MAC types.
Reviewed changes
Copilot reviewed 28 out of 28 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| wrapper/rust/wolfssl-wolfcrypt/tests/test_rsa.rs | Updates tests to new RNG borrowing/sharing patterns (&RNG, set_shared_rng). |
| wrapper/rust/wolfssl-wolfcrypt/tests/test_random.rs | Updates RNG tests to match RNG methods taking &self (no mut). |
| wrapper/rust/wolfssl-wolfcrypt/tests/test_hmac_mac.rs | Adds a clone/forking test to validate cloned HMAC MAC state equivalence. |
| wrapper/rust/wolfssl-wolfcrypt/tests/test_ecc.rs | Updates ECC tests for RNG ownership and shared RNG binding. |
| wrapper/rust/wolfssl-wolfcrypt/tests/test_curve25519.rs | Updates Curve25519 tests for conditional RNG sharing when blinding is enabled. |
| wrapper/rust/wolfssl-wolfcrypt/tests/test_blake2_mac.rs | Adds MAC trait tests for BLAKE2b/BLAKE2s keyed constructions. |
| wrapper/rust/wolfssl-wolfcrypt/tests/test_blake2_digest.rs | Adds Digest trait tests for typed BLAKE2b/BLAKE2s hashers. |
| wrapper/rust/wolfssl-wolfcrypt/tests/test_aes.rs | Adds AES-192-GCM/CCM AEAD roundtrip tests. |
| wrapper/rust/wolfssl-wolfcrypt/src/rsa.rs | Refactors RSA RNG usage (&RNG params, owned/shared RNG binding stored to ensure lifetime). |
| wrapper/rust/wolfssl-wolfcrypt/src/rsa_pkcs1v15.rs | Updates RSA PKCS#1v1.5 signing wrapper to new RNG pointer model. |
| wrapper/rust/wolfssl-wolfcrypt/src/random.rs | Refactors RNG to own WC_RNG* allocated via wc_rng_new_ex, updates methods to take &self. |
| wrapper/rust/wolfssl-wolfcrypt/src/mlkem.rs | Updates ML-KEM APIs to accept &RNG and pass WC_RNG* through FFI. |
| wrapper/rust/wolfssl-wolfcrypt/src/lms.rs | Updates LMS keygen to accept &RNG. |
| wrapper/rust/wolfssl-wolfcrypt/src/lib.rs | Adds alloc support and conditionally exports new BLAKE2 digest/MAC modules. |
| wrapper/rust/wolfssl-wolfcrypt/src/hmac.rs | Implements deep Clone for HMAC via wc_HmacCopy. |
| wrapper/rust/wolfssl-wolfcrypt/src/hmac_mac.rs | Derives Clone for HMAC MAC wrapper types. |
| wrapper/rust/wolfssl-wolfcrypt/src/ed448.rs | Updates Ed448 key generation to accept &RNG. |
| wrapper/rust/wolfssl-wolfcrypt/src/ed25519.rs | Updates Ed25519 key generation to accept &RNG. |
| wrapper/rust/wolfssl-wolfcrypt/src/ecdsa.rs | Adapts ECDSA wrapper FFI calls to ECC key pointer storage changes. |
| wrapper/rust/wolfssl-wolfcrypt/src/ecc.rs | Refactors ECC to store a C-heap ecc_key* and adds owned/shared RNG binding. |
| wrapper/rust/wolfssl-wolfcrypt/src/dilithium.rs | Updates Dilithium APIs to accept &RNG. |
| wrapper/rust/wolfssl-wolfcrypt/src/dh.rs | Updates DH APIs to accept &RNG. |
| wrapper/rust/wolfssl-wolfcrypt/src/curve25519.rs | Updates Curve25519 APIs to accept &RNG, adds RNG ownership/sharing for blinding. |
| wrapper/rust/wolfssl-wolfcrypt/src/blake2_mac.rs | Adds RustCrypto Mac trait wrappers for keyed BLAKE2b/BLAKE2s. |
| wrapper/rust/wolfssl-wolfcrypt/src/blake2_digest.rs | Adds RustCrypto Digest trait wrappers for typed BLAKE2b/BLAKE2s hashers. |
| wrapper/rust/wolfssl-wolfcrypt/src/aes.rs | Adds AES-192 CCM/GCM AEAD wrappers. |
| wrapper/rust/wolfssl-wolfcrypt/Makefile | Enables the new alloc feature in the Makefile feature set. |
| wrapper/rust/wolfssl-wolfcrypt/Cargo.toml | Replaces std feature with alloc and keeps feature list in sync with new APIs/modules. |
Comments suppressed due to low confidence (1)
wrapper/rust/wolfssl-wolfcrypt/Cargo.toml:22
- This change removes the previously exported
stdfeature and adds a newallocfeature. Together with the public API signature changes in this PR (e.g.,set_rng/generatenow takingRNGor&RNGinstead of&mut RNG), this is a semver-breaking change for a 1.x crate. Consider either (a) bumping the crate major version, or (b) keepingstdas a backwards-compatible feature alias toallocand providing compatibility shims where practical.
[features]
alloc = []
rand_core = ["dep:rand_core"]
aead = ["dep:aead"]
cipher = ["dep:cipher"]
mac = ["digest/mac"]
digest = ["dep:digest"]
signature = ["dep:signature"]
password-hash = ["dep:password-hash", "password-hash/phc"]
kem = ["dep:kem", "hybrid-array/extra-sizes"]
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
bb8cfde to
f8f17d0
Compare
|
retest this please (build removed) |
|
retest this please (Finished: FAILURE) |
|
retest this please (fips repo failures) |
1 similar comment
|
retest this please (fips repo failures) |
|
retest this please (Build 'wolfSSL/PRB-fips-repo-and-harness-test-v3-part2' failed with result: FAILURE) |
MarkAtwood
left a comment
There was a problem hiding this comment.
Reviewed the seven logical chunks (RNG memory-safety refactor, ECC heap allocation + double-init fix + empty-slice tests, AES-192 additions, BLAKE2 modules, HMAC Clone, doc/version changes) with FFI checks against the wolfCrypt C sources.
Headline: no P0 (UB / wrong-crypto) regressions introduced. The three architectural changes — RngHandle::Owned(RNG) | Shared(Rc<RNG>), C-heap allocation of ecc_key, manual wc_HmacCopy-based Clone — are sound and address real previous issues. Drop ordering, double-set_rng ordering, and MaybeUninit/assume_init flow all check out.
That said, there is one concrete correctness blocker, one doc-test breakage, and one version-bump item I'd want addressed before merge.
Blocker
ECC::import_unsigned has no length validation — empty slice triggers OOB read in C
wrapper/rust/wolfssl-wolfcrypt/src/ecc.rs:987-1004
pub fn import_unsigned(qx: &[u8], qy: &[u8], d: &[u8], curve_id: i32, ...) -> Result<Self, i32> {
...
let rc = unsafe {
sys::wc_ecc_import_unsigned(ecc.wc_ecc_key, qx.as_ptr(),
qy.as_ptr(), d.as_ptr(), curve_id)
};The C path wc_ecc_import_unsigned → _ecc_import_raw_private (wolfcrypt/src/ecc.c:11898-11904) calls:
err = mp_read_unsigned_bin(key->pubkey.x, (const byte*)qx, (word32)key->dp->size);It reads key->dp->size bytes (32 for P-256, 48 for P-384, 66 for P-521) regardless of the Rust slice length. The sp_read_unsigned_bin guard at wolfcrypt/src/sp_int.c:18177 only catches (in == NULL) && (inSz > 0). An empty Rust slice's as_ptr() returns a non-null dangling pointer, so the guard is bypassed and the C side reads invalid memory → UB at the FFI boundary.
This is pre-existing, but commit c6d14218d ("test more empty slices in test_ecc") explicitly added empty-slice rejection tests for import_raw and import_raw_ex — which are validated in Rust via the trailing-NUL + is_empty check at ecc.rs:866-867,923-924 — while conspicuously skipping import_unsigned. The natural reviewer expectation given the commit's title is that import_unsigned got the same treatment.
Same audit applies to priv_buf in import_private_key / import_private_key_ex (ecc.rs:746-766, 813-833).
Suggested fix: validate each slice length against wc_ecc_get_curve_size_from_id(curve_id) in the Rust wrapper and return BAD_FUNC_ARG on short input. Add a regression test mirroring the c6d14218d pattern.
Should fix in this PR
Five RSA doc tests miss feature = "alloc" in their cfg gates
wrapper/rust/wolfssl-wolfcrypt/src/rsa.rs
These doc-test blocks use std::rc::Rc::new(...) + rsa.set_shared_rng(...):
| Lines | Gate |
|---|---|
30-57 (module-level example) |
#[cfg(random)] |
871-901 (pss_sign) |
#[cfg(all(random, rsa_pss))] |
934-963 (pss_check_padding) |
#[cfg(all(random, rsa_pss, rsa_const_api))] |
1000-1029 (pss_verify) |
same |
1071-1100 (pss_verify_check) |
same |
But set_shared_rng is #[cfg(all(random, feature = "alloc"))]. cargo test --doc --features random (without --features alloc) will fail to compile these.
The correctly-gated pattern is visible at rsa.rs:148-175 and rsa.rs:1284-1310, both of which include feature = "alloc". Either add feature = "alloc" to the five gates above, or rewrite to use owned RNG via set_rng (the approach 4ace46f2e used for the set_rng doc test at :1197-1223).
Bump crate version and document the API breaks
wrapper/rust/wolfssl-wolfcrypt/Cargo.toml, README.md / PR description
Per the Cargo SemVer reference, both of these are MAJOR-bump changes:
- Public function signature change:
set_rng(&mut self, &mut RNG)→set_rng(&mut self, RNG)onECC,RSA,Curve25519Key. Callers passing&mut rngget a hard compile error. - Cargo feature rename:
std = []→alloc = []. Anyone withfeatures = ["std"]gets a hard build failure. (Listed verbatim under MAJOR in the Cargo reference.)
Given the crate is narrow-audience right now, 1.3.0 with an explicit CHANGELOG / PR-description note is pragmatic and honest. Bumping to 2.0.0 would be the strict-SemVer answer. Silently going to 1.3.0 without flagging the breaks would be the worst option.
Suggested CHANGELOG bullets:
## 1.3.0 — 2026-05-NN
### Breaking changes
- `ECC::set_rng`, `RSA::set_rng`, `Curve25519Key::set_rng` now take owned
`RNG` instead of `&mut RNG`. The previous borrow-based API was unsound:
the C library cached the raw `*WC_RNG` past the borrow's lifetime. New
`set_shared_rng(Rc<RNG>)` is also available behind `feature = "alloc"`.
- Cargo feature `std` has been renamed to `alloc`. Update any
`features = ["std"]` in your Cargo.toml.
- `&mut RNG` parameters on `generate*` / `make_pub*` / `sign_*` are now `&RNG`.
(Source-compatible via reborrow coercion.)
### Added
- `AesGcm` / `AesCcm` variants for 192-bit keys.
- `blake2_digest` module (typed BLAKE2b-{256,384,512} and BLAKE2s-{128,192,256}).
- `blake2_mac` module (typed BLAKE2b/2s MACs).
- `Clone` impl for HMAC types (deep-copy via `wc_HmacCopy`).
- `set_shared_rng(Rc<RNG>)` for sharing an RNG across multiple consumers.
### Fixed
- Avoid double `wc_ecc_init_ex` call (was leaking async device context under
WOLFSSL_ASYNC_CRYPT && WC_ASYNC_ENABLE_ECC).
- ECC key is now C-heap-allocated to remain valid across Rust moves under
non-default math configurations.
Also: minimum wolfSSL version is correctly documented as 5.9.0 in the README. wc_HmacCopy is the binding constraint (introduced in v5.9.0); wc_rng_new_ex is older (v5.7.0).
Advisory (track for follow-up, not blocking)
Inline vs. heap inconsistency between ECC and RSA/DH/Curve25519
Commit 113b6b7ec's rationale ("internal pointers breaking if Rust moves the ECC struct with some build configurations") implies certain wolfCrypt configs put self-pointers in C structs. ECC was migrated to a C-heap pointer; RSA.wc_rsakey, DH.wc_dhkey, and Curve25519Key.wc_key remain inline. In the default bindgen config these are move-safe, but if the same configs that affected ECC (non-SP integer math, WOLFSSL_ASYNC_CRYPT, FIPS, hardware-CB) also affect the other key types, they carry the same latent UB this PR just fixed for ECC. Worth a follow-up audit and/or a // Safety: comment in each consumer struct recording the move-safety assumption.
Nits (not blocking)
src/ecc.rs:405— the comment "wc_ecc_key_new()always initializes the key with INVALID_DEVID" is wrong on CAAM builds (ecc.c:6174-6180initializes withWOLFSSL_CAAM_DEVID). The subsequent(*key).devId = dev_id;under#[cfg(wolf_crypto_cb)]will then clobber the CAAM default if the caller passeddev_id: None.src/random.rs:67-70— theunsafe impl Send for RNGsafety comment claims the pointer is non-null but it isn't asserted. Adebug_assert!(!wc_rng.is_null())after the success branch at:137and:197would document and enforce the invariant.src/random.rs:60-62— DRBG state is mutated through&self. Sound because the state lives behind an opaque*mut WC_RNG, butUnsafeCell<*mut WC_RNG>would make the interior-mutability intent explicit and defeat any future compiler improvement that might re-deriveSyncor hoist reads.src/ecc.rs:866-867, 923-924— the null-terminator check accepts buffers with embedded NULs (only the last byte is checked).XSTRLEN/mp_read_radixon the C side stop at the first NUL, so embedded NULs cause silent truncation. Accepting&CStrwould be cleaner.src/rsa.rs:1208-1212— theset_rngdoc test (fixed in4ace46f2e) does callset_rng, but the immediately-followingpublic_encryptuses a separateenc_rngargument, so the bound RNG isn't exercised here. Only the secondset_rngat:1217+private_decryptat:1219actually depends on the bound RNG (underWC_RSA_BLINDING). The firstset_rngcall is dead in this example.src/ecdsa.rs:177-185and manytests/test_*.rssites — stalelet mut rng = ...+&mut rngreborrows left from before the&RNGmigration. Compiles via reborrow coercion, just noisy. Same cleanup as commit1a441c910, just not applied everywhere.tests/test_hmac_mac.rs:43-62— the Clone test would be stronger with divergent post-clone updates (both branches feedsuffix, so the test verifies "clone preserves state" + "clone doesn't poison original" but not "diverged updates produce different MACs"). The existing test catches the most likely shallow-copy bug, so this is a nice-to-have.
What the PR does right (worth calling out)
- RNG ownership refactor: the old
&'a mut WC_RNGborrow was unsound (C cached the raw pointer past the borrow lifetime). The new design heap-allocatesWC_RNGviawc_rng_new_ex(pointer stable across moves), correctly picksRcoverArc(WC_RNG is not internally synchronized), splitsset_rng(RNG)fromset_shared_rng(Rc<RNG>), and gets Drop ordering right — consumer free runs before RNG free, sowc_FreeRsaKey/wc_ecc_key_freecan still safely readkey->rng. Double-set_rngordering is also correct (C pointer replaced before oldRngHandledrops). - ECC heap allocation: switching to
*mut sys::ecc_keyviawc_ecc_key_newis the right fix for move-unsafety under configs wheremp_int.dppoints back into the same struct.Pinisn't needed because the Rust handle is a pointer to a never-moving C-heap object. - Double-init fix: real bug —
wc_ecc_key_newalready callswc_ecc_init_exinternally; the explicit second callXMEMSETs the struct, blowing away heap context and leaking async device contexts inWOLFSSL_ASYNC_CRYPT && WC_ASYNC_ENABLE_ECCbuilds. - HMAC
Clone: uses a manual impl delegating towc_HmacCopyinstead of bitwise#[derive(Clone)]— the latter would be unsound for hash configs with internal pointers.MaybeUninit::assume_initis correctly placed after the error check.
- store pointer to WC_RNG instead of full struct - enforce RNG is not dropped before consumer structs The C library stores a pointer via the set_rng() methods on a few structs (e.g. RSA). This change holds a reference (or instance) of RNG within the consumer structs to ensure it is kept alive if set_rng (or now set_shared_rng) is used.
This fixes internal pointers breaking if Rust moves the ECC struct (with some build configurations).
WC_RNG has no internal locking so it is not safe to share a single WC_RNG across threads without locking.
c6d1421 to
8c11a8e
Compare
I added the slice length checks to The version will be bumped later outside of this PR when preparing for the crate release. |
Description
This PR includes several Fenrir fixes and some new functionality I found missing during my integration tests with boringtun.
See the commits for individual descriptions of each change.
Testing
Unit/CI tests.
Checklist