diff --git a/wrapper/rust/include.am b/wrapper/rust/include.am index 4c3df1d122..22656c1f64 100644 --- a/wrapper/rust/include.am +++ b/wrapper/rust/include.am @@ -43,6 +43,7 @@ EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/src/rsa_pkcs1v15.rs EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/src/scrypt_password_hash.rs EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/src/sha.rs EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/src/sha_digest.rs +EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/src/sm2.rs EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/src/sys.rs EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/tests/common/mod.rs EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/tests/test_aes.rs @@ -75,4 +76,5 @@ EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/tests/test_rsa_pkcs1v15.rs EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/tests/test_scrypt_password_hash.rs EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/tests/test_sha.rs EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/tests/test_sha_digest.rs +EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/tests/test_sm2.rs EXTRA_DIST += wrapper/rust/wolfssl-wolfcrypt/tests/test_wolfcrypt.rs diff --git a/wrapper/rust/wolfssl-wolfcrypt/CHANGELOG.md b/wrapper/rust/wolfssl-wolfcrypt/CHANGELOG.md index 620bbee02f..8a56c766a3 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/CHANGELOG.md +++ b/wrapper/rust/wolfssl-wolfcrypt/CHANGELOG.md @@ -11,6 +11,7 @@ New features: - Add BLAKE2 digest module (blake2_digest) - Add BLAKE2 MAC module (blake2_mac) - Add Aes192Ccm and Aes192Gcm +- Add SM2 wrapper (wolfssl_wolfcrypt::sm2 module) - Implement Clone for HMAC types - Improve cross-compilation and bare-metal target support in build.rs diff --git a/wrapper/rust/wolfssl-wolfcrypt/README.md b/wrapper/rust/wolfssl-wolfcrypt/README.md index ad57b33ec3..96fa34158c 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/README.md +++ b/wrapper/rust/wolfssl-wolfcrypt/README.md @@ -50,6 +50,7 @@ functionality: * PRF * RNG * RSA + * SM2 * scrypt * SHA * SHA-1, SHA-224, SHA-256, SHA-384, SHA-512, SHA3-224, SHA3-256, SHA3-384, diff --git a/wrapper/rust/wolfssl-wolfcrypt/build.rs b/wrapper/rust/wolfssl-wolfcrypt/build.rs index 646932366b..0ac5f2e0be 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/build.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/build.rs @@ -504,5 +504,12 @@ fn scan_cfg() -> Result<()> { check_cfg(&binding, "wc_InitShake128", "shake128"); check_cfg(&binding, "wc_InitShake256", "shake256"); + /* sm2 */ + check_cfg(&binding, "wc_ecc_sm2_make_key", "sm2"); + check_cfg(&binding, "wc_ecc_sm2_shared_secret", "sm2_dh"); + check_cfg(&binding, "wc_ecc_sm2_sign_hash", "sm2_sign"); + check_cfg(&binding, "wc_ecc_sm2_verify_hash", "sm2_verify"); + check_cfg(&binding, "wc_ecc_sm2_create_digest", "sm2_digest"); + Ok(()) } diff --git a/wrapper/rust/wolfssl-wolfcrypt/headers.h b/wrapper/rust/wolfssl-wolfcrypt/headers.h index 719c2f01f0..ec891914da 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/headers.h +++ b/wrapper/rust/wolfssl-wolfcrypt/headers.h @@ -22,3 +22,4 @@ #include "wolfssl/wolfcrypt/dilithium.h" #include "wolfssl/wolfcrypt/wc_mlkem.h" #include "wolfssl/wolfcrypt/wc_lms.h" +#include "wolfssl/wolfcrypt/sm2.h" diff --git a/wrapper/rust/wolfssl-wolfcrypt/src/ecc.rs b/wrapper/rust/wolfssl-wolfcrypt/src/ecc.rs index d0e3fefc79..8c3d0a12cb 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/src/ecc.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/src/ecc.rs @@ -396,6 +396,27 @@ impl ECC { pub const FLAG_COFACTOR: i32 = sys::WC_ECC_FLAG_COFACTOR as i32; pub const FLAG_DEC_SIGN: i32 = sys::WC_ECC_FLAG_DEC_SIGN as i32; + /// Allocate and initialize an ECC key without populating key material. + pub(crate) fn new() -> Result { + Self::new_ex(None, None) + } + + /// Allocate and initialize an ECC key without populating key material, + /// using an optional heap hint and device ID. + pub(crate) fn new_ex( + heap: Option<*mut core::ffi::c_void>, + dev_id: Option, + ) -> Result { + let heap = heap.unwrap_or(core::ptr::null_mut()); + let dev_id = dev_id.unwrap_or(sys::INVALID_DEVID); + let wc_ecc_key = Self::new_ecc_key(heap, dev_id)?; + Ok(Self { + wc_ecc_key, + #[cfg(random)] + rng: None, + }) + } + /// Allocate and initialize a new `sys::ecc_key` on the C heap. fn new_ecc_key(heap: *mut core::ffi::c_void, dev_id: i32) -> Result<*mut sys::ecc_key, i32> { let key = unsafe { sys::wc_ecc_key_new(heap) }; diff --git a/wrapper/rust/wolfssl-wolfcrypt/src/lib.rs b/wrapper/rust/wolfssl-wolfcrypt/src/lib.rs index f25e42a574..1725e3fd6e 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/src/lib.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/src/lib.rs @@ -79,6 +79,8 @@ pub mod rsa_oaep; #[cfg(feature = "signature")] pub mod rsa_pkcs1v15; pub mod sha; +#[cfg(sm2)] +pub mod sm2; #[cfg(all(feature = "password-hash", hmac, kdf_pbkdf2))] pub mod pbkdf2_password_hash; #[cfg(all(feature = "password-hash", kdf_scrypt))] diff --git a/wrapper/rust/wolfssl-wolfcrypt/src/sm2.rs b/wrapper/rust/wolfssl-wolfcrypt/src/sm2.rs new file mode 100644 index 0000000000..411c7c13c6 --- /dev/null +++ b/wrapper/rust/wolfssl-wolfcrypt/src/sm2.rs @@ -0,0 +1,162 @@ +/* + * 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 + */ + +/*! +This module provides a Rust wrapper for wolfCrypt SM2 functionality. +*/ + +#![cfg(sm2)] + +use crate::ecc::ECC; +#[cfg(random)] +use crate::random::RNG; +use crate::sys; + +/// An SM2 key backed by a wolfCrypt ECC key. +pub struct SM2 { + key: ECC, +} + +impl SM2 { + /// SM2 key size in bytes. + pub const KEY_SIZE: usize = sys::SM2_KEY_SIZE as usize; + + /// Default SM2 certificate signature identity. + pub const CERT_SIG_ID: &'static [u8] = b"1234567812345678"; + + /// wolfCrypt hash type identifier for SM3. + pub const HASH_TYPE_SM3: u32 = sys::wc_HashType_WC_HASH_TYPE_SM3; + + /// No ECC operation flags. + pub const FLAG_NONE: i32 = ECC::FLAG_NONE; + + /// Enable the ECC cofactor flag. + pub const FLAG_COFACTOR: i32 = ECC::FLAG_COFACTOR; + + /// Enable the ECC decrypt/sign flag. + pub const FLAG_DEC_SIGN: i32 = ECC::FLAG_DEC_SIGN; + + /// Generate a new SM2 key using the supplied random number generator. + #[cfg(random)] + pub fn generate(rng: &RNG, flags: i32) -> Result { + let key = ECC::new()?; + let rc = unsafe { sys::wc_ecc_sm2_make_key(rng.wc_rng, key.wc_ecc_key, flags) }; + if rc != 0 { + return Err(rc); + } + Ok(Self { key }) + } + + /// Derive a shared secret into the caller-supplied output buffer. + #[cfg(sm2_dh)] + pub fn shared_secret(&mut self, peer: &mut SM2, out: &mut [u8]) -> Result { + let mut out_len = crate::buffer_len_to_u32(out.len())?; + let rc = unsafe { + sys::wc_ecc_sm2_shared_secret( + self.key.wc_ecc_key, + peer.key.wc_ecc_key, + out.as_mut_ptr(), + &mut out_len, + ) + }; + if rc != 0 { + return Err(rc); + } + Ok(out_len as usize) + } + + /// Create an SM2 digest for an identity and message. + #[cfg(sm2_digest)] + pub fn create_digest( + &mut self, + id: &[u8], + message: &[u8], + hash_type: u32, + out: &mut [u8], + ) -> Result<(), i32> { + let id_len = u16::try_from(id.len()).map_err(|_| sys::wolfCrypt_ErrorCodes_BUFFER_E)?; + let message_len = crate::buffer_len_to_i32(message.len())?; + let out_len = crate::buffer_len_to_i32(out.len())?; + let rc = unsafe { + sys::wc_ecc_sm2_create_digest( + id.as_ptr(), + id_len, + message.as_ptr(), + message_len, + hash_type as sys::wc_HashType, + out.as_mut_ptr(), + out_len, + self.key.wc_ecc_key, + ) + }; + if rc != 0 { + return Err(rc); + } + Ok(()) + } + + /// Sign a hash with this SM2 key and return the DER signature length. + #[cfg(all(sm2_sign, random))] + pub fn sign_hash( + &mut self, + hash: &[u8], + signature: &mut [u8], + rng: &RNG, + ) -> Result { + let hash_len = crate::buffer_len_to_u32(hash.len())?; + let mut signature_len = crate::buffer_len_to_u32(signature.len())?; + let rc = unsafe { + sys::wc_ecc_sm2_sign_hash( + hash.as_ptr(), + hash_len, + signature.as_mut_ptr(), + &mut signature_len, + rng.wc_rng, + self.key.wc_ecc_key, + ) + }; + if rc != 0 { + return Err(rc); + } + Ok(signature_len as usize) + } + + /// Verify a DER-encoded SM2 signature against a hash. + #[cfg(sm2_verify)] + pub fn verify_hash(&mut self, signature: &[u8], hash: &[u8]) -> Result { + let signature_len = crate::buffer_len_to_u32(signature.len())?; + let hash_len = crate::buffer_len_to_u32(hash.len())?; + let mut valid = 0; + let rc = unsafe { + sys::wc_ecc_sm2_verify_hash( + signature.as_ptr(), + signature_len, + hash.as_ptr(), + hash_len, + &mut valid, + self.key.wc_ecc_key, + ) + }; + if rc != 0 { + return Err(rc); + } + Ok(valid != 0) + } +} diff --git a/wrapper/rust/wolfssl-wolfcrypt/tests/test_sm2.rs b/wrapper/rust/wolfssl-wolfcrypt/tests/test_sm2.rs new file mode 100644 index 0000000000..e084ea61ab --- /dev/null +++ b/wrapper/rust/wolfssl-wolfcrypt/tests/test_sm2.rs @@ -0,0 +1,130 @@ +#![cfg(sm2)] + +mod common; + +#[cfg(random)] +use wolfssl_wolfcrypt::random::RNG; +use wolfssl_wolfcrypt::sm2::SM2; + +#[test] +#[cfg(random)] +fn test_sm2_generate() { + common::setup(); + let rng = RNG::new().expect("Failed to create RNG"); + SM2::generate(&rng, SM2::FLAG_NONE).expect("Error with generate()"); +} + +#[test] +#[cfg(all(random, sm2_digest))] +fn test_sm2_create_digest() { + common::setup(); + let rng = RNG::new().expect("Failed to create RNG"); + let mut key = SM2::generate(&rng, SM2::FLAG_NONE).expect("Error generating SM2 key"); + let mut digest = [0u8; 32]; + + key.create_digest( + SM2::CERT_SIG_ID, + b"message digest", + SM2::HASH_TYPE_SM3, + &mut digest, + ) + .expect("Error creating SM2 digest"); + + assert_ne!(digest, [0u8; 32]); +} + +#[test] +#[cfg(all(random, sm2_digest, sm2_sign, sm2_verify))] +fn test_sm2_sign_and_verify() { + common::setup(); + let rng = RNG::new().expect("Failed to create RNG"); + let mut key = SM2::generate(&rng, SM2::FLAG_NONE).expect("Error generating SM2 key"); + let mut digest = [0u8; 32]; + key.create_digest( + SM2::CERT_SIG_ID, + b"message digest", + SM2::HASH_TYPE_SM3, + &mut digest, + ) + .expect("Error creating SM2 digest"); + + let mut signature = [0u8; 80]; + let signature_len = key + .sign_hash(&digest, &mut signature, &rng) + .expect("Error signing SM2 digest"); + assert!(signature_len > 0 && signature_len <= signature.len()); + + let valid = key + .verify_hash(&signature[..signature_len], &digest) + .expect("Error verifying SM2 signature"); + assert!(valid); + + digest[0] ^= 0x01; + let valid = key + .verify_hash(&signature[..signature_len], &digest) + .expect("Error verifying modified SM2 digest"); + assert!(!valid); +} + +#[test] +#[cfg(all(random, sm2_digest))] +fn test_sm2_digest_rejects_small_buffer() { + common::setup(); + let rng = RNG::new().expect("Failed to create RNG"); + let mut key = SM2::generate(&rng, SM2::FLAG_NONE).expect("Error generating SM2 key"); + let mut digest = [0u8; 31]; + + let result = key.create_digest( + SM2::CERT_SIG_ID, + b"message digest", + SM2::HASH_TYPE_SM3, + &mut digest, + ); + assert!(result.is_err()); +} + +#[test] +#[cfg(all(random, sm2_sign))] +fn test_sm2_sign_rejects_small_buffer() { + common::setup(); + let rng = RNG::new().expect("Failed to create RNG"); + let mut key = SM2::generate(&rng, SM2::FLAG_NONE).expect("Error generating SM2 key"); + let digest = [0x42u8; 32]; + let mut signature = [0u8; 1]; + + assert!(key.sign_hash(&digest, &mut signature, &rng).is_err()); +} + +#[test] +#[cfg(all(random, sm2_dh))] +fn test_sm2_shared_secret() { + common::setup(); + let rng = RNG::new().expect("Failed to create RNG"); + let mut alice = SM2::generate(&rng, SM2::FLAG_NONE).expect("Error generating Alice key"); + let mut bob = SM2::generate(&rng, SM2::FLAG_NONE).expect("Error generating Bob key"); + let mut alice_secret = [0u8; SM2::KEY_SIZE]; + let mut bob_secret = [0u8; SM2::KEY_SIZE]; + + let alice_len = alice + .shared_secret(&mut bob, &mut alice_secret) + .expect("Error deriving Alice shared secret"); + let bob_len = bob + .shared_secret(&mut alice, &mut bob_secret) + .expect("Error deriving Bob shared secret"); + + assert_eq!(alice_len, SM2::KEY_SIZE); + assert_eq!(alice_len, bob_len); + assert_eq!(alice_secret[..alice_len], bob_secret[..bob_len]); +} + +#[test] +#[cfg(all(random, sm2_dh))] +fn test_sm2_shared_secret_rejects_small_buffer() { + common::setup(); + let rng = RNG::new().expect("Failed to create RNG"); + let mut alice = SM2::generate(&rng, SM2::FLAG_NONE).expect("Error generating Alice key"); + let mut bob = SM2::generate(&rng, SM2::FLAG_NONE).expect("Error generating Bob key"); + let mut secret = [0u8; 1]; + + assert!(alice.shared_secret(&mut bob, &mut secret).is_err()); +}