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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions wrapper/rust/include.am
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
1 change: 1 addition & 0 deletions wrapper/rust/wolfssl-wolfcrypt/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This 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.

- Implement Clone for HMAC types
- Improve cross-compilation and bare-metal target support in build.rs

Expand Down
1 change: 1 addition & 0 deletions wrapper/rust/wolfssl-wolfcrypt/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
7 changes: 7 additions & 0 deletions wrapper/rust/wolfssl-wolfcrypt/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
1 change: 1 addition & 0 deletions wrapper/rust/wolfssl-wolfcrypt/headers.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
21 changes: 21 additions & 0 deletions wrapper/rust/wolfssl-wolfcrypt/src/ecc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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, i32> {
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<i32>,
) -> Result<Self, i32> {
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) };
Expand Down
2 changes: 2 additions & 0 deletions wrapper/rust/wolfssl-wolfcrypt/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ pub mod rsa_oaep;
#[cfg(feature = "signature")]
pub mod rsa_pkcs1v15;
pub mod sha;
#[cfg(sm2)]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This cfg line gives me a duplicated attribute warning. Since you have #![cfg(sm2)] in sm2.rs, you can remove this line to avoid the warning.

pub mod sm2;
#[cfg(all(feature = "password-hash", hmac, kdf_pbkdf2))]
pub mod pbkdf2_password_hash;
#[cfg(all(feature = "password-hash", kdf_scrypt))]
Expand Down
162 changes: 162 additions & 0 deletions wrapper/rust/wolfssl-wolfcrypt/src/sm2.rs
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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Rustdoc comments covering the parameters and return values should be added to these functions.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

#[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(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 set_rng() and set_shared_rng() that just forward the call to the contained key member.

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)
}
}
130 changes: 130 additions & 0 deletions wrapper/rust/wolfssl-wolfcrypt/tests/test_sm2.rs
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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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());
}
Loading