Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 50 additions & 11 deletions wrapper/rust/wolfssl-wolfcrypt/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -391,19 +391,39 @@ const FIPS_RENAMES: &[(&str, &str)] = &[
("wc_PRF_TLS", "wc_PRF_TLSv12_fips"),
];

/// Renames done only by older FIPS modules (v5), used when the module has no
/// `<name>_fips` symbol for the standard name.
const FIPS_V5_RENAMES: &[(&str, &str)] = &[
/* v5 exports the external IV encrypt under the one shot's FIPS name */
("wc_AesGcmEncrypt_ex", "wc_AesGcmEncrypt_fips"),
];

/// The renames that apply to the FIPS module whose bindings are `binding`.
///
/// Computed once and cached, as a build has a single set of bindings.
fn fips_renames(binding: &str) -> &'static [(&'static str, &'static str)] {
static RENAMES: OnceLock<Vec<(&str, &str)>> = OnceLock::new();
RENAMES.get_or_init(|| {
FIPS_RENAMES.iter().chain(FIPS_V5_RENAMES.iter().filter(|(std_name, _)| {
let pattern = format!(r"pub fn {}_fips\s*\(", regex::escape(std_name));
!Regex::new(&pattern).unwrap().is_match(binding)
})).copied().collect()
})
}

/// Returns the name the FIPS module exports `base_name` under, for the
/// functions listed in `FIPS_RENAMES`, or `None` for every other function
/// (whose FIPS name is `<base_name>_fips`).
fn fips_rename(base_name: &str) -> Option<&'static str> {
FIPS_RENAMES.iter()
/// functions listed in `FIPS_RENAMES` and `FIPS_V5_RENAMES`, or `None` for
/// every other function (whose FIPS name is `<base_name>_fips`).
fn fips_rename(binding: &str, base_name: &str) -> Option<&'static str> {
fips_renames(binding).iter()
.find(|(std_name, _)| *std_name == base_name)
.map(|(_, fips_name)| *fips_name)
}

/// Returns the standard wolfCrypt name of the renamed FIPS symbol
/// `fips_name`, or `None` if the FIPS module does not rename it.
fn fips_rename_base(fips_name: &str) -> Option<&'static str> {
FIPS_RENAMES.iter()
fn fips_rename_base(binding: &str, fips_name: &str) -> Option<&'static str> {
fips_renames(binding).iter()
.find(|(_, fips)| *fips == fips_name)
.map(|(std_name, _)| *std_name)
}
Expand Down Expand Up @@ -434,7 +454,7 @@ fn generate_fips_aliases() -> Result<()> {

// Handle the functions the FIPS module renames rather than simply
// suffixing with _fips.
if let Some(renamed_base_name) = fips_rename_base(&fips_name) {
if let Some(renamed_base_name) = fips_rename_base(&binding, &fips_name) {
base_name = renamed_base_name;
}

Expand Down Expand Up @@ -514,7 +534,7 @@ fn has_symbol(binding: &str, function_name: &str) -> bool {
// too: the generated aliases make it callable under its standard name, so
// missing it here would compile out wrapper code the library supports.
let mut pattern = format!(r"\b{}(_fips)?\b", regex::escape(function_name));
if let Some(fips_name) = fips_rename(function_name) {
if let Some(fips_name) = fips_rename(binding, function_name) {
pattern.push_str(&format!(r"|\b{}\b", regex::escape(fips_name)));
}
let re = match Regex::new(&pattern) {
Expand Down Expand Up @@ -601,7 +621,8 @@ fn scan_cfg() -> Result<()> {
/* ecc */
check_cfg(&binding, "wc_ecc_init", "ecc");
check_cfg(&binding, "wc_ecc_key_new_ex", "ecc_key_new_ex");
check_cfg(&binding, "wc_ecc_export_point_der_compressed", "ecc_comp_key");
check_cfg_if(&binding, "wc_ecc_export_point_der_compressed", "ecc_comp_key",
has_symbol(&binding, "WOLFSSL_RUST_HAVE_COMP_KEY"));
check_cfg(&binding, "wc_ecc_shared_secret", "ecc_dh");
check_cfg(&binding, "wc_ecc_sign_hash", "ecc_sign");
check_cfg(&binding, "wc_ecc_verify_hash", "ecc_verify");
Expand Down Expand Up @@ -639,17 +660,34 @@ fn scan_cfg() -> Result<()> {
check_cfg(&binding, "wc_ed448_verify_msg_init", "ed448_streaming_verify");

/* fips */
if check_cfg(&binding, "wc_SetSeed_Cb_fips", "fips") {
let fips = check_cfg(&binding, "wc_SetSeed_Cb_fips", "fips");
if fips {
// Crates that depend on this one see it as DEP_WOLFSSL_FIPS.
println!("cargo::metadata=FIPS=1");
}
// FIPS v5.2.3 builds without RSA blinding, so it has no wc_RsaSetRNG(),
// and keeps wc_RNG_DRBG_Reseed() internal though the header declares it.
println!("cargo::rustc-check-cfg=cfg(fips_v5_2_3)");
let version = |name: &str| {
Regex::new(&format!(r"pub const {}: u32 = (\d+);", name)).unwrap()
.captures(&binding).and_then(|cap| cap[1].parse::<u32>().ok())
};
if fips && version("HAVE_FIPS_VERSION_MAJOR") == Some(5)
&& version("HAVE_FIPS_VERSION_MINOR") == Some(2)
&& version("HAVE_FIPS_VERSION_PATCH") == Some(3) {
println!("cargo::rustc-cfg=fips_v5_2_3");
// Crates that depend on this one see it as DEP_WOLFSSL_FIPS_V5_2_3.
println!("cargo::metadata=FIPS_V5_2_3=1");
}

/* hkdf */
check_cfg(&binding, "wc_HKDF_Extract_ex", "hkdf");
check_cfg(&binding, "wc_HKDF_Extract", "hkdf");
check_cfg(&binding, "wc_HKDF_Extract_ex", "hkdf_ex");

/* hmac */
check_cfg(&binding, "wc_HmacSetKey", "hmac");
check_cfg(&binding, "wc_HmacSetKey_ex", "hmac_setkey_ex");
check_cfg(&binding, "wc_HmacCopy", "hmac_copy");

/* kdf */
check_cfg(&binding, "wc_PBKDF2", "kdf_pbkdf2");
Expand All @@ -665,6 +703,7 @@ fn scan_cfg() -> Result<()> {
/* random */
check_cfg(&binding, "wc_RNG_DRBG_Reseed", "random_hashdrbg");
check_cfg(&binding, "wc_InitRng", "random");
check_cfg(&binding, "wc_rng_new_ex", "rng_new_ex");

// When WOLFSSL_NO_MALLOC is set without WOLFSSL_STATIC_MEMORY, the
// WC_RNG struct contains an inline `drbg_data` field and wolfCrypt sets
Expand Down
4 changes: 4 additions & 0 deletions wrapper/rust/wolfssl-wolfcrypt/headers.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
#if !defined(NO_RSA) && !defined(WC_NO_RSA_OAEP)
#define WOLFSSL_RUST_HAVE_RSA_OAEP 1
#endif
/* FIPS v5 headers declare the compressed ECC export without HAVE_COMP_KEY. */
#ifdef HAVE_COMP_KEY
#define WOLFSSL_RUST_HAVE_COMP_KEY 1
#endif

/* Sentinels for wolfSSL build configurations that the Rust wrapper does not
* support. build.rs fails the build if any of these appear in the generated
Expand Down
61 changes: 59 additions & 2 deletions wrapper/rust/wolfssl-wolfcrypt/src/hkdf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ pub fn hkdf_extract_ex(typ: i32, salt: Option<&[u8]>, key: &[u8], out: &mut [u8]
None => sys::INVALID_DEVID,
};
let rc = unsafe {
sys::wc_HKDF_Extract_ex(typ, salt_ptr, salt_size,
hkdf_extract_raw(typ, salt_ptr, salt_size,
key.as_ptr(), key_size, out.as_mut_ptr(), heap, dev_id)
};
if rc != 0 {
Expand Down Expand Up @@ -211,7 +211,7 @@ pub fn hkdf_expand_ex(typ: i32, key: &[u8], info: Option<&[u8]>, out: &mut [u8],
None => sys::INVALID_DEVID,
};
let rc = unsafe {
sys::wc_HKDF_Expand_ex(typ, key.as_ptr(), key_size,
hkdf_expand_raw(typ, key.as_ptr(), key_size,
info_ptr, info_size, out.as_mut_ptr(), out_size, heap, dev_id)
};
if rc != 0 {
Expand Down Expand Up @@ -277,3 +277,60 @@ pub fn hkdf(typ: i32, key: &[u8], salt: Option<&[u8]>, info: Option<&[u8]>, out:
}
Ok(())
}

/// `wc_HKDF_Extract_ex()`, or on libraries without it (FIPS v5) the older
/// `wc_HKDF_Extract()`, which takes no heap hint or device ID, so either is
/// refused there.
///
/// # Safety
///
/// Same as `wc_HKDF_Extract_ex()`.
#[allow(clippy::too_many_arguments)]
unsafe fn hkdf_extract_raw(typ: i32, salt: *const u8, salt_size: u32,
key: *const u8, key_size: u32, out: *mut u8,
heap: *mut core::ffi::c_void, dev_id: i32) -> i32 {
#[cfg(hkdf_ex)]
{
unsafe {
sys::wc_HKDF_Extract_ex(typ, salt, salt_size, key, key_size, out,
heap, dev_id)
}
}
#[cfg(not(hkdf_ex))]
{
if !heap.is_null() || dev_id != sys::INVALID_DEVID {
return sys::wolfCrypt_ErrorCodes_BAD_FUNC_ARG;
}
unsafe { sys::wc_HKDF_Extract(typ, salt, salt_size, key, key_size, out) }
}
}

/// `wc_HKDF_Expand_ex()`, or on libraries without it (FIPS v5) the older
/// `wc_HKDF_Expand()`, which takes no heap hint or device ID, so either is
/// refused there.
///
/// # Safety
///
/// Same as `wc_HKDF_Expand_ex()`.
#[allow(clippy::too_many_arguments)]
unsafe fn hkdf_expand_raw(typ: i32, key: *const u8, key_size: u32,
info: *const u8, info_size: u32, out: *mut u8, out_size: u32,
heap: *mut core::ffi::c_void, dev_id: i32) -> i32 {
#[cfg(hkdf_ex)]
{
unsafe {
sys::wc_HKDF_Expand_ex(typ, key, key_size, info, info_size, out,
out_size, heap, dev_id)
}
}
#[cfg(not(hkdf_ex))]
{
if !heap.is_null() || dev_id != sys::INVALID_DEVID {
return sys::wolfCrypt_ErrorCodes_BAD_FUNC_ARG;
}
unsafe {
sys::wc_HKDF_Expand(typ, key, key_size, info, info_size, out,
out_size)
}
}
}
3 changes: 3 additions & 0 deletions wrapper/rust/wolfssl-wolfcrypt/src/hmac.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,7 @@ impl HMAC {
/// let mut forked = hmac.copy().expect("Error with copy()");
/// forked.update(b"suffix").expect("Error with update()");
/// ```
#[cfg(hmac_copy)]
pub fn copy(&mut self) -> Result<Self, i32> {
/* &mut self, so a writable pointer to the context is available
* directly. */
Expand All @@ -374,6 +375,7 @@ impl HMAC {
/// `src` must point to an initialized `Hmac` context that nothing else is
/// accessing for the duration of the call; `wc_HmacCopy()` may write
/// through it.
#[cfg(hmac_copy)]
unsafe fn copy_from(src: *mut sys::Hmac) -> Result<Self, i32> {
/* wc_HmacCopy() fills dst with XMEMCPY() before touching it, so an
* uninitialized destination is fine here (unlike wc_Sha256Copy(),
Expand All @@ -392,6 +394,7 @@ impl HMAC {
}
}

#[cfg(hmac_copy)]
impl Clone for HMAC {
/// Deep-copy the HMAC state via `wc_HmacCopy()`.
///
Expand Down
2 changes: 1 addition & 1 deletion wrapper/rust/wolfssl-wolfcrypt/src/hmac_mac.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ macro_rules! impl_hmac_mac {
$name:ident, hmac_type = $hmac_type:expr, key = $key_size:ty, out = $out_size:ty
) => {
$(#[$attr])*
#[derive(Clone)]
#[cfg_attr(hmac_copy, derive(Clone))]
pub struct $name {
hmac: crate::hmac::HMAC,
}
Expand Down
32 changes: 29 additions & 3 deletions wrapper/rust/wolfssl-wolfcrypt/src/random.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ impl RNG {
None => sys::INVALID_DEVID,
};
let rc = unsafe {
sys::wc_rng_new_ex(&mut wc_rng, core::ptr::null_mut(), 0, heap, dev_id)
rng_new_ex(&mut wc_rng, core::ptr::null_mut(), 0, heap, dev_id)
};
if rc == 0 {
Ok(RNG {wc_rng})
Expand Down Expand Up @@ -191,7 +191,7 @@ impl RNG {
None => sys::INVALID_DEVID,
};
let rc = unsafe {
sys::wc_rng_new_ex(&mut wc_rng, ptr, size, heap, dev_id)
rng_new_ex(&mut wc_rng, ptr, size, heap, dev_id)
};
if rc == 0 {
Ok(RNG {wc_rng})
Expand Down Expand Up @@ -404,7 +404,7 @@ impl RNG {
/// let seed = [1u8, 2, 3, 4];
/// rng.reseed(&seed).expect("Error with reseed()");
/// ```
#[cfg(random_hashdrbg)]
#[cfg(all(random_hashdrbg, not(fips_v5_2_3)))]
pub fn reseed(&self, seed: &[u8]) -> Result<(), i32> {
let seed_size = crate::buffer_len_to_u32(seed.len())?;
let rc = unsafe {
Expand Down Expand Up @@ -496,6 +496,32 @@ impl rand_core::TryRng for RNG {
#[cfg(feature = "rand_core")]
impl rand_core::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.
///
/// # Safety
///
/// Same as `wc_rng_new_ex()`.
unsafe fn rng_new_ex(rng: &mut *mut sys::WC_RNG, nonce: *mut u8,
nonce_size: u32, heap: *mut core::ffi::c_void, dev_id: i32) -> i32 {
#[cfg(rng_new_ex)]
{
unsafe { sys::wc_rng_new_ex(rng, nonce, nonce_size, heap, dev_id) }
}
#[cfg(not(rng_new_ex))]
{
if dev_id != sys::INVALID_DEVID {
return sys::wolfCrypt_ErrorCodes_BAD_FUNC_ARG;
}
*rng = unsafe { sys::wc_rng_new(nonce, nonce_size, heap) };
if rng.is_null() {
sys::wolfCrypt_ErrorCodes_RNG_FAILURE_E
} else {
0
}
}
}

impl Drop for RNG {
/// Safely free the underlying wolfSSL RNG context.
///
Expand Down
39 changes: 23 additions & 16 deletions wrapper/rust/wolfssl-wolfcrypt/src/rsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1261,13 +1261,14 @@ impl RSA {
/// rsa.pss_verify_check(signature, &mut verify_out, msg, RSA::HASH_TYPE_SHA256, RSA::MGF1SHA256).expect("Error with pss_verify_check()");
/// }
/// ```
#[cfg(all(rsa_pss, rsa_const_api))]
#[cfg(all(rsa_pss, any(rsa_const_api, fips_v5_2_3)))]
pub fn pss_verify_check(&mut self, din: &[u8], dout: &mut [u8], digest: &[u8], hash_algo: u32, mgf: i32) -> Result<usize, i32> {
let din_size = crate::buffer_len_to_u32(din.len())?;
let dout_size = crate::buffer_len_to_u32(dout.len())?;
let digest_size = crate::buffer_len_to_u32(digest.len())?;
let rc = unsafe {
sys::wc_RsaPSS_VerifyCheck(din.as_ptr(), din_size,
// FIPS v5.2.3 declares the input non const but only reads it.
sys::wc_RsaPSS_VerifyCheck(din.as_ptr().cast_mut(), din_size,
dout.as_mut_ptr(), dout_size, digest.as_ptr(), digest_size,
hash_algo, mgf, &mut self.wc_rsakey)
};
Expand Down Expand Up @@ -1385,13 +1386,7 @@ impl RSA {
/// ```
#[cfg(random)]
pub fn set_rng(&mut self, rng: RNG) -> Result<(), i32> {
let wc_rng = rng.wc_rng;
let rc = unsafe {
sys::wc_RsaSetRNG(&mut self.wc_rsakey, wc_rng)
};
if rc != 0 {
return Err(rc);
}
self.set_wc_rng(&rng)?;
self.rng = Some(RngHandle::Owned(rng));
Ok(())
}
Expand All @@ -1403,17 +1398,29 @@ impl RSA {
/// when the `alloc` feature is enabled.
#[cfg(all(random, feature = "alloc"))]
pub fn set_shared_rng(&mut self, rng: alloc::rc::Rc<RNG>) -> Result<(), i32> {
let wc_rng = rng.wc_rng;
let rc = unsafe {
sys::wc_RsaSetRNG(&mut self.wc_rsakey, wc_rng)
};
if rc != 0 {
return Err(rc);
}
self.set_wc_rng(&rng)?;
self.rng = Some(RngHandle::Shared(rng));
Ok(())
}

/// Point the wolfCrypt key at `rng` for blinding.
#[cfg(random)]
#[cfg_attr(fips_v5_2_3, allow(unused_variables))]
fn set_wc_rng(&mut self, rng: &RNG) -> Result<(), i32> {
// FIPS v5.2.3 builds without RSA blinding, so there is no
// wc_RsaSetRNG() and private operations need no RNG.
#[cfg(not(fips_v5_2_3))]
{
let rc = unsafe {
sys::wc_RsaSetRNG(&mut self.wc_rsakey, rng.wc_rng)
};
if rc != 0 {
return Err(rc);
}
}
Ok(())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function just returns Ok(()) without doing anything with FIPS v5.2.3? That does not seem great. If it is still going to exist in that configuration, should it return an error instead?

@gasbytes gasbytes Oct 27, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On v5.2.3 the FIPS settings turn RSA blinding off entirely, so there's nothing for the RNG to attach to and decryption doesn't need one, I think returning an error would only break callers, so it succeeds as a no-op.
But I think the comment can be a little bit more explicit, thanks.

}

/// Borrow the RNG previously bound via `set_rng` or `set_shared_rng`.
#[cfg(random)]
pub fn rng(&self) -> Option<&RNG> {
Expand Down
2 changes: 1 addition & 1 deletion wrapper/rust/wolfssl-wolfcrypt/src/sys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
/* Include generated FIPS symbol aliases. */
include!(concat!(env!("OUT_DIR"), "/fips_aliases.rs"));

#[cfg(all(rsa,not(rsa_setrng)))]
#[cfg(all(rsa,not(rsa_setrng),not(fips_v5_2_3)))]
unsafe extern "C" {
pub fn wc_RsaSetRNG(key: *mut RsaKey, rng: *mut WC_RNG) -> core::ffi::c_int;
}
Loading
Loading