-
Notifications
You must be signed in to change notification settings - Fork 995
Add SM2 support to the Rust wolfCrypt wrapper #10743
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -79,6 +79,8 @@ pub mod rsa_oaep; | |
| #[cfg(feature = "signature")] | ||
| pub mod rsa_pkcs1v15; | ||
| pub mod sha; | ||
| #[cfg(sm2)] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This cfg line gives me a duplicated attribute warning. Since you have |
||
| pub mod sm2; | ||
| #[cfg(all(feature = "password-hash", hmac, kdf_pbkdf2))] | ||
| pub mod pbkdf2_password_hash; | ||
| #[cfg(all(feature = "password-hash", kdf_scrypt))] | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rustdoc comments covering the parameters and return values should be added to these functions.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See https://github.com/wolfSSL/wolfssl/blob/master/wrapper/rust/wolfssl-wolfcrypt/src/ecc.rs#L42 for example formatting. |
||
| #[cfg(random)] | ||
| pub fn generate(rng: &RNG, flags: i32) -> Result<Self, i32> { | ||
| 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<usize, i32> { | ||
| let mut out_len = crate::buffer_len_to_u32(out.len())?; | ||
| let rc = unsafe { | ||
| sys::wc_ecc_sm2_shared_secret( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the wolfssl C library is built with ECC_TIMING_RESISTANT, then an RNG needs to be set for the ECC before calling this function. Although this could be done via sm2.key.set_rng(), it would be nicer for SM2 struct itself to provide a |
||
| 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<usize, i32> { | ||
| 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<bool, i32> { | ||
| 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) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These output length tests should test that the returned length is > 0 and <= KEY_SIZE, but not that they are necessarily equal to 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()); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should not be added here since v2.0.0 was published already. Just remove this line and I will include it with the next version we publish.