From 18cd43d89f11c2a90584dfb936c687dec15742af Mon Sep 17 00:00:00 2001 From: Reda Chouk Date: Tue, 12 May 2026 08:34:09 +0200 Subject: [PATCH 1/5] update to latest release of wolfssl (5.9.1) - updated build.rs to build the bindings to the latest release; - updated the wc_sha_* shas to use the update wc_HashType_Wc_hash_*; - updated ecc ids; - per https://github.com/wolfSSL/rustls-wolfcrypt-provider/pull/24#discussion_r2630915242 refactored the eddsa.rs file in order to import the key using the asn parsing directly from wolfcrypt, with proper slicing since the private key gets imported full size (64) instead of 32. --- rustls-wolfcrypt-provider/src/hmac/mod.rs | 12 +- rustls-wolfcrypt-provider/src/kx/sec256r1.rs | 8 +- rustls-wolfcrypt-provider/src/kx/sec384r1.rs | 8 +- rustls-wolfcrypt-provider/src/kx/sec521r1.rs | 8 +- rustls-wolfcrypt-provider/src/kx/x25519.rs | 4 +- rustls-wolfcrypt-provider/src/sign/ecdsa.rs | 6 +- rustls-wolfcrypt-provider/src/sign/eddsa.rs | 107 +++++++----------- rustls-wolfcrypt-provider/src/sign/rsa.rs | 2 +- rustls-wolfcrypt-provider/src/verify/ecdsa.rs | 6 +- .../src/verify/rsapkcs1.rs | 6 +- rustls-wolfcrypt-provider/tests/e2e.rs | 10 +- wolfcrypt-rs/build.rs | 8 +- 12 files changed, 80 insertions(+), 105 deletions(-) diff --git a/rustls-wolfcrypt-provider/src/hmac/mod.rs b/rustls-wolfcrypt-provider/src/hmac/mod.rs index 1092fa8..4f8cc85 100644 --- a/rustls-wolfcrypt-provider/src/hmac/mod.rs +++ b/rustls-wolfcrypt-provider/src/hmac/mod.rs @@ -21,23 +21,23 @@ impl WCShaHmac { fn algorithm(&self) -> i32 { match self { - WCShaHmac::Sha256 => WC_SHA256.try_into().unwrap(), - WCShaHmac::Sha384 => WC_SHA384.try_into().unwrap(), + WCShaHmac::Sha256 => wc_HashType_WC_HASH_TYPE_SHA256.try_into().unwrap(), + WCShaHmac::Sha384 => wc_HashType_WC_HASH_TYPE_SHA384.try_into().unwrap(), } } pub fn new(hash_type: wc_HashType) -> Self { match hash_type { - WC_SHA256 => WCShaHmac::Sha256, - WC_SHA384 => WCShaHmac::Sha384, + wolfcrypt_rs::wc_HashType_WC_HASH_TYPE_SHA256 => WCShaHmac::Sha256, + wolfcrypt_rs::wc_HashType_WC_HASH_TYPE_SHA384 => WCShaHmac::Sha384, _ => panic!("Unsupported hash type"), } } pub fn hash_type(&self) -> wc_HashType { match self { - WCShaHmac::Sha256 => WC_SHA256, - WCShaHmac::Sha384 => WC_SHA384, + WCShaHmac::Sha256 => wc_HashType_WC_HASH_TYPE_SHA256, + WCShaHmac::Sha384 => wc_HashType_WC_HASH_TYPE_SHA384, } } diff --git a/rustls-wolfcrypt-provider/src/kx/sec256r1.rs b/rustls-wolfcrypt-provider/src/kx/sec256r1.rs index 426362f..f5dfd6c 100644 --- a/rustls-wolfcrypt-provider/src/kx/sec256r1.rs +++ b/rustls-wolfcrypt-provider/src/kx/sec256r1.rs @@ -35,7 +35,7 @@ impl KeyExchangeSecP256r1 { key_object.init(); rng_object.init(); - let key_size = unsafe { wc_ecc_get_curve_size_from_id(ecc_curve_id_ECC_SECP256R1) }; + let key_size = unsafe { wc_ecc_get_curve_size_from_id(ecc_curve_ids_ECC_SECP256R1) }; let mut priv_key_raw = [0u8; 32]; let mut priv_key_raw_len: word32 = priv_key_raw.len() as word32; @@ -45,7 +45,7 @@ impl KeyExchangeSecP256r1 { rng_object.as_ptr(), key_size, key_object.as_ptr(), - ecc_curve_id_ECC_SECP256R1, + ecc_curve_ids_ECC_SECP256R1, ) }; check_if_zero(ret) @@ -112,7 +112,7 @@ impl KeyExchangeSecP256r1 { ptr::null_mut(), 0, priv_key_object.as_ptr(), - ecc_curve_id_ECC_SECP256R1, + ecc_curve_ids_ECC_SECP256R1, ) }; check_if_zero(ret) @@ -124,7 +124,7 @@ impl KeyExchangeSecP256r1 { peer_pub_key[1..33].as_ptr(), peer_pub_key[33..].as_ptr(), ptr::null_mut(), - ecc_curve_id_ECC_SECP256R1, + ecc_curve_ids_ECC_SECP256R1, ) }; check_if_zero(ret) diff --git a/rustls-wolfcrypt-provider/src/kx/sec384r1.rs b/rustls-wolfcrypt-provider/src/kx/sec384r1.rs index 36e4ce5..4754101 100644 --- a/rustls-wolfcrypt-provider/src/kx/sec384r1.rs +++ b/rustls-wolfcrypt-provider/src/kx/sec384r1.rs @@ -37,14 +37,14 @@ impl KeyExchangeSecP384r1 { rng_object.init(); - let key_size = unsafe { wc_ecc_get_curve_size_from_id(ecc_curve_id_ECC_SECP384R1) }; + let key_size = unsafe { wc_ecc_get_curve_size_from_id(ecc_curve_ids_ECC_SECP384R1) }; ret = unsafe { wc_ecc_make_key_ex( rng_object.as_ptr(), key_size, key_object.as_ptr(), - ecc_curve_id_ECC_SECP384R1, + ecc_curve_ids_ECC_SECP384R1, ) }; check_if_zero(ret) @@ -115,7 +115,7 @@ impl KeyExchangeSecP384r1 { ptr::null_mut(), 0, priv_key_object.as_ptr(), - ecc_curve_id_ECC_SECP384R1, + ecc_curve_ids_ECC_SECP384R1, ) }; check_if_zero(ret) @@ -131,7 +131,7 @@ impl KeyExchangeSecP384r1 { peer_pub_key[1..49].as_ptr(), peer_pub_key[49..].as_ptr(), ptr::null_mut(), - ecc_curve_id_ECC_SECP384R1, + ecc_curve_ids_ECC_SECP384R1, ) }; check_if_zero(ret) diff --git a/rustls-wolfcrypt-provider/src/kx/sec521r1.rs b/rustls-wolfcrypt-provider/src/kx/sec521r1.rs index 2086581..1887f5d 100644 --- a/rustls-wolfcrypt-provider/src/kx/sec521r1.rs +++ b/rustls-wolfcrypt-provider/src/kx/sec521r1.rs @@ -38,14 +38,14 @@ impl KeyExchangeSecP521r1 { // We initiliaze the rng object. rng_object.init(); - let key_size = unsafe { wc_ecc_get_curve_size_from_id(ecc_curve_id_ECC_SECP521R1) }; + let key_size = unsafe { wc_ecc_get_curve_size_from_id(ecc_curve_ids_ECC_SECP521R1) }; ret = unsafe { wc_ecc_make_key_ex( rng_object.as_ptr(), key_size, key_object.as_ptr(), - ecc_curve_id_ECC_SECP521R1, + ecc_curve_ids_ECC_SECP521R1, ) }; check_if_zero(ret) @@ -116,7 +116,7 @@ impl KeyExchangeSecP521r1 { ptr::null_mut(), 0, priv_key_object.as_ptr(), - ecc_curve_id_ECC_SECP521R1, + ecc_curve_ids_ECC_SECP521R1, ) }; check_if_zero(ret) @@ -132,7 +132,7 @@ impl KeyExchangeSecP521r1 { peer_pub_key[1..67].as_ptr(), peer_pub_key[67..].as_ptr(), ptr::null_mut(), - ecc_curve_id_ECC_SECP521R1, + ecc_curve_ids_ECC_SECP521R1, ) }; check_if_zero(ret) diff --git a/rustls-wolfcrypt-provider/src/kx/x25519.rs b/rustls-wolfcrypt-provider/src/kx/x25519.rs index 705be61..a367a3f 100644 --- a/rustls-wolfcrypt-provider/src/kx/x25519.rs +++ b/rustls-wolfcrypt-provider/src/kx/x25519.rs @@ -128,7 +128,9 @@ impl KeyExchangeX25519 { ) }; check_if_zero(ret).map_err(|_| { - rustls::Error::General("Failed to compute Curve25519 shared secret".into()) + rustls::Error::General(alloc::format!( + "Failed to compute Curve25519 shared secret (ret = {ret})" + )) })?; Ok(Box::new(out)) diff --git a/rustls-wolfcrypt-provider/src/sign/ecdsa.rs b/rustls-wolfcrypt-provider/src/sign/ecdsa.rs index f747bd8..8fa776d 100644 --- a/rustls-wolfcrypt-provider/src/sign/ecdsa.rs +++ b/rustls-wolfcrypt-provider/src/sign/ecdsa.rs @@ -246,9 +246,9 @@ fn hash_message_for_scheme( /// Converts a rustls `SignatureScheme` to the WolfSSL curve id (ecc_curve_id_ECC_...). fn scheme_to_curve_id(scheme: SignatureScheme) -> Result { match scheme { - SignatureScheme::ECDSA_NISTP256_SHA256 => Ok(ecc_curve_id_ECC_SECP256R1), - SignatureScheme::ECDSA_NISTP384_SHA384 => Ok(ecc_curve_id_ECC_SECP384R1), - SignatureScheme::ECDSA_NISTP521_SHA512 => Ok(ecc_curve_id_ECC_SECP521R1), + SignatureScheme::ECDSA_NISTP256_SHA256 => Ok(ecc_curve_ids_ECC_SECP256R1), + SignatureScheme::ECDSA_NISTP384_SHA384 => Ok(ecc_curve_ids_ECC_SECP384R1), + SignatureScheme::ECDSA_NISTP521_SHA512 => Ok(ecc_curve_ids_ECC_SECP521R1), _ => Err("Not an ECDSA_NISTPxxx_SHAxxx scheme"), } } diff --git a/rustls-wolfcrypt-provider/src/sign/eddsa.rs b/rustls-wolfcrypt-provider/src/sign/eddsa.rs index fa07a8f..27e2854 100644 --- a/rustls-wolfcrypt-provider/src/sign/eddsa.rs +++ b/rustls-wolfcrypt-provider/src/sign/eddsa.rs @@ -34,79 +34,52 @@ impl Ed25519PrivateKey { /// Extract ED25519 private and if available public key values from a PKCS#8 DER formatted key fn extract_key_pair(input_key: &[u8]) -> Result<([u8; 32], Option<[u8; 32]>), rustls::Error> { let mut public_key_raw: [u8; 32] = [0; ED25519_PUB_KEY_SIZE as usize]; - let mut private_key_raw: [u8; 32] = [0; ED25519_KEY_SIZE as usize]; - let mut skip_bytes: usize; - let mut key_sub_slice = input_key; - - const SHORT_FORM_LEN_MAX: u8 = 127; - const TAG_SEQUENCE: u8 = 0x30; - const TAG_OCTET_SEQUENCE: u8 = 0x04; - const TAG_OPTIONAL_SET_OF_ATTRIBUTES: u8 = 0x80; //Implicit, context-specific, and primitive underlying type (SET OF) - const TAG_OPTIONAL_PUBLIC_KEY_BIT_STRING: u8 = 0x81; //Implicit, context-specific, and primitive underlying type (BIT STRING) - - // The input key is encoded in PKCS#8 DER format with a structure as in - // https://www.rfc-editor.org/rfc/rfc5958.html - // - // AsymmetricKeyPackage ::= SEQUENCE SIZE (1..MAX) OF OneAsymmetricKey - - // OneAsymmetricKey ::= SEQUENCE { - // version Version, - // privateKeyAlgorithm PrivateKeyAlgorithmIdentifier, - // privateKey PrivateKey, - // attributes [0] Attributes OPTIONAL, - // ..., - // [[2: publicKey [1] PublicKey OPTIONAL ]], - // ... - // } - - // The key structure must begin with a SEQUENCE tag with at least 2 bytes length if short - // length format is used - if key_sub_slice[0] != TAG_SEQUENCE || key_sub_slice.len() < 2 { - return Err(rustls::Error::General( - "Faulty PKCS#8 ED25519 private key structure".into(), - )); - } - // Check which length format and skip tag and length encoding bytes - if key_sub_slice[1] > SHORT_FORM_LEN_MAX { - skip_bytes = (2 + (key_sub_slice[1] & 0x7F)) as usize; - } else { - skip_bytes = 2; - } + let mut private_key_raw: [u8; 64] = [0; ED25519_PRV_KEY_SIZE as usize]; + let mut ed25519_c_type: ed25519_key = unsafe { mem::zeroed() }; + let ed25519_key_object = ED25519KeyObject::new(&mut ed25519_c_type); + let mut idx: u32 = 0; + let mut private_key_raw_len: word32 = private_key_raw.len() as word32; + let mut public_key_raw_len: word32 = public_key_raw.len() as word32; + let mut ret: i32; - // Skip version (3 bytes), algorithm ID sequence (0x30 + length encoding bytes + 5 ID bytes), - skip_bytes += 3 + 7; - key_sub_slice = input_key.get(skip_bytes..).unwrap(); + ed25519_key_object.init(); - // Check if next bytes are 0x04, 0x22, 0x04, 0x20 - if !matches!( - key_sub_slice, - [TAG_OCTET_SEQUENCE, 0x22, TAG_OCTET_SEQUENCE, 0x20, ..] - ) { - return Err(rustls::Error::General( - "Faulty PKCS#8 ED25519 private key structure".into(), - )); - } + ret = unsafe { + wc_Ed25519PrivateKeyDecode( + input_key.as_ptr(), + &mut idx, + ed25519_key_object.as_ptr(), + input_key.len() as u32, + ) + }; - // Copy private key value - skip_bytes += 4; - key_sub_slice = input_key.get(skip_bytes..).unwrap(); - private_key_raw.copy_from_slice(&key_sub_slice[..ED25519_KEY_SIZE as usize]); - skip_bytes += ED25519_KEY_SIZE as usize; - key_sub_slice = input_key.get(skip_bytes..).unwrap(); + check_if_zero(ret) + .map_err(|_| rustls::Error::General("wc_Ed25519PrivateKeyDecode failed".into()))?; - // Check if optional SET OF attributes exists and skip - if key_sub_slice.first() == Some(&TAG_OPTIONAL_SET_OF_ATTRIBUTES) { - skip_bytes += (2 + (key_sub_slice[1])) as usize; - key_sub_slice = input_key.get(skip_bytes..).unwrap(); - } + ret = unsafe { + wc_ed25519_export_key( + ed25519_key_object.as_ptr(), + private_key_raw.as_mut_ptr(), + &mut private_key_raw_len, + public_key_raw.as_mut_ptr(), + &mut public_key_raw_len, + ) + }; - // Check if optional public key value exists. If exists, skip tag, length encoding byte, - // and bits-used byte - if key_sub_slice.first() == Some(&TAG_OPTIONAL_PUBLIC_KEY_BIT_STRING) { - public_key_raw.copy_from_slice(&key_sub_slice[3..(3 + ED25519_KEY_SIZE as usize)]); - Ok((private_key_raw, Some(public_key_raw))) + check_if_zero(ret) + .map_err(|_| rustls::Error::General("wc_ed25519_export_key failed: {ret}".into()))?; + + // Check if optional public key value exists was exported or not. + // Also we return only the first 32 bytes, since wc_ed25519_export_key + // exports both the secret seed and the public key into one single + // array. + if public_key_raw != [0; 32] { + Ok(( + private_key_raw[..32].try_into().unwrap(), + Some(public_key_raw), + )) } else { - Ok((private_key_raw, None)) + Ok((private_key_raw[..32].try_into().unwrap(), None)) } } } diff --git a/rustls-wolfcrypt-provider/src/sign/rsa.rs b/rustls-wolfcrypt-provider/src/sign/rsa.rs index 0c75790..40dcfba 100644 --- a/rustls-wolfcrypt-provider/src/sign/rsa.rs +++ b/rustls-wolfcrypt-provider/src/sign/rsa.rs @@ -271,7 +271,7 @@ impl Signer for RsaSigner { message.len() as u32, sig_buf.as_mut_ptr(), &mut sig_len, - rsa_key.as_ptr() as *const core::ffi::c_void, + rsa_key.as_ptr() as *mut core::ffi::c_void, mem::size_of_val(&deref_rsa_key_c_type).try_into().unwrap(), rng_object.as_ptr(), ) diff --git a/rustls-wolfcrypt-provider/src/verify/ecdsa.rs b/rustls-wolfcrypt-provider/src/verify/ecdsa.rs index 78065f2..416431f 100644 --- a/rustls-wolfcrypt-provider/src/verify/ecdsa.rs +++ b/rustls-wolfcrypt-provider/src/verify/ecdsa.rs @@ -70,17 +70,17 @@ impl SignatureVerificationAlgorithm for EcdsaVerifier { // Determine curve, how many bytes to skip from public_key, and which hash to use let (curve_id, skip_len, wc_hash_type) = match self.scheme { SignatureScheme::ECDSA_NISTP256_SHA256 => ( - ecc_curve_id_ECC_SECP256R1, + ecc_curve_ids_ECC_SECP256R1, 32, wc_HashType_WC_HASH_TYPE_SHA256, ), SignatureScheme::ECDSA_NISTP384_SHA384 => ( - ecc_curve_id_ECC_SECP384R1, + ecc_curve_ids_ECC_SECP384R1, 48, wc_HashType_WC_HASH_TYPE_SHA384, ), SignatureScheme::ECDSA_NISTP521_SHA512 => ( - ecc_curve_id_ECC_SECP521R1, + ecc_curve_ids_ECC_SECP521R1, 66, wc_HashType_WC_HASH_TYPE_SHA512, ), diff --git a/rustls-wolfcrypt-provider/src/verify/rsapkcs1.rs b/rustls-wolfcrypt-provider/src/verify/rsapkcs1.rs index 3b5d7b3..da5d770 100644 --- a/rustls-wolfcrypt-provider/src/verify/rsapkcs1.rs +++ b/rustls-wolfcrypt-provider/src/verify/rsapkcs1.rs @@ -62,7 +62,7 @@ impl SignatureVerificationAlgorithm for RsaPkcs1Sha256Verify { message.len() as word32, signature.as_ptr(), signature.len() as word32, - rsa_key_object.as_ptr() as *const c_void, + rsa_key_object.as_ptr() as *mut c_void, mem::size_of_val(&derefenced_rsa_key_c_type) .try_into() .unwrap(), @@ -128,7 +128,7 @@ impl SignatureVerificationAlgorithm for RsaPkcs1Sha384Verify { message.len() as word32, signature.as_ptr(), signature.len() as word32, - rsa_key_object.as_ptr() as *const c_void, + rsa_key_object.as_ptr() as *mut c_void, mem::size_of_val(&dereferenced_rsa_key_c_type) .try_into() .unwrap(), @@ -194,7 +194,7 @@ impl SignatureVerificationAlgorithm for RsaPkcs1Sha512Verify { message.len() as word32, signature.as_ptr(), signature.len() as word32, - rsa_key_object.as_ptr() as *const c_void, + rsa_key_object.as_ptr() as *mut c_void, mem::size_of_val(&dereferenced_rsa_key_c_type) .try_into() .unwrap(), diff --git a/rustls-wolfcrypt-provider/tests/e2e.rs b/rustls-wolfcrypt-provider/tests/e2e.rs index ba58b3e..6044fa9 100644 --- a/rustls-wolfcrypt-provider/tests/e2e.rs +++ b/rustls-wolfcrypt-provider/tests/e2e.rs @@ -59,10 +59,10 @@ fn init_thread_pool() { * Starts background job for wolfssl server (localhost:4443). * */ fn start_wolfssl_server(current_dir_string: String, tls_version: &str) -> Child { - if let Err(e) = env::set_current_dir("../wolfcrypt-rs/wolfssl-5.7.6-stable/") { + if let Err(e) = env::set_current_dir("../wolfcrypt-rs/wolfssl-5.9.1-stable/") { panic!("Error changing directory: {}", e); } else { - println!("Changed directory to wolfssl-5.7.6-stable."); + println!("Changed directory to wolfssl-5.9.1-stable."); Command::new("./examples/server/server") .arg("-d") @@ -410,17 +410,17 @@ mod tests { let test_configs = [ ( SignatureScheme::ECDSA_NISTP256_SHA256, - ecc_curve_id_ECC_SECP256R1, + ecc_curve_ids_ECC_SECP256R1, 32, // P256 key size ), ( SignatureScheme::ECDSA_NISTP384_SHA384, - ecc_curve_id_ECC_SECP384R1, + ecc_curve_ids_ECC_SECP384R1, 48, // P384 key size ), ( SignatureScheme::ECDSA_NISTP521_SHA512, - ecc_curve_id_ECC_SECP521R1, + ecc_curve_ids_ECC_SECP521R1, 66, // P521 key size ), ]; diff --git a/wolfcrypt-rs/build.rs b/wolfcrypt-rs/build.rs index 0a49813..14ee61d 100644 --- a/wolfcrypt-rs/build.rs +++ b/wolfcrypt-rs/build.rs @@ -8,10 +8,10 @@ use std::path::PathBuf; use std::process::Command; /// Version-related constants for WolfSSL -const WOLFSSL_DIR: &str = "wolfssl-5.7.6-stable"; -const WOLFSSL_ZIP: &str = "wolfssl-5.7.6-stable.zip"; -const WOLFSSL_URL: &str = "https://github.com/wolfSSL/wolfssl/archive/refs/tags/v5.7.6-stable.zip"; -const WOLFSSL_SHA256: &str = "1aeb6e49222bb9d8cf012063f0dfc3f229084f24ce2b5740a2dcdb64d72b00bf"; +const WOLFSSL_DIR: &str = "wolfssl-5.9.1-stable"; +const WOLFSSL_ZIP: &str = "wolfssl-5.9.1-stable.zip"; +const WOLFSSL_URL: &str = "https://github.com/wolfSSL/wolfssl/archive/refs/tags/v5.9.1-stable.zip"; +const WOLFSSL_SHA256: &str = "a91ccbf742391db95d5f1821273c59841f54a5306b9d2caea0cd7a8f2fbe0a5c"; /// Entry point for the build script. /// Handles the main build process and exits with an error code if anything fails. From b3ec7ca4d632d6d969620d2fed1b5a4b5363eb0e Mon Sep 17 00:00:00 2001 From: Reda Chouk Date: Wed, 27 May 2026 13:53:38 +0200 Subject: [PATCH 2/5] Add cfg config flag to detect if blinding is enabled or not - Also removed --enable-all since it was pretty much useless; - If blinding is enabled, set the rng and associate it with the private key; This was necessary since starting from 5.9.1 curve blinding is enabled by default on pure-c builds (no asm is selected). This change prevents a -173 failure when deriving a secret with curve25519. --- rustls-wolfcrypt-provider/src/kx/x25519.rs | 15 ++++++++++++++ wolfcrypt-rs/build.rs | 19 ++++++++++++++--- wolfcrypt-rs/src/lib.rs | 24 ++++++++++++++++++++++ 3 files changed, 55 insertions(+), 3 deletions(-) diff --git a/rustls-wolfcrypt-provider/src/kx/x25519.rs b/rustls-wolfcrypt-provider/src/kx/x25519.rs index a367a3f..685cf6c 100644 --- a/rustls-wolfcrypt-provider/src/kx/x25519.rs +++ b/rustls-wolfcrypt-provider/src/kx/x25519.rs @@ -116,6 +116,21 @@ impl KeyExchangeX25519 { rustls::Error::General("Failed to import Curve25519 private key".into()) })?; + // When wolfSSL is built with curve25519 blinding (pure-C builds, e.g. + // Apple Silicon where no asm backend is selected), the shared-secret + // scalar multiplication draws random values to blind the private + // scalar. An imported key has no RNG attached, so we must supply one; + // otherwise wc_curve25519_shared_secret_ex returns BAD_FUNC_ARG (-173). + // On non-blinding (asm) builds curve25519_set_rng is a no-op. + let mut rng: WC_RNG = unsafe { mem::zeroed() }; + let rng_object = WCRngObject::new(&mut rng); + rng_object.init(); + + ret = unsafe { curve25519_set_rng(private_key_object.as_ptr(), rng_object.as_ptr()) }; + check_if_zero(ret).map_err(|_| { + rustls::Error::General("Failed to set RNG on Curve25519 private key".into()) + })?; + // This function computes a shared secret key given a secret private key and // a received public key. Stores the generated secret in the buffer out. ret = unsafe { diff --git a/wolfcrypt-rs/build.rs b/wolfcrypt-rs/build.rs index 14ee61d..1330a07 100644 --- a/wolfcrypt-rs/build.rs +++ b/wolfcrypt-rs/build.rs @@ -72,9 +72,23 @@ fn generate_bindings() -> Result<()> { .map_err(|_| io::Error::other("Failed to generate bindings"))?; let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + let bindings_file = out_path.join("bindings.rs"); bindings - .write_to_file(out_path.join("bindings.rs")) - .map_err(|e| io::Error::other(format!("Couldn't write bindings: {e}"))) + .write_to_file(&bindings_file) + .map_err(|e| io::Error::other(format!("Couldn't write bindings: {e}")))?; + + // wolfSSL only compiles wc_curve25519_set_rng when it builds the blinded, + // pure-C curve25519 implementation (WOLFSSL_CURVE25519_BLINDING, i.e. no asm + // backend). Detect it from the generated bindings and expose a cfg so the + // crate can pick the real call vs. a no-op. Declaring it with check-cfg + // keeps `-D warnings` (clippy) builds from failing on an unexpected cfg. + println!("cargo:rustc-check-cfg=cfg(curve25519_blinding)"); + let generated = fs::read_to_string(&bindings_file)?; + if generated.contains("wc_curve25519_set_rng") { + println!("cargo:rustc-cfg=curve25519_blinding"); + } + + Ok(()) } /// Coordinates the complete setup process for WolfSSL. @@ -198,7 +212,6 @@ fn build_wolfssl() -> Result<()> { run_command( "./configure", &[ - "--enable-all", "--enable-all-crypto", "--enable-debug", "--disable-shared", diff --git a/wolfcrypt-rs/src/lib.rs b/wolfcrypt-rs/src/lib.rs index ca6f032..f538270 100644 --- a/wolfcrypt-rs/src/lib.rs +++ b/wolfcrypt-rs/src/lib.rs @@ -1,6 +1,30 @@ pub mod bindings; pub use bindings::*; +/// Attach an RNG to a curve25519 key so the blinded scalar multiplication has +/// entropy. wolfSSL only compiles wc_curve25519_set_rng when it builds the +/// blinded, pure-C curve25519 implementation (WOLFSSL_CURVE25519_BLINDING, +/// i.e. no asm backend). On asm builds the symbol is absent and blinding isn't +/// used, so this becomes a no-op. The curve25519_blinding cfg is emitted by +/// build.rs based on whether that symbol appears in the generated bindings. +/// +/// # Safety +/// key must point to an initialized curve25519_key, and rng to an +/// initialized WC_RNG that outlives the following shared-secret call. +#[cfg(curve25519_blinding)] +pub unsafe fn curve25519_set_rng(key: *mut curve25519_key, rng: *mut WC_RNG) -> i32 { + wc_curve25519_set_rng(key, rng) +} + +/// No-op variant: builds without curve25519 blinding don't attach an RNG. +/// +/// # Safety +/// Same contract as the blinding variant; the arguments are ignored. +#[cfg(not(curve25519_blinding))] +pub unsafe fn curve25519_set_rng(_key: *mut curve25519_key, _rng: *mut WC_RNG) -> i32 { + 0 +} + #[cfg(test)] mod tests { use super::*; From 2980ab289f8fdbd4e27081aedc23117e3808a315 Mon Sep 17 00:00:00 2001 From: Reda Chouk Date: Thu, 4 Jun 2026 06:45:34 -0700 Subject: [PATCH 3/5] decode pkcs#8 v2 keys via DecodeAsymKey Decode pkcs#8v2 keys via DecodeAsymKey and strip the BIT STRING prefix from the embedded public key, avoiding wc_Ed25519PrivateKeyDecode's BAD_FUNC_ARG (-173) on RFC 5958 keys --- rustls-wolfcrypt-provider/src/sign/eddsa.rs | 90 ++++++++++++--------- 1 file changed, 50 insertions(+), 40 deletions(-) diff --git a/rustls-wolfcrypt-provider/src/sign/eddsa.rs b/rustls-wolfcrypt-provider/src/sign/eddsa.rs index 27e2854..7c0260b 100644 --- a/rustls-wolfcrypt-provider/src/sign/eddsa.rs +++ b/rustls-wolfcrypt-provider/src/sign/eddsa.rs @@ -33,54 +33,64 @@ impl fmt::Debug for Ed25519PrivateKey { impl Ed25519PrivateKey { /// Extract ED25519 private and if available public key values from a PKCS#8 DER formatted key fn extract_key_pair(input_key: &[u8]) -> Result<([u8; 32], Option<[u8; 32]>), rustls::Error> { - let mut public_key_raw: [u8; 32] = [0; ED25519_PUB_KEY_SIZE as usize]; - let mut private_key_raw: [u8; 64] = [0; ED25519_PRV_KEY_SIZE as usize]; - let mut ed25519_c_type: ed25519_key = unsafe { mem::zeroed() }; - let ed25519_key_object = ED25519KeyObject::new(&mut ed25519_c_type); - let mut idx: u32 = 0; + let mut private_key_raw: [u8; 32] = [0; ED25519_KEY_SIZE as usize]; let mut private_key_raw_len: word32 = private_key_raw.len() as word32; - let mut public_key_raw_len: word32 = public_key_raw.len() as word32; - let mut ret: i32; - - ed25519_key_object.init(); - - ret = unsafe { - wc_Ed25519PrivateKeyDecode( + // Scratch buffer large enough for any public-key form wolfSSL may emit: + // a bare 32-byte point, a 33-byte prefixed point, or an uncompressed point. + let mut public_key_scratch: [u8; 65] = [0; 65]; + let mut public_key_scratch_len: word32 = public_key_scratch.len() as word32; + let mut idx: word32 = 0; + + // Parse the PKCS#8 structure with wolfSSL's own ASN parser. This is the + // same parser wc_Ed25519PrivateKeyDecode uses internally, but calling it + // directly lets us normalise the public key before it is imported. + // + // For an RFC 5958 (PKCS#8 v2) key that embeds the optional public key, + // DecodeAsymKey returns the [1] field as a raw DER BIT STRING body: the + // 32-byte key prefixed by its "unused bits" 0x00 octet (33 bytes total). + // wc_Ed25519PrivateKeyDecode forwards those 33 bytes straight to + // wc_ed25519_import_public_ex, which only accepts 32 bytes, a 0x40-prefixed + // compressed point, or an 0x04 uncompressed point, so it fails with + // BAD_FUNC_ARG (-173). We strip the prefix ourselves instead. + let ret = unsafe { + DecodeAsymKey( input_key.as_ptr(), &mut idx, - ed25519_key_object.as_ptr(), - input_key.len() as u32, - ) - }; - - check_if_zero(ret) - .map_err(|_| rustls::Error::General("wc_Ed25519PrivateKeyDecode failed".into()))?; - - ret = unsafe { - wc_ed25519_export_key( - ed25519_key_object.as_ptr(), + input_key.len() as word32, private_key_raw.as_mut_ptr(), &mut private_key_raw_len, - public_key_raw.as_mut_ptr(), - &mut public_key_raw_len, + public_key_scratch.as_mut_ptr(), + &mut public_key_scratch_len, + Key_Sum_ED25519k as i32, ) }; - check_if_zero(ret) - .map_err(|_| rustls::Error::General("wc_ed25519_export_key failed: {ret}".into()))?; - - // Check if optional public key value exists was exported or not. - // Also we return only the first 32 bytes, since wc_ed25519_export_key - // exports both the secret seed and the public key into one single - // array. - if public_key_raw != [0; 32] { - Ok(( - private_key_raw[..32].try_into().unwrap(), - Some(public_key_raw), - )) - } else { - Ok((private_key_raw[..32].try_into().unwrap(), None)) - } + .map_err(|_| rustls::Error::General("DecodeAsymKey (ED25519) failed".into()))?; + + // Normalise the optional public key based on the length the parser reported. + let pub_key: Option<[u8; 32]> = match public_key_scratch_len { + // No embedded public key; the caller derives it from the private seed. + 0 => None, + // Bare 32-byte public key. + len if len == ED25519_PUB_KEY_SIZE => Some( + public_key_scratch[..ED25519_PUB_KEY_SIZE as usize] + .try_into() + .unwrap(), + ), + // BIT STRING body: leading 0x00 "unused bits" octet + 32-byte key. + len if len == ED25519_PUB_KEY_SIZE + 1 && public_key_scratch[0] == 0x00 => Some( + public_key_scratch[1..1 + ED25519_PUB_KEY_SIZE as usize] + .try_into() + .unwrap(), + ), + _ => { + return Err(rustls::Error::General( + "Unexpected ED25519 public key encoding".into(), + )) + } + }; + + Ok((private_key_raw, pub_key)) } } From 7b8da68f98d423351fbeec23363a886a816fee29 Mon Sep 17 00:00:00 2001 From: Reda Chouk Date: Fri, 5 Jun 2026 03:46:24 -0700 Subject: [PATCH 4/5] Refactor .unwrap() to mapping the error. --- rustls-wolfcrypt-provider/src/sign/eddsa.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/rustls-wolfcrypt-provider/src/sign/eddsa.rs b/rustls-wolfcrypt-provider/src/sign/eddsa.rs index 7c0260b..6559750 100644 --- a/rustls-wolfcrypt-provider/src/sign/eddsa.rs +++ b/rustls-wolfcrypt-provider/src/sign/eddsa.rs @@ -75,13 +75,17 @@ impl Ed25519PrivateKey { len if len == ED25519_PUB_KEY_SIZE => Some( public_key_scratch[..ED25519_PUB_KEY_SIZE as usize] .try_into() - .unwrap(), + .map_err(|_| { + rustls::Error::General("Unexpected ED25519 public key encoding".into()) + })?, ), // BIT STRING body: leading 0x00 "unused bits" octet + 32-byte key. len if len == ED25519_PUB_KEY_SIZE + 1 && public_key_scratch[0] == 0x00 => Some( public_key_scratch[1..1 + ED25519_PUB_KEY_SIZE as usize] .try_into() - .unwrap(), + .map_err(|_| { + rustls::Error::General("Unexpected ED25519 public key encoding".into()) + })?, ), _ => { return Err(rustls::Error::General( From 0d81ed048eec3617e18d820acfb87d9a2427485d Mon Sep 17 00:00:00 2001 From: Reda Chouk Date: Fri, 5 Jun 2026 03:59:29 -0700 Subject: [PATCH 5/5] Updated the comments with the correct working around the PKCS#8 v2 parsing Since "uncompressed point" is technically wrong for ed25519 keys --- rustls-wolfcrypt-provider/src/sign/ecdsa.rs | 2 +- rustls-wolfcrypt-provider/src/sign/eddsa.rs | 27 ++++++++++++--------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/rustls-wolfcrypt-provider/src/sign/ecdsa.rs b/rustls-wolfcrypt-provider/src/sign/ecdsa.rs index 8fa776d..9e4f612 100644 --- a/rustls-wolfcrypt-provider/src/sign/ecdsa.rs +++ b/rustls-wolfcrypt-provider/src/sign/ecdsa.rs @@ -243,7 +243,7 @@ fn hash_message_for_scheme( } } -/// Converts a rustls `SignatureScheme` to the WolfSSL curve id (ecc_curve_id_ECC_...). +/// Converts a rustls `SignatureScheme` to the WolfSSL curve id (ecc_curve_ids_ECC_...). fn scheme_to_curve_id(scheme: SignatureScheme) -> Result { match scheme { SignatureScheme::ECDSA_NISTP256_SHA256 => Ok(ecc_curve_ids_ECC_SECP256R1), diff --git a/rustls-wolfcrypt-provider/src/sign/eddsa.rs b/rustls-wolfcrypt-provider/src/sign/eddsa.rs index 6559750..42478b1 100644 --- a/rustls-wolfcrypt-provider/src/sign/eddsa.rs +++ b/rustls-wolfcrypt-provider/src/sign/eddsa.rs @@ -35,23 +35,28 @@ impl Ed25519PrivateKey { fn extract_key_pair(input_key: &[u8]) -> Result<([u8; 32], Option<[u8; 32]>), rustls::Error> { let mut private_key_raw: [u8; 32] = [0; ED25519_KEY_SIZE as usize]; let mut private_key_raw_len: word32 = private_key_raw.len() as word32; - // Scratch buffer large enough for any public-key form wolfSSL may emit: - // a bare 32-byte point, a 33-byte prefixed point, or an uncompressed point. - let mut public_key_scratch: [u8; 65] = [0; 65]; + // Scratch buffer for the optional embedded public key. DecodeAsymKey + // reports it either as the bare 32-byte key, or as the raw DER BIT STRING + // body (a leading 0x00 "unused bits" octet + the 32-byte key = 33 bytes), + // so 33 bytes is the largest form we ever need to hold here. + let mut public_key_scratch: [u8; ED25519_PUB_KEY_SIZE as usize + 1] = + [0; ED25519_PUB_KEY_SIZE as usize + 1]; let mut public_key_scratch_len: word32 = public_key_scratch.len() as word32; let mut idx: word32 = 0; // Parse the PKCS#8 structure with wolfSSL's own ASN parser. This is the // same parser wc_Ed25519PrivateKeyDecode uses internally, but calling it - // directly lets us normalise the public key before it is imported. + // directly lets us normalise the embedded public key before it is imported. // - // For an RFC 5958 (PKCS#8 v2) key that embeds the optional public key, - // DecodeAsymKey returns the [1] field as a raw DER BIT STRING body: the - // 32-byte key prefixed by its "unused bits" 0x00 octet (33 bytes total). - // wc_Ed25519PrivateKeyDecode forwards those 33 bytes straight to - // wc_ed25519_import_public_ex, which only accepts 32 bytes, a 0x40-prefixed - // compressed point, or an 0x04 uncompressed point, so it fails with - // BAD_FUNC_ARG (-173). We strip the prefix ourselves instead. + // For an RFC 5958 (PKCS#8 v2) key that carries the optional public key, + // DecodeAsymKey hands back the [1] field as the raw DER BIT STRING body: + // a leading "unused bits" 0x00 octet followed by the 32-byte key (33 bytes + // total). wc_Ed25519PrivateKeyDecode passes that body verbatim to + // wc_ed25519_import_public_ex, which expects the key material itself, + // which is a bare 32-byte key (or a 0x40-prefixed 33-byte form). + // The 0x00-led 33-byte body matches neither, so it is rejected + // with BAD_FUNC_ARG (-173). So we strip the 0x00 octet here and + // hand on the bare 32-byte key instead. let ret = unsafe { DecodeAsymKey( input_key.as_ptr(),