diff --git a/wrapper/rust/wolfssl-wolfcrypt/Cargo.lock b/wrapper/rust/wolfssl-wolfcrypt/Cargo.lock index 9fe8a69681..ea88736bdf 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/Cargo.lock +++ b/wrapper/rust/wolfssl-wolfcrypt/Cargo.lock @@ -8,7 +8,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ - "blobby", + "blobby 0.3.1", "crypto-common 0.1.7", "generic-array", ] @@ -54,6 +54,21 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "847495c209977a90e8aad588b959d0ca9f5dc228096d29a6bd3defd53f35eaec" +[[package]] +name = "blobby" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89af0b093cc13baa4e51e64e65ec2422f7e73aea0e612e5ad3872986671622f1" + +[[package]] +name = "block-buffer" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +dependencies = [ + "hybrid-array", +] + [[package]] name = "cexpr" version = "0.6.0" @@ -110,6 +125,17 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "digest" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c" +dependencies = [ + "blobby 0.4.0", + "block-buffer", + "crypto-common 0.2.1", +] + [[package]] name = "either" version = "1.15.0" @@ -284,6 +310,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" + [[package]] name = "syn" version = "2.0.106" @@ -391,8 +423,10 @@ dependencies = [ "aead", "bindgen", "cipher", + "digest", "rand_core 0.10.0", "regex", + "signature", "zeroize", ] diff --git a/wrapper/rust/wolfssl-wolfcrypt/Cargo.toml b/wrapper/rust/wolfssl-wolfcrypt/Cargo.toml index 08ee5ac62b..9defa79fee 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/Cargo.toml +++ b/wrapper/rust/wolfssl-wolfcrypt/Cargo.toml @@ -15,16 +15,22 @@ std = [] rand_core = ["dep:rand_core"] aead = ["dep:aead"] cipher = ["dep:cipher"] +digest = ["dep:digest"] +signature = ["dep:signature"] [dependencies] rand_core = { version = "0.10", optional = true, default-features = false } aead = { version = "0.5", optional = true, default-features = false } cipher = { version = "0.5", optional = true, default-features = false } +digest = { version = "0.11", optional = true, default-features = false, features = ["block-api"] } +signature = { version = "2.2", optional = true, default-features = false } zeroize = { version = "1.3", default-features = false, features = ["derive"] } [dev-dependencies] aead = { version = "0.5", features = ["alloc", "dev"] } cipher = "0.5" +digest = { version = "0.11", features = ["dev"] } +signature = "2.2" [build-dependencies] bindgen = "0.72.1" diff --git a/wrapper/rust/wolfssl-wolfcrypt/Makefile b/wrapper/rust/wolfssl-wolfcrypt/Makefile index 37dc9a8579..51dc4c801e 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/Makefile +++ b/wrapper/rust/wolfssl-wolfcrypt/Makefile @@ -1,4 +1,4 @@ -FEATURES := rand_core,aead,cipher +FEATURES := rand_core,aead,cipher,digest,signature CARGO_FEATURE_FLAGS := --features $(FEATURES) .PHONY: all diff --git a/wrapper/rust/wolfssl-wolfcrypt/build.rs b/wrapper/rust/wolfssl-wolfcrypt/build.rs index 6f275d3a3b..6495d6bdb8 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/build.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/build.rs @@ -433,6 +433,22 @@ fn scan_cfg() -> Result<()> { check_cfg(&binding, "wc_RNG_DRBG_Reseed", "random_hashdrbg"); check_cfg(&binding, "wc_InitRng", "random"); + // When WOLFSSL_NO_MALLOC is set without WOLFSSL_STATIC_MEMORY, the + // WC_RNG struct contains an inline `drbg_data` field and wolfCrypt sets + // `rng->drbg = &rng->drbg_data` — a self-referential pointer. Rust + // moves values by memcpy, which would silently invalidate that pointer. + // Detect this configuration and refuse to build. + if binding.contains("drbg_data") { + eprintln!( + "error: wolfSSL appears to be built with WOLFSSL_NO_MALLOC \ + (without WOLFSSL_STATIC_MEMORY). This embeds a self-referential \ + pointer inside WC_RNG (drbg -> drbg_data) that is incompatible \ + with Rust move semantics. Please rebuild wolfSSL without \ + WOLFSSL_NO_MALLOC, or enable WOLFSSL_STATIC_MEMORY." + ); + std::process::exit(1); + } + /* rsa */ check_cfg(&binding, "wc_InitRsaKey", "rsa"); check_cfg(&binding, "wc_RsaDirect", "rsa_direct"); diff --git a/wrapper/rust/wolfssl-wolfcrypt/src/ecc.rs b/wrapper/rust/wolfssl-wolfcrypt/src/ecc.rs index e8a2e3700b..4fc2cd8921 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/src/ecc.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/src/ecc.rs @@ -296,7 +296,7 @@ impl Drop for ECCPoint { /// `import_x963_ex()`, `import_private_key()`, `import_private_key_ex()`, /// `import_raw()`, or `import_raw_ex()`. pub struct ECC { - wc_ecc_key: sys::ecc_key, + pub(crate) wc_ecc_key: sys::ecc_key, } #[cfg(ecc_curve_ids)] diff --git a/wrapper/rust/wolfssl-wolfcrypt/src/ecdsa.rs b/wrapper/rust/wolfssl-wolfcrypt/src/ecdsa.rs new file mode 100644 index 0000000000..b771694b05 --- /dev/null +++ b/wrapper/rust/wolfssl-wolfcrypt/src/ecdsa.rs @@ -0,0 +1,372 @@ +/* + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/*! +ECDSA trait impls for the RustCrypto `signature` crate. + +Provides per-curve wrapper types (`P256SigningKey`, `P256VerifyingKey`, +`P256Signature`, etc.) over the inherent [`crate::ecc::ECC`] wrapper. Each +curve pairs with its canonical hash algorithm (P-256 with SHA-256, P-384 with +SHA-384, P-521 with SHA-512) and produces fixed-size `r‖s` signatures +matching the conventions used by the RustCrypto `ecdsa` crate. + +Signing and verifying use the high-level `wc_SignatureGenerate` / +`wc_SignatureVerify` wolfCrypt entry points, which hash the raw message +internally and emit/consume DER-encoded ECDSA signatures; the wrapper +converts between DER and fixed `r‖s` via `wc_ecc_sig_to_rs` and +`wc_ecc_rs_raw_to_sig`. +*/ + +#![cfg(all(feature = "signature", ecc, ecc_sign, ecc_verify, ecc_import, ecc_export, ecc_curve_ids, random))] + +use core::ffi::c_void; +use core::mem::size_of; + +use signature::{Error, Keypair, SignatureEncoding, SignerMut, Verifier}; + +use crate::ecc::ECC; +use crate::random::RNG; +use crate::sys; + +/// Build a fixed `r‖s` signature buffer from DER bytes produced by wolfCrypt. +fn der_to_rs( + der: &[u8], +) -> Result<[u8; SIG_SIZE], Error> { + debug_assert_eq!(SIG_SIZE, 2 * FIELD_SIZE); + let mut r_buf = [0u8; FIELD_SIZE]; + let mut s_buf = [0u8; FIELD_SIZE]; + let mut r_len = FIELD_SIZE as u32; + let mut s_len = FIELD_SIZE as u32; + let rc = unsafe { + sys::wc_ecc_sig_to_rs( + der.as_ptr(), der.len() as u32, + r_buf.as_mut_ptr(), &mut r_len, + s_buf.as_mut_ptr(), &mut s_len, + ) + }; + if rc != 0 { + return Err(Error::new()); + } + let r_len = r_len as usize; + let s_len = s_len as usize; + if r_len > FIELD_SIZE || s_len > FIELD_SIZE { + return Err(Error::new()); + } + let mut out = [0u8; SIG_SIZE]; + out[FIELD_SIZE - r_len..FIELD_SIZE].copy_from_slice(&r_buf[..r_len]); + out[SIG_SIZE - s_len..SIG_SIZE].copy_from_slice(&s_buf[..s_len]); + Ok(out) +} + +/// Build a DER signature from fixed `r‖s` bytes. +fn rs_to_der( + rs: &[u8], + der_out: &mut [u8], +) -> Result { + if rs.len() != 2 * FIELD_SIZE { + return Err(Error::new()); + } + let (r, s) = rs.split_at(FIELD_SIZE); + let mut der_len = der_out.len() as u32; + let rc = unsafe { + sys::wc_ecc_rs_raw_to_sig( + r.as_ptr(), FIELD_SIZE as u32, + s.as_ptr(), FIELD_SIZE as u32, + der_out.as_mut_ptr(), &mut der_len, + ) + }; + if rc != 0 { + return Err(Error::new()); + } + Ok(der_len as usize) +} + +macro_rules! define_ecdsa_curve { + ( + $(#[$meta:meta])* + ($signing_key:ident, $verifying_key:ident, $signature:ident), + field_size = $field_size:literal, + sig_size = $sig_size:literal, + x963_size = $x963_size:literal, + der_max = $der_max:literal, + curve_id = $curve_id:expr, + hash_type = $hash_type:expr, + hash_cfg = $hash_cfg:meta $(,)? + ) => { + /// Fixed-size ECDSA signature in `r‖s` form. + $(#[$meta])* + #[cfg($hash_cfg)] + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct $signature([u8; $sig_size]); + + #[cfg($hash_cfg)] + impl $signature { + /// Size in bytes of the fixed `r‖s` encoding. + pub const BYTE_SIZE: usize = $sig_size; + + /// Construct a signature from raw `r‖s` bytes. + pub const fn from_bytes(bytes: [u8; $sig_size]) -> Self { + Self(bytes) + } + + /// Return the raw `r‖s` bytes. + pub const fn to_bytes(&self) -> [u8; $sig_size] { + self.0 + } + } + + #[cfg($hash_cfg)] + impl AsRef<[u8]> for $signature { + fn as_ref(&self) -> &[u8] { &self.0 } + } + + #[cfg($hash_cfg)] + impl TryFrom<&[u8]> for $signature { + type Error = Error; + fn try_from(bytes: &[u8]) -> Result { + let arr: [u8; $sig_size] = bytes.try_into().map_err(|_| Error::new())?; + Ok(Self(arr)) + } + } + + #[cfg($hash_cfg)] + impl From<$signature> for [u8; $sig_size] { + fn from(sig: $signature) -> Self { sig.0 } + } + + #[cfg($hash_cfg)] + impl SignatureEncoding for $signature { + type Repr = [u8; $sig_size]; + } + + /// ECDSA signing key (private key + owned RNG + cached public key). + $(#[$meta])* + #[cfg($hash_cfg)] + pub struct $signing_key { + inner: ECC, + rng: RNG, + pub_bytes: [u8; $x963_size], + } + + #[cfg($hash_cfg)] + impl $signing_key { + /// Byte length of the uncompressed X9.63 public key encoding. + pub const PUB_KEY_SIZE: usize = $x963_size; + + /// Private-scalar byte length (`d`, curve field size). + pub const SCALAR_SIZE: usize = $field_size; + + /// Generate a fresh signing key using the provided RNG. + pub fn generate(mut rng: RNG) -> Result { + let ecc = ECC::generate_ex( + $field_size as i32, + &mut rng, + $curve_id, + None, None, + )?; + Self::from_ecc(ecc, rng) + } + + /// Import a signing key from unsigned big-endian public + /// coordinates `qx`, `qy` and private scalar `d`, each of exactly + /// the curve's field size in bytes. + pub fn import_unsigned( + qx: &[u8; $field_size], + qy: &[u8; $field_size], + d: &[u8; $field_size], + rng: RNG, + ) -> Result { + let ecc = ECC::import_unsigned(qx, qy, d, $curve_id, None, None)?; + Self::from_ecc(ecc, rng) + } + + /// Import a signing key from an uncompressed X9.63 public key + /// (leading `0x04` byte + `x‖y`) and a matching unsigned + /// big-endian private scalar `d`. + pub fn import_x963( + public_x963: &[u8; $x963_size], + d: &[u8; $field_size], + rng: RNG, + ) -> Result { + let ecc = ECC::import_private_key_ex( + d, public_x963, $curve_id, None, None, + )?; + Self::from_ecc(ecc, rng) + } + + /// Borrow the inner [`ECC`] key for operations not covered by the + /// signature traits. + pub fn as_ecc(&self) -> &ECC { &self.inner } + + /// Consume the signing key and return its `ECC` and `RNG` parts. + pub fn into_parts(self) -> (ECC, RNG) { + (self.inner, self.rng) + } + + /// Helper that caches the X9.63 public key bytes from an already + /// populated [`ECC`] and pairs it with the given `rng`. + fn from_ecc(mut ecc: ECC, rng: RNG) -> Result { + let mut pub_bytes = [0u8; $x963_size]; + let written = ecc.export_x963(&mut pub_bytes)?; + if written != $x963_size { + return Err(sys::wolfCrypt_ErrorCodes_BAD_FUNC_ARG); + } + Ok(Self { inner: ecc, rng, pub_bytes }) + } + } + + #[cfg($hash_cfg)] + impl Keypair for $signing_key { + type VerifyingKey = $verifying_key; + fn verifying_key(&self) -> $verifying_key { + $verifying_key { pub_bytes: self.pub_bytes } + } + } + + #[cfg($hash_cfg)] + impl SignerMut<$signature> for $signing_key { + fn try_sign(&mut self, msg: &[u8]) -> Result<$signature, Error> { + let mut der = [0u8; $der_max]; + let mut der_len: u32 = der.len() as u32; + let msg_len: u32 = msg.len().try_into().map_err(|_| Error::new())?; + let rc = unsafe { + sys::wc_SignatureGenerate( + $hash_type, + sys::wc_SignatureType_WC_SIGNATURE_TYPE_ECC, + msg.as_ptr(), msg_len, + der.as_mut_ptr(), &mut der_len, + &mut self.inner.wc_ecc_key as *mut _ as *mut c_void, + size_of::() as u32, + &mut self.rng.wc_rng, + ) + }; + if rc != 0 { + return Err(Error::new()); + } + let rs = der_to_rs::<$sig_size, $field_size>(&der[..der_len as usize])?; + Ok($signature(rs)) + } + } + + /// ECDSA verifying key. Owns the uncompressed X9.63 public key bytes + /// and instantiates a short-lived [`ECC`] on each verification. + $(#[$meta])* + #[cfg($hash_cfg)] + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct $verifying_key { + pub_bytes: [u8; $x963_size], + } + + #[cfg($hash_cfg)] + impl $verifying_key { + /// Byte length of the uncompressed X9.63 public key encoding. + pub const BYTE_SIZE: usize = $x963_size; + + /// Construct a verifying key from its uncompressed X9.63 bytes. + /// + /// The buffer must start with `0x04` followed by `x‖y` (each + /// `FIELD_SIZE` bytes). + pub const fn from_bytes(bytes: [u8; $x963_size]) -> Self { + Self { pub_bytes: bytes } + } + + /// Return the uncompressed X9.63 public key bytes. + pub const fn to_bytes(&self) -> [u8; $x963_size] { + self.pub_bytes + } + } + + #[cfg($hash_cfg)] + impl AsRef<[u8]> for $verifying_key { + fn as_ref(&self) -> &[u8] { &self.pub_bytes } + } + + #[cfg($hash_cfg)] + impl TryFrom<&[u8]> for $verifying_key { + type Error = Error; + fn try_from(bytes: &[u8]) -> Result { + let arr: [u8; $x963_size] = + bytes.try_into().map_err(|_| Error::new())?; + Ok(Self { pub_bytes: arr }) + } + } + + #[cfg($hash_cfg)] + impl Verifier<$signature> for $verifying_key { + fn verify(&self, msg: &[u8], sig: &$signature) -> Result<(), Error> { + let mut der = [0u8; $der_max]; + let der_len = rs_to_der::<$field_size>(&sig.0, &mut der)?; + let mut key = ECC::import_x963_ex(&self.pub_bytes, $curve_id, None, None) + .map_err(|_| Error::new())?; + let msg_len: u32 = msg.len().try_into().map_err(|_| Error::new())?; + let rc = unsafe { + sys::wc_SignatureVerify( + $hash_type, + sys::wc_SignatureType_WC_SIGNATURE_TYPE_ECC, + msg.as_ptr(), msg_len, + der.as_ptr(), der_len as u32, + &mut key.wc_ecc_key as *mut _ as *mut c_void, + size_of::() as u32, + ) + }; + if rc != 0 { + return Err(Error::new()); + } + Ok(()) + } + } + }; +} + +define_ecdsa_curve! { + /// NIST P-256 (secp256r1) paired with SHA-256. + (P256SigningKey, P256VerifyingKey, P256Signature), + field_size = 32, + sig_size = 64, + x963_size = 65, + der_max = 72, + curve_id = sys::ecc_curve_ids_ECC_SECP256R1, + hash_type = sys::wc_HashType_WC_HASH_TYPE_SHA256, + hash_cfg = sha256, +} + +define_ecdsa_curve! { + /// NIST P-384 (secp384r1) paired with SHA-384. + (P384SigningKey, P384VerifyingKey, P384Signature), + field_size = 48, + sig_size = 96, + x963_size = 97, + der_max = 104, + curve_id = sys::ecc_curve_ids_ECC_SECP384R1, + hash_type = sys::wc_HashType_WC_HASH_TYPE_SHA384, + hash_cfg = sha384, +} + +define_ecdsa_curve! { + /// NIST P-521 (secp521r1) paired with SHA-512. + (P521SigningKey, P521VerifyingKey, P521Signature), + field_size = 66, + sig_size = 132, + x963_size = 133, + der_max = 141, + curve_id = sys::ecc_curve_ids_ECC_SECP521R1, + hash_type = sys::wc_HashType_WC_HASH_TYPE_SHA512, + hash_cfg = sha512, +} diff --git a/wrapper/rust/wolfssl-wolfcrypt/src/ed25519.rs b/wrapper/rust/wolfssl-wolfcrypt/src/ed25519.rs index d0e7e4f1df..a0b91f1e27 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/src/ed25519.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/src/ed25519.rs @@ -1443,3 +1443,123 @@ impl Drop for Ed25519 { self.zeroize(); } } + +/// RustCrypto `signature` crate trait implementations. +/// +/// Provides a fixed-size [`Signature`] and a [`VerifyingKey`] type so that +/// [`Ed25519`] can be used wherever the `signature` crate's +/// [`signature::SignerMut`], [`signature::Keypair`], and +/// [`signature::Verifier`] traits are accepted. +#[cfg(feature = "signature")] +mod signature_impl { + use super::Ed25519; + use signature::Error; + + /// Ed25519 signature in its standard 64-byte encoded form. + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct Signature([u8; Ed25519::SIG_SIZE]); + + impl Signature { + /// Construct a signature from its raw bytes. + pub const fn from_bytes(bytes: [u8; Ed25519::SIG_SIZE]) -> Self { + Self(bytes) + } + + /// Return the raw signature bytes. + pub const fn to_bytes(&self) -> [u8; Ed25519::SIG_SIZE] { + self.0 + } + } + + impl AsRef<[u8]> for Signature { + fn as_ref(&self) -> &[u8] { + &self.0 + } + } + + impl TryFrom<&[u8]> for Signature { + type Error = Error; + fn try_from(bytes: &[u8]) -> Result { + let arr: [u8; Ed25519::SIG_SIZE] = bytes.try_into().map_err(|_| Error::new())?; + Ok(Self(arr)) + } + } + + impl From for [u8; Ed25519::SIG_SIZE] { + fn from(sig: Signature) -> Self { + sig.0 + } + } + + impl signature::SignatureEncoding for Signature { + type Repr = [u8; Ed25519::SIG_SIZE]; + } + + /// Ed25519 verifying (public) key. + /// + /// Owns a copy of the 32-byte compressed public key and instantiates a + /// short-lived wolfCrypt `ed25519_key` on each verification. + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct VerifyingKey([u8; Ed25519::PUB_KEY_SIZE]); + + impl VerifyingKey { + /// Construct a verifying key from its raw public key bytes. + pub const fn from_bytes(bytes: [u8; Ed25519::PUB_KEY_SIZE]) -> Self { + Self(bytes) + } + + /// Return the raw public key bytes. + pub const fn to_bytes(&self) -> [u8; Ed25519::PUB_KEY_SIZE] { + self.0 + } + } + + impl AsRef<[u8]> for VerifyingKey { + fn as_ref(&self) -> &[u8] { + &self.0 + } + } + + impl TryFrom<&[u8]> for VerifyingKey { + type Error = Error; + fn try_from(bytes: &[u8]) -> Result { + let arr: [u8; Ed25519::PUB_KEY_SIZE] = + bytes.try_into().map_err(|_| Error::new())?; + Ok(Self(arr)) + } + } + + #[cfg(all(ed25519_sign, ed25519_export))] + impl signature::Keypair for Ed25519 { + type VerifyingKey = VerifyingKey; + fn verifying_key(&self) -> Self::VerifyingKey { + let mut pub_key = [0u8; Ed25519::PUB_KEY_SIZE]; + self.export_public(&mut pub_key).expect("ed25519 export_public failed"); + VerifyingKey(pub_key) + } + } + + #[cfg(ed25519_sign)] + impl signature::SignerMut for Ed25519 { + fn try_sign(&mut self, msg: &[u8]) -> Result { + let mut sig = [0u8; Ed25519::SIG_SIZE]; + self.sign_msg(msg, &mut sig).map_err(|_| Error::new())?; + Ok(Signature(sig)) + } + } + + #[cfg(all(ed25519_import, ed25519_verify))] + impl signature::Verifier for VerifyingKey { + fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), Error> { + let mut key = Ed25519::new().map_err(|_| Error::new())?; + key.import_public(&self.0).map_err(|_| Error::new())?; + let valid = key + .verify_msg(&signature.0, msg) + .map_err(|_| Error::new())?; + if valid { Ok(()) } else { Err(Error::new()) } + } + } +} + +#[cfg(feature = "signature")] +pub use signature_impl::{Signature, VerifyingKey}; diff --git a/wrapper/rust/wolfssl-wolfcrypt/src/ed448.rs b/wrapper/rust/wolfssl-wolfcrypt/src/ed448.rs index 9191bed688..0a77aa071e 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/src/ed448.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/src/ed448.rs @@ -1368,3 +1368,127 @@ impl Drop for Ed448 { self.zeroize(); } } + +/// RustCrypto `signature` crate trait implementations. +/// +/// Provides a fixed-size [`Signature`] and a [`VerifyingKey`] type so that +/// [`Ed448`] can be used wherever the `signature` crate's +/// [`signature::SignerMut`], [`signature::Keypair`], and +/// [`signature::Verifier`] traits are accepted. +/// +/// These impls use the plain Ed448 (pure) signature variant with no context; +/// the context-, hashed-, and streaming-signature variants remain accessible +/// via the inherent methods on [`Ed448`]. +#[cfg(feature = "signature")] +mod signature_impl { + use super::Ed448; + use signature::Error; + + /// Ed448 signature in its standard 114-byte encoded form. + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct Signature([u8; Ed448::SIG_SIZE]); + + impl Signature { + /// Construct a signature from its raw bytes. + pub const fn from_bytes(bytes: [u8; Ed448::SIG_SIZE]) -> Self { + Self(bytes) + } + + /// Return the raw signature bytes. + pub const fn to_bytes(&self) -> [u8; Ed448::SIG_SIZE] { + self.0 + } + } + + impl AsRef<[u8]> for Signature { + fn as_ref(&self) -> &[u8] { + &self.0 + } + } + + impl TryFrom<&[u8]> for Signature { + type Error = Error; + fn try_from(bytes: &[u8]) -> Result { + let arr: [u8; Ed448::SIG_SIZE] = bytes.try_into().map_err(|_| Error::new())?; + Ok(Self(arr)) + } + } + + impl From for [u8; Ed448::SIG_SIZE] { + fn from(sig: Signature) -> Self { + sig.0 + } + } + + impl signature::SignatureEncoding for Signature { + type Repr = [u8; Ed448::SIG_SIZE]; + } + + /// Ed448 verifying (public) key. + /// + /// Owns a copy of the 57-byte compressed public key and instantiates a + /// short-lived wolfCrypt `ed448_key` on each verification. + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct VerifyingKey([u8; Ed448::PUB_KEY_SIZE]); + + impl VerifyingKey { + /// Construct a verifying key from its raw public key bytes. + pub const fn from_bytes(bytes: [u8; Ed448::PUB_KEY_SIZE]) -> Self { + Self(bytes) + } + + /// Return the raw public key bytes. + pub const fn to_bytes(&self) -> [u8; Ed448::PUB_KEY_SIZE] { + self.0 + } + } + + impl AsRef<[u8]> for VerifyingKey { + fn as_ref(&self) -> &[u8] { + &self.0 + } + } + + impl TryFrom<&[u8]> for VerifyingKey { + type Error = Error; + fn try_from(bytes: &[u8]) -> Result { + let arr: [u8; Ed448::PUB_KEY_SIZE] = + bytes.try_into().map_err(|_| Error::new())?; + Ok(Self(arr)) + } + } + + #[cfg(all(ed448_sign, ed448_export))] + impl signature::Keypair for Ed448 { + type VerifyingKey = VerifyingKey; + fn verifying_key(&self) -> Self::VerifyingKey { + let mut pub_key = [0u8; Ed448::PUB_KEY_SIZE]; + self.export_public(&mut pub_key).expect("ed448 export_public failed"); + VerifyingKey(pub_key) + } + } + + #[cfg(ed448_sign)] + impl signature::SignerMut for Ed448 { + fn try_sign(&mut self, msg: &[u8]) -> Result { + let mut sig = [0u8; Ed448::SIG_SIZE]; + self.sign_msg(msg, None, &mut sig).map_err(|_| Error::new())?; + Ok(Signature(sig)) + } + } + + #[cfg(all(ed448_import, ed448_verify))] + impl signature::Verifier for VerifyingKey { + fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), Error> { + let mut key = Ed448::new().map_err(|_| Error::new())?; + key.import_public(&self.0).map_err(|_| Error::new())?; + let valid = key + .verify_msg(&signature.0, msg, None) + .map_err(|_| Error::new())?; + if valid { Ok(()) } else { Err(Error::new()) } + } + } +} + +#[cfg(feature = "signature")] +pub use signature_impl::{Signature, VerifyingKey}; diff --git a/wrapper/rust/wolfssl-wolfcrypt/src/lib.rs b/wrapper/rust/wolfssl-wolfcrypt/src/lib.rs index 729c7cff96..0c954abce9 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/src/lib.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/src/lib.rs @@ -48,6 +48,8 @@ pub mod curve25519; pub mod dh; pub mod dilithium; pub mod ecc; +#[cfg(feature = "signature")] +pub mod ecdsa; pub mod ed25519; pub mod ed448; pub mod fips; @@ -59,7 +61,11 @@ pub mod mlkem; pub mod prf; pub mod random; pub mod rsa; +#[cfg(feature = "signature")] +pub mod rsa_pkcs1v15; pub mod sha; +#[cfg(feature = "digest")] +mod sha_digest; /// Convert a buffer length to `u32`, returning `BUFFER_E` if it overflows. pub(crate) fn buffer_len_to_u32(len: usize) -> Result { diff --git a/wrapper/rust/wolfssl-wolfcrypt/src/rsa.rs b/wrapper/rust/wolfssl-wolfcrypt/src/rsa.rs index 56526f34ab..3e89b79142 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/src/rsa.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/src/rsa.rs @@ -71,7 +71,7 @@ use core::mem::{MaybeUninit}; /// An instance can be created with `new_from_der()`, `new_public_from_der()`, /// or `generate()`. pub struct RSA { - wc_rsakey: sys::RsaKey, + pub(crate) wc_rsakey: sys::RsaKey, } impl RSA { @@ -362,6 +362,58 @@ impl RSA { Ok(rsa) } + /// Create a new RSA public key from raw modulus (`n`) and public + /// exponent (`e`) bytes in big-endian form. + /// + /// # Parameters + /// + /// * `n`: Big-endian modulus bytes. + /// * `e`: Big-endian public exponent bytes. + /// + /// # Returns + /// + /// Returns either Ok(RSA) containing the RSA struct instance or Err(e) + /// containing the wolfSSL library error code value. + pub fn new_public_from_raw(n: &[u8], e: &[u8]) -> Result { + Self::new_public_from_raw_ex(n, e, None, None) + } + + /// Create a new RSA public key from raw modulus (`n`) and public + /// exponent (`e`) bytes with optional heap and device ID. + pub fn new_public_from_raw_ex( + n: &[u8], e: &[u8], + heap: Option<*mut core::ffi::c_void>, dev_id: Option, + ) -> Result { + let n_size = crate::buffer_len_to_u32(n.len())?; + let e_size = crate::buffer_len_to_u32(e.len())?; + let mut wc_rsakey: MaybeUninit = MaybeUninit::uninit(); + let heap = match heap { + Some(heap) => heap, + None => core::ptr::null_mut(), + }; + let dev_id = match dev_id { + Some(dev_id) => dev_id, + None => sys::INVALID_DEVID, + }; + let rc = unsafe { sys::wc_InitRsaKey_ex(wc_rsakey.as_mut_ptr(), heap, dev_id) }; + if rc != 0 { + return Err(rc); + } + let mut wc_rsakey = unsafe { wc_rsakey.assume_init() }; + let rc = unsafe { + sys::wc_RsaPublicKeyDecodeRaw( + n.as_ptr(), n_size, + e.as_ptr(), e_size, + &mut wc_rsakey, + ) + }; + if rc != 0 { + unsafe { sys::wc_FreeRsaKey(&mut wc_rsakey); } + return Err(rc); + } + Ok(RSA { wc_rsakey }) + } + /// Generate a new RSA key using the given size and exponent. /// /// This function generates an RSA private key of length size (in bits) and diff --git a/wrapper/rust/wolfssl-wolfcrypt/src/rsa_pkcs1v15.rs b/wrapper/rust/wolfssl-wolfcrypt/src/rsa_pkcs1v15.rs new file mode 100644 index 0000000000..1effa323c9 --- /dev/null +++ b/wrapper/rust/wolfssl-wolfcrypt/src/rsa_pkcs1v15.rs @@ -0,0 +1,346 @@ +/* + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/*! +RSA PKCS#1 v1.5 trait impls for the RustCrypto `signature` crate. + +Provides fixed-size const-generic wrapper types over [`crate::rsa::RSA`] so +RSA PKCS#1 v1.5 signing/verifying fits cleanly into `no_std` without `alloc`: + +- [`SigningKey`] / [`VerifyingKey`] — `H` is a [`Hash`] marker + selecting the digest algorithm, `N` is the modulus size in bytes (e.g. + `256` for RSA-2048). +- [`Signature`] — fixed-size `[u8; N]` wrapper implementing + [`signature::SignatureEncoding`]. + +Signing and verifying delegate to `wc_SignatureGenerate` and +`wc_SignatureVerify` with `WC_SIGNATURE_TYPE_RSA_W_ENC`, which hash the raw +message and apply the PKCS#1 v1.5 DigestInfo encoding internally. +*/ + +#![cfg(all(feature = "signature", rsa, random))] + +use core::ffi::c_void; +use core::marker::PhantomData; +use core::mem::size_of; + +use signature::{Error, Keypair, SignatureEncoding, SignerMut, Verifier}; + +use crate::random::RNG; +use crate::rsa::RSA; +use crate::sys; + +mod private { + pub trait Sealed {} +} + +/// Marker trait selecting the digest algorithm used by PKCS#1 v1.5 DigestInfo +/// encoding. +pub trait Hash: private::Sealed { + /// wolfCrypt hash algorithm identifier. + const HASH_TYPE: u32; +} + +/// SHA-256 digest selection for PKCS#1 v1.5. +#[cfg(sha256)] +pub enum Sha256 {} +#[cfg(sha256)] +impl private::Sealed for Sha256 {} +#[cfg(sha256)] +impl Hash for Sha256 { + const HASH_TYPE: u32 = sys::wc_HashType_WC_HASH_TYPE_SHA256; +} + +/// SHA-384 digest selection for PKCS#1 v1.5. +#[cfg(sha384)] +pub enum Sha384 {} +#[cfg(sha384)] +impl private::Sealed for Sha384 {} +#[cfg(sha384)] +impl Hash for Sha384 { + const HASH_TYPE: u32 = sys::wc_HashType_WC_HASH_TYPE_SHA384; +} + +/// SHA-512 digest selection for PKCS#1 v1.5. +#[cfg(sha512)] +pub enum Sha512 {} +#[cfg(sha512)] +impl private::Sealed for Sha512 {} +#[cfg(sha512)] +impl Hash for Sha512 { + const HASH_TYPE: u32 = sys::wc_HashType_WC_HASH_TYPE_SHA512; +} + +/// Fixed-size RSA PKCS#1 v1.5 signature. `N` is the modulus size in bytes. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct Signature([u8; N]); + +impl Signature { + /// Construct a signature from its raw bytes. + pub const fn from_bytes(bytes: [u8; N]) -> Self { + Self(bytes) + } + + /// Return the raw signature bytes. + pub const fn to_bytes(&self) -> [u8; N] { + self.0 + } +} + +impl AsRef<[u8]> for Signature { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl TryFrom<&[u8]> for Signature { + type Error = Error; + fn try_from(bytes: &[u8]) -> Result { + let arr: [u8; N] = bytes.try_into().map_err(|_| Error::new())?; + Ok(Self(arr)) + } +} + +impl From> for [u8; N] { + fn from(sig: Signature) -> Self { + sig.0 + } +} + +impl SignatureEncoding for Signature { + type Repr = [u8; N]; +} + +fn check_modulus_size(rsa: &RSA, expected: usize) -> Result<(), i32> { + let actual = rsa.get_encrypt_size()?; + if actual != expected { + return Err(sys::wolfCrypt_ErrorCodes_BAD_FUNC_ARG); + } + Ok(()) +} + +/// RSA PKCS#1 v1.5 signing key. +/// +/// `H` selects the hash used in DigestInfo encoding; `N` is the expected +/// modulus size in bytes (e.g. `256` for RSA-2048, `384` for RSA-3072). +pub struct SigningKey { + inner: RSA, + rng: RNG, + _hash: PhantomData, +} + +impl SigningKey { + /// Generate a fresh `N * 8`-bit RSA key with public exponent 65537. + #[cfg(rsa_keygen)] + pub fn generate(mut rng: RNG) -> Result { + let bits: i32 = (N * 8).try_into().map_err(|_| sys::wolfCrypt_ErrorCodes_BAD_FUNC_ARG)?; + let rsa = RSA::generate(bits, 65537, &mut rng)?; + Ok(Self { inner: rsa, rng, _hash: PhantomData }) + } + + /// Adopt an existing [`RSA`] key, verifying that its modulus size in + /// bytes matches `N`. + pub fn from_rsa(rsa: RSA, rng: RNG) -> Result { + check_modulus_size(&rsa, N)?; + Ok(Self { inner: rsa, rng, _hash: PhantomData }) + } + + /// Borrow the inner [`RSA`] key. + pub fn as_rsa(&self) -> &RSA { + &self.inner + } + + /// Consume the signing key and return its `RSA` and `RNG` parts. + pub fn into_parts(self) -> (RSA, RNG) { + (self.inner, self.rng) + } +} + +impl SignerMut> for SigningKey { + fn try_sign(&mut self, msg: &[u8]) -> Result, Error> { + let mut sig = [0u8; N]; + let mut sig_len: u32 = N as u32; + let msg_len: u32 = msg.len().try_into().map_err(|_| Error::new())?; + let rc = unsafe { + sys::wc_SignatureGenerate( + H::HASH_TYPE, + sys::wc_SignatureType_WC_SIGNATURE_TYPE_RSA_W_ENC, + msg.as_ptr(), msg_len, + sig.as_mut_ptr(), &mut sig_len, + &mut self.inner.wc_rsakey as *mut _ as *mut c_void, + size_of::() as u32, + &mut self.rng.wc_rng, + ) + }; + if rc != 0 || sig_len as usize != N { + return Err(Error::new()); + } + Ok(Signature(sig)) + } +} + +const MAX_E_LEN: usize = 8; + +/// RSA PKCS#1 v1.5 verifying key. +/// +/// Owns a copy of the public key as raw `(n, e)` bytes and instantiates a +/// short-lived [`RSA`] on each verification. `H` selects the hash algorithm +/// used in DigestInfo encoding; `N` is the modulus size in bytes. +pub struct VerifyingKey { + n: [u8; N], + e: [u8; MAX_E_LEN], + e_len: u8, + _hash: PhantomData, +} + +// Manual impls avoid requiring `H: Clone`/`Copy`/etc. — `H` is a marker +// (uninhabited enum) that only appears inside `PhantomData`. +impl Clone for VerifyingKey { + fn clone(&self) -> Self { *self } +} +impl Copy for VerifyingKey {} +impl core::fmt::Debug for VerifyingKey { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("VerifyingKey") + .field("n", &&self.n[..]) + .field("e", &self.exponent()) + .finish() + } +} +impl PartialEq for VerifyingKey { + fn eq(&self, other: &Self) -> bool { + self.n == other.n && self.exponent() == other.exponent() + } +} +impl Eq for VerifyingKey {} + +impl VerifyingKey { + /// Construct a verifying key from raw big-endian modulus (`n`) and + /// public exponent (`e`) bytes. + pub fn from_components(n: &[u8], e: &[u8]) -> Result { + if n.len() != N || e.is_empty() || e.len() > MAX_E_LEN { + return Err(Error::new()); + } + let mut n_arr = [0u8; N]; + n_arr.copy_from_slice(n); + let mut e_arr = [0u8; MAX_E_LEN]; + e_arr[..e.len()].copy_from_slice(e); + Ok(Self { + n: n_arr, + e: e_arr, + e_len: e.len() as u8, + _hash: PhantomData, + }) + } + + /// Adopt an existing [`RSA`] public key, verifying its modulus size in + /// bytes matches `N`. + pub fn from_rsa(rsa: RSA) -> Result { + check_modulus_size(&rsa, N)?; + let mut n = [0u8; N]; + let mut e = [0u8; MAX_E_LEN]; + let mut n_len: u32 = n.len() as u32; + let mut e_len: u32 = e.len() as u32; + let rc = unsafe { + sys::wc_RsaFlattenPublicKey( + &rsa.wc_rsakey, + e.as_mut_ptr(), &mut e_len, + n.as_mut_ptr(), &mut n_len, + ) + }; + if rc != 0 { + return Err(rc); + } + if (n_len as usize) != N || e_len == 0 || (e_len as usize) > MAX_E_LEN { + return Err(sys::wolfCrypt_ErrorCodes_BAD_FUNC_ARG); + } + Ok(Self { + n, + e, + e_len: e_len as u8, + _hash: PhantomData, + }) + } + + /// Construct a verifying key from a DER-encoded `SubjectPublicKeyInfo` + /// / PKCS#1 public key. + pub fn from_public_der(der: &[u8]) -> Result { + let rsa = RSA::new_public_from_der(der)?; + Self::from_rsa(rsa) + } + + /// Return the raw modulus bytes. + pub const fn modulus(&self) -> &[u8; N] { + &self.n + } + + /// Return the raw public exponent bytes. + pub fn exponent(&self) -> &[u8] { + &self.e[..self.e_len as usize] + } +} + +impl Verifier> for VerifyingKey { + fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), Error> { + let msg_len: u32 = msg.len().try_into().map_err(|_| Error::new())?; + let mut rsa = RSA::new_public_from_raw(&self.n, self.exponent()) + .map_err(|_| Error::new())?; + let rc = unsafe { + sys::wc_SignatureVerify( + H::HASH_TYPE, + sys::wc_SignatureType_WC_SIGNATURE_TYPE_RSA_W_ENC, + msg.as_ptr(), msg_len, + signature.0.as_ptr(), N as u32, + &mut rsa.wc_rsakey as *mut _ as *mut c_void, + size_of::() as u32, + ) + }; + if rc != 0 { + return Err(Error::new()); + } + Ok(()) + } +} + +impl Keypair for SigningKey { + type VerifyingKey = VerifyingKey; + fn verifying_key(&self) -> VerifyingKey { + let mut n = [0u8; N]; + let mut e = [0u8; MAX_E_LEN]; + let mut n_len: u32 = n.len() as u32; + let mut e_len: u32 = e.len() as u32; + let rc = unsafe { + sys::wc_RsaFlattenPublicKey( + &self.inner.wc_rsakey, + e.as_mut_ptr(), &mut e_len, + n.as_mut_ptr(), &mut n_len, + ) + }; + if rc != 0 { + panic!("wc_RsaFlattenPublicKey failed: {rc}"); + } + VerifyingKey { + n, + e, + e_len: e_len as u8, + _hash: PhantomData, + } + } +} diff --git a/wrapper/rust/wolfssl-wolfcrypt/src/sha_digest.rs b/wrapper/rust/wolfssl-wolfcrypt/src/sha_digest.rs new file mode 100644 index 0000000000..7a62666215 --- /dev/null +++ b/wrapper/rust/wolfssl-wolfcrypt/src/sha_digest.rs @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/*! +RustCrypto `digest` trait implementations for the wolfCrypt SHA-family hash +types. + +This module provides implementations of the traits from the `digest` crate +(`HashMarker`, `OutputSizeUser`, `BlockSizeUser`, `Update`, `Reset`, +`FixedOutput`, and `FixedOutputReset`) for the fixed-output hash types +defined in [`crate::sha`]. With these implementations the `digest::Digest` +trait becomes available via its blanket implementation, allowing these +hashers to be used anywhere a RustCrypto `Digest` is accepted. + +Any failure returned by the underlying wolfCrypt call in a trait method will +result in a panic, matching the infallible signatures required by the +RustCrypto traits. +*/ + +use digest::consts::{ + U20, U28, U32, U48, U64, U72, U104, U128, U136, U144, +}; + +macro_rules! impl_digest_traits { + ( + $(#[$attr:meta])* + $ty:path, out = $output:ty, block = $block:ty + ) => { + $(#[$attr])* + impl Default for $ty { + fn default() -> Self { + <$ty>::new().expect("wolfCrypt hash init failed") + } + } + + $(#[$attr])* + impl digest::HashMarker for $ty {} + + $(#[$attr])* + impl digest::OutputSizeUser for $ty { + type OutputSize = $output; + } + + $(#[$attr])* + impl digest::block_api::BlockSizeUser for $ty { + type BlockSize = $block; + } + + $(#[$attr])* + impl digest::Update for $ty { + fn update(&mut self, data: &[u8]) { + <$ty>::update(self, data).expect("wolfCrypt hash update failed"); + } + } + + $(#[$attr])* + impl digest::Reset for $ty { + fn reset(&mut self) { + <$ty>::init(self).expect("wolfCrypt hash init failed"); + } + } + + $(#[$attr])* + impl digest::FixedOutput for $ty { + fn finalize_into(mut self, out: &mut digest::Output) { + <$ty>::finalize(&mut self, out.as_mut_slice()) + .expect("wolfCrypt hash finalize failed"); + } + } + + $(#[$attr])* + impl digest::FixedOutputReset for $ty { + fn finalize_into_reset(&mut self, out: &mut digest::Output) { + <$ty>::finalize(self, out.as_mut_slice()) + .expect("wolfCrypt hash finalize failed"); + <$ty>::init(self).expect("wolfCrypt hash init failed"); + } + } + }; +} + +impl_digest_traits! { + #[cfg(sha)] + crate::sha::SHA, out = U20, block = U64 +} + +impl_digest_traits! { + #[cfg(sha224)] + crate::sha::SHA224, out = U28, block = U64 +} + +impl_digest_traits! { + #[cfg(sha256)] + crate::sha::SHA256, out = U32, block = U64 +} + +impl_digest_traits! { + #[cfg(sha384)] + crate::sha::SHA384, out = U48, block = U128 +} + +impl_digest_traits! { + #[cfg(sha512)] + crate::sha::SHA512, out = U64, block = U128 +} + +impl_digest_traits! { + #[cfg(sha3)] + crate::sha::SHA3_224, out = U28, block = U144 +} + +impl_digest_traits! { + #[cfg(sha3)] + crate::sha::SHA3_256, out = U32, block = U136 +} + +impl_digest_traits! { + #[cfg(sha3)] + crate::sha::SHA3_384, out = U48, block = U104 +} + +impl_digest_traits! { + #[cfg(sha3)] + crate::sha::SHA3_512, out = U64, block = U72 +} diff --git a/wrapper/rust/wolfssl-wolfcrypt/tests/test_ecdsa.rs b/wrapper/rust/wolfssl-wolfcrypt/tests/test_ecdsa.rs new file mode 100644 index 0000000000..344a9d01e2 --- /dev/null +++ b/wrapper/rust/wolfssl-wolfcrypt/tests/test_ecdsa.rs @@ -0,0 +1,142 @@ +#![cfg(all(feature = "signature", ecc, ecc_sign, ecc_verify, ecc_curve_ids, random))] + +mod common; + +use signature::{Keypair, SignerMut, Verifier}; +use wolfssl_wolfcrypt::random::RNG; + +#[test] +#[cfg(sha256)] +fn test_p256_sign_verify() { + use wolfssl_wolfcrypt::ecdsa::{P256Signature, P256SigningKey, P256VerifyingKey}; + + common::setup(); + + let rng = RNG::new().expect("RNG"); + let mut sk = P256SigningKey::generate(rng).expect("generate P256"); + + let msg = b"ecdsa p256 signature trait test"; + let sig: P256Signature = sk.sign(msg); + + // Encoding round-trip. + let bytes = sig.to_bytes(); + assert_eq!(bytes.len(), 64); + let sig2 = P256Signature::try_from(bytes.as_ref()).expect("parse sig"); + assert_eq!(sig, sig2); + + // Wrong length must fail. + assert!(P256Signature::try_from(&bytes[..63]).is_err()); + + // Keypair provides a matching verifying key. + let vk: P256VerifyingKey = sk.verifying_key(); + vk.verify(msg, &sig).expect("verify"); + + // Tampered message fails. + let mut tampered = *msg; + tampered[0] ^= 0x01; + assert!(vk.verify(&tampered, &sig).is_err()); + + // VerifyingKey bytes round-trip. + let vk_bytes = vk.to_bytes(); + assert_eq!(vk_bytes.len(), 65); + assert_eq!(vk_bytes[0], 0x04); // uncompressed X9.63 tag + let vk2 = P256VerifyingKey::try_from(vk_bytes.as_ref()).expect("parse vk"); + assert_eq!(vk, vk2); + vk2.verify(msg, &sig).expect("verify via rebuilt vk"); +} + +#[test] +#[cfg(sha256)] +fn test_p256_import_unsigned_and_x963() { + use wolfssl_wolfcrypt::ecc::ECC; + use wolfssl_wolfcrypt::ecdsa::{P256SigningKey, P256VerifyingKey}; + + common::setup(); + + // Start from a freshly generated key so we have known-good (qx, qy, d). + let mut rng = RNG::new().expect("RNG"); + let mut src = ECC::generate_ex(32, &mut rng, ECC::SECP256R1, None, None) + .expect("generate ECC"); + let mut qx = [0u8; 32]; + let mut qy = [0u8; 32]; + let mut d_buf = [0u8; 32]; + let mut qx_len = 0u32; + let mut qy_len = 0u32; + let mut d_len = 0u32; + src.export_ex(&mut qx, &mut qx_len, &mut qy, &mut qy_len, &mut d_buf, &mut d_len, false) + .expect("export_ex"); + assert_eq!(qx_len as usize, 32); + assert_eq!(qy_len as usize, 32); + assert_eq!(d_len as usize, 32); + let mut x963 = [0u8; 65]; + let x963_written = src.export_x963(&mut x963).expect("export_x963"); + assert_eq!(x963_written, 65); + + let msg = b"ecdsa p256 import path"; + + // Path 1: raw unsigned components. + let rng = RNG::new().expect("RNG"); + let mut sk_a = P256SigningKey::import_unsigned(&qx, &qy, &d_buf, rng) + .expect("import_unsigned"); + let sig_a = sk_a.sign(msg); + sk_a.verifying_key().verify(msg, &sig_a).expect("verify a"); + + // Path 2: X9.63 public + private scalar. + let rng = RNG::new().expect("RNG"); + let mut sk_b = P256SigningKey::import_x963(&x963, &d_buf, rng) + .expect("import_x963"); + let sig_b = sk_b.sign(msg); + sk_b.verifying_key().verify(msg, &sig_b).expect("verify b"); + + // Both imported keys produce the same public key bytes. + let vk_a: P256VerifyingKey = sk_a.verifying_key(); + let vk_b: P256VerifyingKey = sk_b.verifying_key(); + assert_eq!(vk_a, vk_b); + + // Cross-verify: vk_a verifies a signature produced by sk_b. + vk_a.verify(msg, &sig_b).expect("cross-verify a/b"); +} + +#[test] +#[cfg(sha384)] +fn test_p384_sign_verify() { + use wolfssl_wolfcrypt::ecdsa::{P384Signature, P384SigningKey, P384VerifyingKey}; + + common::setup(); + + let rng = RNG::new().expect("RNG"); + let mut sk = P384SigningKey::generate(rng).expect("generate P384"); + + let msg = b"ecdsa p384 signature trait test"; + let sig: P384Signature = sk.sign(msg); + assert_eq!(sig.to_bytes().len(), 96); + + let vk: P384VerifyingKey = sk.verifying_key(); + vk.verify(msg, &sig).expect("verify p384"); + + let mut tampered = *msg; + tampered[5] ^= 0x80; + assert!(vk.verify(&tampered, &sig).is_err()); +} + +#[test] +#[cfg(sha512)] +fn test_p521_sign_verify() { + use wolfssl_wolfcrypt::ecdsa::{P521Signature, P521SigningKey, P521VerifyingKey}; + + common::setup(); + + let rng = RNG::new().expect("RNG"); + let mut sk = P521SigningKey::generate(rng).expect("generate P521"); + + let msg = b"ecdsa p521 signature trait test"; + let sig: P521Signature = sk.sign(msg); + assert_eq!(sig.to_bytes().len(), 132); + + let vk: P521VerifyingKey = sk.verifying_key(); + vk.verify(msg, &sig).expect("verify p521"); + + let mut tampered = *msg; + tampered[10] ^= 0x55; + assert!(vk.verify(&tampered, &sig).is_err()); +} diff --git a/wrapper/rust/wolfssl-wolfcrypt/tests/test_ed25519.rs b/wrapper/rust/wolfssl-wolfcrypt/tests/test_ed25519.rs index 8c7e225c28..2426281285 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/tests/test_ed25519.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/tests/test_ed25519.rs @@ -243,6 +243,43 @@ fn test_import_export() { ed.import_public_ex(&public, false).expect("Error with import_public_ex()"); } +#[test] +#[cfg(all(feature = "signature", ed25519_import, ed25519_export, ed25519_sign, ed25519_verify))] +fn test_signature_traits() { + use signature::{Keypair, SignerMut, Verifier}; + + common::setup(); + + let mut rng = RNG::new().expect("Error creating RNG"); + let mut ed = Ed25519::generate(&mut rng).expect("Error with generate()"); + + let message = b"message to sign via RustCrypto signature trait"; + let sig: Signature = ed.sign(message); + + // Round-trip the signature bytes through the SignatureEncoding machinery. + let bytes = sig.to_bytes(); + assert_eq!(bytes.len(), Ed25519::SIG_SIZE); + let sig_round_trip = Signature::try_from(bytes.as_ref()).expect("Signature::try_from bytes"); + assert_eq!(sig, sig_round_trip); + + // Reject signatures of the wrong length. + assert!(Signature::try_from(&bytes[..bytes.len() - 1]).is_err()); + + // VerifyingKey obtained via the Keypair trait verifies this signature. + let vk: VerifyingKey = ed.verifying_key(); + vk.verify(message, &sig).expect("Verifier::verify failed"); + + // A tampered message must fail verification. + let mut tampered = *message; + tampered[0] ^= 0x01; + assert!(vk.verify(&tampered, &sig).is_err()); + + // VerifyingKey bytes round-trip. + let vk_bytes = vk.to_bytes(); + let vk2 = VerifyingKey::try_from(vk_bytes.as_ref()).expect("VerifyingKey::try_from bytes"); + assert_eq!(vk, vk2); +} + #[test] fn test_sizes() { let mut rng = RNG::new().expect("Error creating RNG"); diff --git a/wrapper/rust/wolfssl-wolfcrypt/tests/test_ed448.rs b/wrapper/rust/wolfssl-wolfcrypt/tests/test_ed448.rs index d0f7186f12..858f041a34 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/tests/test_ed448.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/tests/test_ed448.rs @@ -247,6 +247,43 @@ fn test_import_export() { ed.import_public_ex(&public, false).expect("Error with import_public_ex()"); } +#[test] +#[cfg(all(feature = "signature", ed448_import, ed448_export, ed448_sign, ed448_verify))] +fn test_signature_traits() { + use signature::{Keypair, SignerMut, Verifier}; + + common::setup(); + + let mut rng = RNG::new().expect("Error creating RNG"); + let mut ed = Ed448::generate(&mut rng).expect("Error with generate()"); + + let message = b"message to sign via RustCrypto signature trait"; + let sig: Signature = ed.sign(message); + + // Round-trip the signature bytes through the SignatureEncoding machinery. + let bytes = sig.to_bytes(); + assert_eq!(bytes.len(), Ed448::SIG_SIZE); + let sig_round_trip = Signature::try_from(bytes.as_ref()).expect("Signature::try_from bytes"); + assert_eq!(sig, sig_round_trip); + + // Reject signatures of the wrong length. + assert!(Signature::try_from(&bytes[..bytes.len() - 1]).is_err()); + + // VerifyingKey obtained via the Keypair trait verifies this signature. + let vk: VerifyingKey = ed.verifying_key(); + vk.verify(message, &sig).expect("Verifier::verify failed"); + + // A tampered message must fail verification. + let mut tampered = *message; + tampered[0] ^= 0x01; + assert!(vk.verify(&tampered, &sig).is_err()); + + // VerifyingKey bytes round-trip. + let vk_bytes = vk.to_bytes(); + let vk2 = VerifyingKey::try_from(vk_bytes.as_ref()).expect("VerifyingKey::try_from bytes"); + assert_eq!(vk, vk2); +} + #[test] fn test_sizes() { let mut rng = RNG::new().expect("Error creating RNG"); diff --git a/wrapper/rust/wolfssl-wolfcrypt/tests/test_rsa_pkcs1v15.rs b/wrapper/rust/wolfssl-wolfcrypt/tests/test_rsa_pkcs1v15.rs new file mode 100644 index 0000000000..09f14ed016 --- /dev/null +++ b/wrapper/rust/wolfssl-wolfcrypt/tests/test_rsa_pkcs1v15.rs @@ -0,0 +1,81 @@ +#![cfg(all(feature = "signature", rsa, random))] + +mod common; + +use signature::{Keypair, SignerMut, Verifier}; +use wolfssl_wolfcrypt::random::RNG; + +#[test] +#[cfg(all(sha256, rsa_keygen))] +fn test_rsa2048_sha256_sign_verify() { + use wolfssl_wolfcrypt::rsa_pkcs1v15::{Sha256, Signature, SigningKey, VerifyingKey}; + + common::setup(); + + let rng = RNG::new().expect("RNG"); + let mut sk: SigningKey = SigningKey::generate(rng).expect("generate 2048"); + + let msg = b"rsa pkcs1v15 sha256 signature trait test"; + let sig: Signature<256> = sk.sign(msg); + + // Encoding round-trip. + let bytes = sig.to_bytes(); + assert_eq!(bytes.len(), 256); + let sig2 = Signature::<256>::try_from(bytes.as_ref()).expect("parse sig"); + assert_eq!(sig, sig2); + + // Wrong length must fail. + assert!(Signature::<256>::try_from(&bytes[..255]).is_err()); + + // Keypair gives a matching verifying key. + let vk: VerifyingKey = sk.verifying_key(); + vk.verify(msg, &sig).expect("verify"); + + // Tampered message fails. + let mut tampered = *msg; + tampered[0] ^= 0x01; + assert!(vk.verify(&tampered, &sig).is_err()); + + // VerifyingKey rebuilt from raw components still verifies. + let vk_copy = VerifyingKey::::from_components(vk.modulus(), vk.exponent()) + .expect("from_components"); + assert_eq!(vk, vk_copy); + vk_copy.verify(msg, &sig).expect("verify via rebuilt vk"); +} + +#[test] +#[cfg(all(sha384, rsa_keygen))] +fn test_rsa3072_sha384_sign_verify() { + use wolfssl_wolfcrypt::rsa_pkcs1v15::{Sha384, Signature, SigningKey, VerifyingKey}; + + common::setup(); + + let rng = RNG::new().expect("RNG"); + let mut sk: SigningKey = SigningKey::generate(rng).expect("generate 3072"); + + let msg = b"rsa pkcs1v15 sha384 signature trait test"; + let sig: Signature<384> = sk.sign(msg); + assert_eq!(sig.to_bytes().len(), 384); + + let vk: VerifyingKey = sk.verifying_key(); + vk.verify(msg, &sig).expect("verify"); + + let mut tampered = *msg; + tampered[2] ^= 0x10; + assert!(vk.verify(&tampered, &sig).is_err()); +} + +#[test] +#[cfg(all(sha256, rsa_keygen))] +fn test_modulus_size_mismatch_rejected() { + use wolfssl_wolfcrypt::rsa::RSA; + use wolfssl_wolfcrypt::rsa_pkcs1v15::{Sha256, SigningKey}; + + common::setup(); + + let mut rng = RNG::new().expect("RNG"); + let rsa2048 = RSA::generate(2048, 65537, &mut rng).expect("generate"); + // Attempt to adopt a 2048-bit key as if it were 3072. + let result: Result, _> = SigningKey::from_rsa(rsa2048, rng); + assert!(result.is_err(), "modulus size mismatch must be rejected"); +} diff --git a/wrapper/rust/wolfssl-wolfcrypt/tests/test_sha_digest.rs b/wrapper/rust/wolfssl-wolfcrypt/tests/test_sha_digest.rs new file mode 100644 index 0000000000..090cf1ac3d --- /dev/null +++ b/wrapper/rust/wolfssl-wolfcrypt/tests/test_sha_digest.rs @@ -0,0 +1,150 @@ +#![cfg(feature = "digest")] + +use digest::{Digest, FixedOutputReset}; +use digest::block_api::BlockSizeUser; + +mod common; + +fn check_digest( + input: &[u8], + expected: &[u8], + expected_block_size: usize, +) { + assert_eq!(::output_size(), expected.len()); + assert_eq!(::block_size(), expected_block_size); + + /* One-shot digest via associated function. */ + let out = D::digest(input); + assert_eq!(out.as_slice(), expected); + + /* Streaming via Digest::update and finalize. */ + let mut hasher = D::new(); + Digest::update(&mut hasher, input); + let out = hasher.finalize(); + assert_eq!(out.as_slice(), expected); + + /* Split update, via Default + Update + FixedOutputReset::finalize_reset. */ + let mut hasher = D::default(); + if input.len() >= 2 { + let mid = input.len() / 2; + Digest::update(&mut hasher, &input[..mid]); + Digest::update(&mut hasher, &input[mid..]); + } else { + Digest::update(&mut hasher, input); + } + let out = hasher.finalize_reset(); + assert_eq!(out.as_slice(), expected); + + /* After reset, the same hasher should produce the same result. */ + Digest::update(&mut hasher, input); + let out = hasher.finalize(); + assert_eq!(out.as_slice(), expected); +} + +#[test] +#[cfg(sha)] +fn test_digest_sha() { + use wolfssl_wolfcrypt::sha::SHA; + common::setup(); + check_digest::( + b"abc", + b"\xA9\x99\x3E\x36\x47\x06\x81\x6A\xBA\x3E\x25\x71\x78\x50\xC2\x6C\x9C\xD0\xD8\x9D", + 64, + ); +} + +#[test] +#[cfg(sha224)] +fn test_digest_sha224() { + use wolfssl_wolfcrypt::sha::SHA224; + common::setup(); + check_digest::( + b"abc", + b"\x23\x09\x7d\x22\x34\x05\xd8\x22\x86\x42\xa4\x77\xbd\xa2\x55\xb3\x2a\xad\xbc\xe4\xbd\xa0\xb3\xf7\xe3\x6c\x9d\xa7", + 64, + ); +} + +#[test] +#[cfg(sha256)] +fn test_digest_sha256() { + use wolfssl_wolfcrypt::sha::SHA256; + common::setup(); + check_digest::( + b"abc", + b"\xBA\x78\x16\xBF\x8F\x01\xCF\xEA\x41\x41\x40\xDE\x5D\xAE\x22\x23\xB0\x03\x61\xA3\x96\x17\x7A\x9C\xB4\x10\xFF\x61\xF2\x00\x15\xAD", + 64, + ); +} + +#[test] +#[cfg(sha384)] +fn test_digest_sha384() { + use wolfssl_wolfcrypt::sha::SHA384; + common::setup(); + check_digest::( + b"abc", + b"\xcb\x00\x75\x3f\x45\xa3\x5e\x8b\xb5\xa0\x3d\x69\x9a\xc6\x50\x07\x27\x2c\x32\xab\x0e\xde\xd1\x63\x1a\x8b\x60\x5a\x43\xff\x5b\xed\x80\x86\x07\x2b\xa1\xe7\xcc\x23\x58\xba\xec\xa1\x34\xc8\x25\xa7", + 128, + ); +} + +#[test] +#[cfg(sha512)] +fn test_digest_sha512() { + use wolfssl_wolfcrypt::sha::SHA512; + common::setup(); + check_digest::( + b"abc", + b"\xdd\xaf\x35\xa1\x93\x61\x7a\xba\xcc\x41\x73\x49\xae\x20\x41\x31\x12\xe6\xfa\x4e\x89\xa9\x7e\xa2\x0a\x9e\xee\xe6\x4b\x55\xd3\x9a\x21\x92\x99\x2a\x27\x4f\xc1\xa8\x36\xba\x3c\x23\xa3\xfe\xeb\xbd\x45\x4d\x44\x23\x64\x3c\xe8\x0e\x2a\x9a\xc9\x4f\xa5\x4c\xa4\x9f", + 128, + ); +} + +#[test] +#[cfg(sha3)] +fn test_digest_sha3_224() { + use wolfssl_wolfcrypt::sha::SHA3_224; + common::setup(); + check_digest::( + b"abc", + b"\xe6\x42\x82\x4c\x3f\x8c\xf2\x4a\xd0\x92\x34\xee\x7d\x3c\x76\x6f\xc9\xa3\xa5\x16\x8d\x0c\x94\xad\x73\xb4\x6f\xdf", + 144, + ); +} + +#[test] +#[cfg(sha3)] +fn test_digest_sha3_256() { + use wolfssl_wolfcrypt::sha::SHA3_256; + common::setup(); + check_digest::( + b"abc", + b"\x3a\x98\x5d\xa7\x4f\xe2\x25\xb2\x04\x5c\x17\x2d\x6b\xd3\x90\xbd\x85\x5f\x08\x6e\x3e\x9d\x52\x5b\x46\xbf\xe2\x45\x11\x43\x15\x32", + 136, + ); +} + +#[test] +#[cfg(sha3)] +fn test_digest_sha3_384() { + use wolfssl_wolfcrypt::sha::SHA3_384; + common::setup(); + check_digest::( + b"abc", + b"\xec\x01\x49\x82\x88\x51\x6f\xc9\x26\x45\x9f\x58\xe2\xc6\xad\x8d\xf9\xb4\x73\xcb\x0f\xc0\x8c\x25\x96\xda\x7c\xf0\xe4\x9b\xe4\xb2\x98\xd8\x8c\xea\x92\x7a\xc7\xf5\x39\xf1\xed\xf2\x28\x37\x6d\x25", + 104, + ); +} + +#[test] +#[cfg(sha3)] +fn test_digest_sha3_512() { + use wolfssl_wolfcrypt::sha::SHA3_512; + common::setup(); + check_digest::( + b"abc", + b"\xb7\x51\x85\x0b\x1a\x57\x16\x8a\x56\x93\xcd\x92\x4b\x6b\x09\x6e\x08\xf6\x21\x82\x74\x44\xf7\x0d\x88\x4f\x5d\x02\x40\xd2\x71\x2e\x10\xe1\x16\xe9\x19\x2a\xf3\xc9\x1a\x7e\xc5\x76\x47\xe3\x93\x40\x57\x34\x0b\x4c\xf4\x08\xd5\xa5\x65\x92\xf8\x27\x4e\xec\x53\xf0", + 72, + ); +}