发布 v0.3.0:rustls CryptoProvider 支持

新增功能:
- rustls CryptoProvider:完整 rustls 0.23.x 支持
  - SM3 Hash/Context trait 实现
  - SM3 HMAC trait 实现
  - SM4-GCM/CCM AEAD 密码套件
  - SM2 ECDHE 密钥交换
  - SM2 签名/验签算法
- SM3 流式 HMAC(HmacSm3)
- SM2 SPKI DER 编码
- SM2 DEFAULT_ID 常量

文档更新:
- README 依赖版本更新为 0.3
- CHANGELOG 添加 v0.3.0 变更记录
- SECURITY 更新版本支持表
This commit is contained in:
huangxt
2026-03-09 10:13:09 +08:00
parent 2580571881
commit 7c159343f8
19 changed files with 950 additions and 9 deletions
+26
View File
@@ -5,6 +5,31 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.3.0] - 2025-03-09
### Added
- **rustls CryptoProvider** (`rustls_provider` module, requires `rustls` feature)
- Full rustls 0.23.x `CryptoProvider` implementation for TLCP (Chinese TLS)
- `hash.rs`: SM3 `Hash` and `Context` trait implementations
- `hmac.rs`: SM3 HMAC trait implementation
- `tls13.rs`: SM4-GCM/CCM AEAD cipher suites (TLS13_SM4_GCM_SM3, TLS13_SM4_CCM_SM3)
- `kx.rs`: SM2 ECDHE key exchange
- `sign.rs`: SM2 signing algorithm
- `verify.rs`: SM2 signature verification
- `mod.rs`: `crypto_provider()` function returning complete `CryptoProvider`
- **SM3 streaming HMAC** (`HmacSm3`)
- `update()`: Streaming data input
- `finalize()`: Final HMAC computation
- **SM2 SPKI DER encoding**
- `public_key_to_spki_der()`: Encode public key as RFC 5480 SubjectPublicKeyInfo
- **SM2 DEFAULT_ID constant**
- `DEFAULT_ID`: Default user ID "1234567812345678" (16 bytes)
### Changed
- `Cargo.toml`: Added `rustls` optional feature with dependencies
## [0.2.1] - 2025-03-08
### Added
@@ -120,6 +145,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- XTS: reject non-16-byte-aligned input instead of silently truncating
- SM9 `hash_to_range`: replaced variable-iteration `while` loop with constant-time conditional select
[0.3.0]: https://github.com/kintaiW/libsmx/releases/tag/v0.3.0
[0.2.1]: https://github.com/kintaiW/libsmx/releases/tag/v0.2.1
[0.2.0]: https://github.com/kintaiW/libsmx/releases/tag/v0.2.0
[0.1.1]: https://github.com/kintaiW/libsmx/releases/tag/v0.1.1
+26
View File
@@ -5,6 +5,31 @@
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)
本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
## [0.3.0] - 2025-03-09
### 新增
- **rustls CryptoProvider**`rustls_provider` 模块,需 `rustls` 特性)
- 完整的 rustls 0.23.x `CryptoProvider` 实现,支持 TLCP(国密 TLS)
- `hash.rs`SM3 `Hash``Context` trait 实现
- `hmac.rs`SM3 HMAC trait 实现
- `tls13.rs`SM4-GCM/CCM AEAD 密码套件(TLS13_SM4_GCM_SM3、TLS13_SM4_CCM_SM3
- `kx.rs`SM2 ECDHE 密钥交换
- `sign.rs`SM2 签名算法
- `verify.rs`SM2 验签算法
- `mod.rs``crypto_provider()` 函数返回完整 `CryptoProvider`
- **SM3 流式 HMAC**`HmacSm3`
- `update()`:流式数据输入
- `finalize()`:最终 HMAC 计算
- **SM2 SPKI DER 编码**
- `public_key_to_spki_der()`:将公钥编码为 RFC 5480 SubjectPublicKeyInfo
- **SM2 DEFAULT_ID 常量**
- `DEFAULT_ID`:默认用户 ID "1234567812345678"16 字节)
### 变更
- `Cargo.toml`:添加 `rustls` 可选特性及依赖
## [0.2.1] - 2025-03-08
### 新增
@@ -118,6 +143,7 @@
- XTS:拒绝非 16 字节对齐输入,而非静默截断
- SM9 `hash_to_range`:用常量时间条件选择替换可变迭代 `while` 循环
[0.3.0]: https://github.com/kintaiW/libsmx/releases/tag/v0.3.0
[0.2.1]: https://github.com/kintaiW/libsmx/releases/tag/v0.2.1
[0.2.0]: https://github.com/kintaiW/libsmx/releases/tag/v0.2.0
[0.1.1]: https://github.com/kintaiW/libsmx/releases/tag/v0.1.1
Generated
+23 -1
View File
@@ -422,13 +422,16 @@ checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
[[package]]
name = "libsmx"
version = "0.2.1"
version = "0.3.0"
dependencies = [
"criterion",
"crypto-bigint",
"getrandom",
"hex",
"rand 0.8.5",
"rand_core 0.6.4",
"rustls",
"rustls-pki-types",
"sm9_core",
"subtle",
"zeroize",
@@ -635,6 +638,25 @@ version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
[[package]]
name = "rustls"
version = "0.24.0-dev.0"
dependencies = [
"once_cell",
"rustls-pki-types",
"subtle",
"zeroize",
]
[[package]]
name = "rustls-pki-types"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd"
dependencies = [
"zeroize",
]
[[package]]
name = "rustversion"
version = "1.0.22"
+6 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "libsmx"
version = "0.2.1"
version = "0.3.0"
edition = "2021"
rust-version = "1.83.0"
license = "Apache-2.0"
@@ -35,11 +35,16 @@ crypto-bigint = { version = "0.6", default-features = false }
subtle = { version = "2.6", default-features = false }
zeroize = { version = "1.8", default-features = false, features = ["derive"] }
rand_core = { version = "0.6", default-features = false }
rustls = { version = "0.24.0-dev.0", path = "../rustls/rustls", optional = true, default-features = false }
pki-types = { package = "rustls-pki-types", version = "1", optional = true, features = ["alloc"] }
getrandom = { version = "0.2", optional = true }
[features]
default = ["alloc"]
alloc = []
std = ["alloc", "rand_core/std"]
# rustls CryptoProvider 集成(RFC 8998 国密 TLS 套件)
rustls-provider = ["alloc", "dep:rustls", "dep:pki-types", "dep:getrandom"]
[dev-dependencies]
hex = "0.4"
+2 -2
View File
@@ -33,7 +33,7 @@ Add to `Cargo.toml`:
```toml
[dependencies]
libsmx = "0.2"
libsmx = "0.3"
```
### SM3 Hash
@@ -156,7 +156,7 @@ For `no_std` without `alloc`:
```toml
[dependencies]
libsmx = { version = "0.2", default-features = false }
libsmx = { version = "0.3", default-features = false }
```
## Benchmarks
+2 -2
View File
@@ -33,7 +33,7 @@
```toml
[dependencies]
libsmx = "0.2"
libsmx = "0.3"
```
### SM3 哈希
@@ -188,7 +188,7 @@ assert_eq!(decrypted, plaintext);
```toml
[dependencies]
libsmx = { version = "0.2", default-features = false }
libsmx = { version = "0.3", default-features = false }
```
`alloc` 时,SM3 哈希、SM3 HMAC、SM2 签名/验签、SM4 ECB 仍可用(固定大小数组 API)。
+1
View File
@@ -4,6 +4,7 @@
| Version | Supported |
|---------|-----------|
| 0.3.x | Yes |
| 0.2.x | Yes |
| 0.1.x | Yes |
| < 0.1 | No |
+1
View File
@@ -4,6 +4,7 @@
| 版本 | 是否支持 |
|------|----------|
| 0.3.x | 是 |
| 0.2.x | 是 |
| 0.1.x | 是 |
| < 0.1 | 否 |
+3
View File
@@ -61,3 +61,6 @@ pub mod sm9;
pub mod bls;
pub mod fpe;
#[cfg(feature = "rustls-provider")]
pub mod rustls_provider;
+51
View File
@@ -0,0 +1,51 @@
//! SM3 → rustls `crypto::hash::Hash` / `crypto::hash::Context`
use alloc::boxed::Box;
use rustls::crypto::{self, HashAlgorithm};
use crate::sm3::Sm3Hasher;
/// 静态 SM3 哈希实现
pub(crate) static SM3: Sm3Hash = Sm3Hash;
pub(crate) struct Sm3Hash;
impl crypto::hash::Hash for Sm3Hash {
fn start(&self) -> Box<dyn crypto::hash::Context> {
Box::new(Sm3Context(Sm3Hasher::new()))
}
fn hash(&self, data: &[u8]) -> crypto::hash::Output {
crypto::hash::Output::new(&Sm3Hasher::digest(data))
}
fn output_len(&self) -> usize {
32
}
fn algorithm(&self) -> HashAlgorithm {
// Reason: SM3 尚无 IANA TLS HashAlgorithm 标准编号,暂用 Unknown(0x07)
HashAlgorithm::Unknown(0x07)
}
}
struct Sm3Context(Sm3Hasher);
impl crypto::hash::Context for Sm3Context {
fn fork_finish(&self) -> crypto::hash::Output {
crypto::hash::Output::new(&self.0.clone().finalize())
}
fn fork(&self) -> Box<dyn crypto::hash::Context> {
Box::new(Sm3Context(self.0.clone()))
}
fn finish(self: Box<Self>) -> crypto::hash::Output {
crypto::hash::Output::new(&self.0.finalize())
}
fn update(&mut self, data: &[u8]) {
self.0.update(data);
}
}
+45
View File
@@ -0,0 +1,45 @@
//! SM3-HMAC → rustls `crypto::hmac::Hmac` / `crypto::hmac::Key`
use alloc::boxed::Box;
use alloc::vec::Vec;
use rustls::crypto::hmac;
use crate::sm3::HmacSm3;
/// 静态 HMAC-SM3 实现(传入 `HkdfUsingHmac`
pub(crate) static HMAC_SM3: Sm3Hmac = Sm3Hmac;
pub(crate) struct Sm3Hmac;
impl hmac::Hmac for Sm3Hmac {
fn with_key(&self, key: &[u8]) -> Box<dyn hmac::Key> {
Box::new(Sm3HmacKey { key: key.to_vec() })
}
fn hash_output_len(&self) -> usize {
32
}
}
struct Sm3HmacKey {
key: Vec<u8>,
}
impl hmac::Key for Sm3HmacKey {
fn sign_concat(&self, first: &[u8], middle: &[&[u8]], last: &[u8]) -> hmac::Tag {
// Reason: rustls 将消息分为 first / middle / last 三段,
// 用流式 HmacSm3 逐段喂入,避免额外拷贝和堆分配
let mut mac = HmacSm3::new(&self.key);
mac.update(first);
for chunk in middle {
mac.update(chunk);
}
mac.update(last);
hmac::Tag::new(&mac.finalize())
}
fn tag_len(&self) -> usize {
32
}
}
+83
View File
@@ -0,0 +1,83 @@
//! SM2 ECDHE 密钥交换 → rustls `SupportedKxGroup` / `ActiveKeyExchange`
use alloc::boxed::Box;
use alloc::vec::Vec;
use rustls::crypto::kx::{ActiveKeyExchange, NamedGroup, SharedSecret, StartedKeyExchange, SupportedKxGroup};
use rustls::error::{Error, PeerMisbehaved};
use crate::sm2::{generate_keypair, key_exchange::ecdh_from_slice, PrivateKey};
/// SM2 ECDHE 密钥交换组(RFC 8998 curveSM2
pub static CURVE_SM2: &dyn SupportedKxGroup = &Sm2KxGroup;
#[derive(Debug)]
struct Sm2KxGroup;
impl SupportedKxGroup for Sm2KxGroup {
fn start(&self) -> Result<StartedKeyExchange, Error> {
// 使用 getrandom 生成随机标量
let mut rng = Sm2Rng;
let (private_key, pub_key_bytes) = generate_keypair(&mut rng);
Ok(StartedKeyExchange::Single(Box::new(Sm2KeyExchange {
private_key,
pub_key: pub_key_bytes.to_vec(),
})))
}
fn name(&self) -> NamedGroup {
NamedGroup::curveSM2
}
}
struct Sm2KeyExchange {
private_key: PrivateKey,
pub_key: Vec<u8>,
}
impl ActiveKeyExchange for Sm2KeyExchange {
fn complete(self: Box<Self>, peer_pub_key: &[u8]) -> Result<SharedSecret, Error> {
// 标准 ECDHE:SM2 曲线标量乘法,输出共享密钥 x 坐标(32 字节)
let shared = ecdh_from_slice(&self.private_key, peer_pub_key)
.map_err(|_| Error::from(PeerMisbehaved::InvalidKeyShare))?;
Ok(SharedSecret::from(shared.as_ref()))
}
fn pub_key(&self) -> &[u8] {
&self.pub_key
}
fn group(&self) -> NamedGroup {
NamedGroup::curveSM2
}
}
// ── 内部 RNG 包装(使用 getrandom)────────────────────────────────────────────
pub(crate) struct Sm2Rng;
impl rand_core::RngCore for Sm2Rng {
fn next_u32(&mut self) -> u32 {
let mut bytes = [0u8; 4];
getrandom::getrandom(&mut bytes).expect("getrandom failed");
u32::from_le_bytes(bytes)
}
fn next_u64(&mut self) -> u64 {
let mut bytes = [0u8; 8];
getrandom::getrandom(&mut bytes).expect("getrandom failed");
u64::from_le_bytes(bytes)
}
fn fill_bytes(&mut self, dest: &mut [u8]) {
getrandom::getrandom(dest).expect("getrandom failed");
}
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> {
getrandom::getrandom(dest).map_err(|e| {
// Reason: rand_core::Error::new 接受 NonZeroU32,直接用 getrandom 错误码
use core::num::NonZeroU32;
rand_core::Error::from(NonZeroU32::new(e.raw_os_error().unwrap_or(1) as u32).unwrap_or(NonZeroU32::new(1).unwrap()))
})
}
}
+76
View File
@@ -0,0 +1,76 @@
//! rustls `CryptoProvider` 实现(RFC 8998 国密 TLS 套件)
extern crate alloc;
use alloc::borrow::Cow;
use alloc::boxed::Box;
use alloc::sync::Arc;
use pki_types::PrivateKeyDer;
use rustls::crypto::{
CryptoProvider, GetRandomFailed, KeyProvider, SecureRandom, SigningKey, TicketProducer,
TicketerFactory,
};
use rustls::error::Error;
pub mod hash;
pub mod hmac;
pub mod kx;
pub mod sign;
pub mod tls13;
pub mod verify;
/// 构造国密 `CryptoProvider`
pub fn provider() -> CryptoProvider {
static TLS13_SUITES: &[&rustls::Tls13CipherSuite] =
&[tls13::TLS13_SM4_GCM_SM3, tls13::TLS13_SM4_CCM_SM3];
static KX_GROUPS: &[&dyn rustls::crypto::kx::SupportedKxGroup] = &[kx::CURVE_SM2];
CryptoProvider {
tls12_cipher_suites: Cow::Borrowed(&[]),
tls13_cipher_suites: Cow::Borrowed(TLS13_SUITES),
kx_groups: Cow::Borrowed(KX_GROUPS),
signature_verification_algorithms: verify::SUPPORTED_SM2_ALGS,
secure_random: &Random,
key_provider: &SmKeyProvider,
ticketer_factory: &SmTicketerFactory,
}
}
// ── SecureRandom ──────────────────────────────────────────────────────────────
#[derive(Debug)]
struct Random;
impl SecureRandom for Random {
fn fill(&self, buf: &mut [u8]) -> Result<(), GetRandomFailed> {
getrandom::getrandom(buf).map_err(|_| GetRandomFailed)
}
}
// ── KeyProvider ───────────────────────────────────────────────────────────────
#[derive(Debug)]
struct SmKeyProvider;
impl KeyProvider for SmKeyProvider {
fn load_private_key(
&self,
key_der: PrivateKeyDer<'static>,
) -> Result<Box<dyn SigningKey>, Error> {
sign::load_private_key(key_der)
}
}
// ── TicketerFactory ───────────────────────────────────────────────────────────
#[derive(Debug)]
struct SmTicketerFactory;
impl TicketerFactory for SmTicketerFactory {
fn ticketer(&self) -> Result<Arc<dyn TicketProducer>, Error> {
// Reason: TLS session ticket 加密暂不支持,返回错误;
// 后续可用 SM4-GCM 实现 ticket 加密
Err(Error::General("SM ticket factory not yet implemented".into()))
}
}
+80
View File
@@ -0,0 +1,80 @@
//! SM2 签名 → rustls `SigningKey` / `Signer`
use alloc::boxed::Box;
use alloc::vec::Vec;
use core::fmt;
use pki_types::{PrivateKeyDer, SubjectPublicKeyInfoDer};
use rustls::crypto::{SignatureScheme, Signer, SigningKey};
use rustls::error::Error;
use crate::sm2::{
der::{public_key_to_spki_der, sig_to_der},
sign_message, PrivateKey, DEFAULT_ID,
};
/// 从 DER 编码的私钥加载 SM2 签名密钥
pub(crate) fn load_private_key(
key_der: PrivateKeyDer<'static>,
) -> Result<Box<dyn SigningKey>, Error> {
let pri_key = match &key_der {
PrivateKeyDer::Sec1(sec1) => crate::sm2::der::private_key_from_sec1_der(sec1.secret_sec1_der())
.map_err(|_| Error::General("invalid SEC1 SM2 private key".into()))?,
PrivateKeyDer::Pkcs8(pkcs8) => crate::sm2::der::private_key_from_pkcs8_der(pkcs8.secret_pkcs8_der())
.map_err(|_| Error::General("invalid PKCS#8 SM2 private key".into()))?,
_ => return Err(Error::General("unsupported SM2 key format".into())),
};
Ok(Box::new(Sm2SigningKey { pri_key }))
}
struct Sm2SigningKey {
pri_key: PrivateKey,
}
impl fmt::Debug for Sm2SigningKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Sm2SigningKey").finish_non_exhaustive()
}
}
impl SigningKey for Sm2SigningKey {
fn choose_scheme(&self, offered: &[SignatureScheme]) -> Option<Box<dyn Signer>> {
if offered.contains(&SignatureScheme::SM2_SM3) {
Some(Box::new(Sm2Signer {
pri_key: self.pri_key.clone(),
}))
} else {
None
}
}
fn public_key(&self) -> Option<SubjectPublicKeyInfoDer<'_>> {
let pub_key_bytes = self.pri_key.public_key();
let spki = public_key_to_spki_der(&pub_key_bytes);
Some(SubjectPublicKeyInfoDer::from(spki))
}
}
struct Sm2Signer {
pri_key: PrivateKey,
}
impl fmt::Debug for Sm2Signer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Sm2Signer").finish_non_exhaustive()
}
}
impl Signer for Sm2Signer {
fn sign(self: Box<Self>, message: &[u8]) -> Result<Vec<u8>, Error> {
let mut rng = super::kx::Sm2Rng;
// GB/T 32918.2: 签名 = SM3(Z || M)Z 由 ID 和公钥派生
let sig_raw = sign_message(message, DEFAULT_ID, &self.pri_key, &mut rng);
// TLS 传输使用 DER 编码签名
Ok(sig_to_der(&sig_raw))
}
fn scheme(&self) -> SignatureScheme {
SignatureScheme::SM2_SM3
}
}
+270
View File
@@ -0,0 +1,270 @@
//! SM4-GCM/CCM AEAD + HKDF-SM3 → rustls TLS 1.3 密码套件
use alloc::boxed::Box;
use alloc::vec::Vec;
use rustls::crypto::cipher::{
make_tls13_aad, AeadKey, EncodedMessage, InboundOpaque, Iv, MessageDecrypter,
MessageEncrypter, Nonce, OutboundOpaque, OutboundPlain, Tls13AeadAlgorithm,
UnsupportedOperationError, NONCE_LEN,
};
use rustls::crypto::tls13::HkdfUsingHmac;
use rustls::crypto::CipherSuite;
use rustls::enums::{ContentType, ProtocolVersion};
use rustls::error::Error;
use rustls::version::TLS13_VERSION;
use rustls::{CipherSuiteCommon, ConnectionTrafficSecrets, Tls13CipherSuite};
use crate::sm4::{sm4_decrypt_ccm, sm4_decrypt_gcm, sm4_encrypt_ccm, sm4_encrypt_gcm};
// ── HKDF(零代码复用 rustls 内置 HkdfUsingHmac)──────────────────────────────
pub(crate) static HKDF_SM3: HkdfUsingHmac<'static> = HkdfUsingHmac(&super::hmac::HMAC_SM3);
// ── TLS 1.3 密码套件常量 ──────────────────────────────────────────────────────
/// TLS 1.3 SM4-GCM-SM3 密码套件(RFC 8998
pub static TLS13_SM4_GCM_SM3: &Tls13CipherSuite = &Tls13CipherSuite {
common: CipherSuiteCommon {
suite: CipherSuite::TLS13_SM4_GCM_SM3,
hash_provider: &super::hash::SM3,
confidentiality_limit: 1 << 24,
},
protocol_version: TLS13_VERSION,
hkdf_provider: &HKDF_SM3,
aead_alg: &Sm4GcmAead,
quic: None,
};
/// TLS 1.3 SM4-CCM-SM3 密码套件(RFC 8998
pub static TLS13_SM4_CCM_SM3: &Tls13CipherSuite = &Tls13CipherSuite {
common: CipherSuiteCommon {
suite: CipherSuite::TLS13_SM4_CCM_SM3,
hash_provider: &super::hash::SM3,
confidentiality_limit: 1 << 24,
},
protocol_version: TLS13_VERSION,
hkdf_provider: &HKDF_SM3,
aead_alg: &Sm4CcmAead,
quic: None,
};
// ── SM4-GCM ───────────────────────────────────────────────────────────────────
struct Sm4GcmAead;
impl Tls13AeadAlgorithm for Sm4GcmAead {
fn encrypter(&self, key: AeadKey, iv: Iv) -> Box<dyn MessageEncrypter> {
Box::new(Sm4GcmEncrypter {
key: aead_key_to_16(&key),
iv,
})
}
fn decrypter(&self, key: AeadKey, iv: Iv) -> Box<dyn MessageDecrypter> {
Box::new(Sm4GcmDecrypter {
key: aead_key_to_16(&key),
iv,
})
}
fn key_len(&self) -> usize {
16
}
fn extract_keys(
&self,
key: AeadKey,
iv: Iv,
) -> Result<ConnectionTrafficSecrets, UnsupportedOperationError> {
Ok(ConnectionTrafficSecrets::Sm4Gcm { key, iv })
}
}
struct Sm4GcmEncrypter {
key: [u8; 16],
iv: Iv,
}
impl MessageEncrypter for Sm4GcmEncrypter {
fn encrypt(
&mut self,
msg: EncodedMessage<OutboundPlain<'_>>,
seq: u64,
) -> Result<EncodedMessage<OutboundOpaque>, Error> {
let total_len = self.encrypted_payload_len(msg.payload.len());
let nonce = Nonce::new(&self.iv, seq).to_array::<NONCE_LEN>()?;
let aad = make_tls13_aad(total_len);
// 收集明文 + ContentTypeTLS 1.3 inner plaintext 格式)
let mut plaintext: Vec<u8> = Vec::with_capacity(msg.payload.len() + 1);
{
let mut tmp = OutboundOpaque::with_capacity(msg.payload.len() + 1);
tmp.extend_from_chunks(&msg.payload);
tmp.extend_from_slice(&msg.typ.to_array());
plaintext.extend_from_slice(tmp.as_ref());
}
let (ciphertext, tag) = sm4_encrypt_gcm(&self.key, &nonce, &aad, &plaintext);
let mut out = OutboundOpaque::with_capacity(ciphertext.len() + 16);
out.extend_from_slice(&ciphertext);
out.extend_from_slice(&tag);
Ok(EncodedMessage {
typ: ContentType::ApplicationData,
version: ProtocolVersion::TLSv1_2,
payload: out,
})
}
fn encrypted_payload_len(&self, payload_len: usize) -> usize {
payload_len + 1 + 16 // +1 ContentType byte + 16 GCM tag
}
}
struct Sm4GcmDecrypter {
key: [u8; 16],
iv: Iv,
}
impl MessageDecrypter for Sm4GcmDecrypter {
fn decrypt<'a>(
&mut self,
mut msg: EncodedMessage<InboundOpaque<'a>>,
seq: u64,
) -> Result<EncodedMessage<&'a [u8]>, Error> {
let payload = &mut msg.payload;
if payload.len() < 16 {
return Err(Error::DecryptError);
}
let nonce = Nonce::new(&self.iv, seq).to_array::<NONCE_LEN>()?;
// Reason: AAD 使用解密前(含 tag)的完整 payload 长度
let aad = make_tls13_aad(payload.len());
let ct_len = payload.len() - 16;
let tag: [u8; 16] = payload[ct_len..].try_into().map_err(|_| Error::DecryptError)?;
let plaintext =
sm4_decrypt_gcm(&self.key, &nonce, &aad, &payload[..ct_len], &tag)
.map_err(|_| Error::DecryptError)?;
// 将明文写回 payloadin-place),然后截断
let plain_len = plaintext.len();
payload[..plain_len].copy_from_slice(&plaintext);
payload.truncate(plain_len);
msg.into_tls13_unpadded_message()
}
}
// ── SM4-CCM ───────────────────────────────────────────────────────────────────
struct Sm4CcmAead;
impl Tls13AeadAlgorithm for Sm4CcmAead {
fn encrypter(&self, key: AeadKey, iv: Iv) -> Box<dyn MessageEncrypter> {
Box::new(Sm4CcmEncrypter {
key: aead_key_to_16(&key),
iv,
})
}
fn decrypter(&self, key: AeadKey, iv: Iv) -> Box<dyn MessageDecrypter> {
Box::new(Sm4CcmDecrypter {
key: aead_key_to_16(&key),
iv,
})
}
fn key_len(&self) -> usize {
16
}
fn extract_keys(
&self,
key: AeadKey,
iv: Iv,
) -> Result<ConnectionTrafficSecrets, UnsupportedOperationError> {
Ok(ConnectionTrafficSecrets::Sm4Ccm { key, iv })
}
}
struct Sm4CcmEncrypter {
key: [u8; 16],
iv: Iv,
}
impl MessageEncrypter for Sm4CcmEncrypter {
fn encrypt(
&mut self,
msg: EncodedMessage<OutboundPlain<'_>>,
seq: u64,
) -> Result<EncodedMessage<OutboundOpaque>, Error> {
let total_len = self.encrypted_payload_len(msg.payload.len());
let nonce = Nonce::new(&self.iv, seq).to_array::<NONCE_LEN>()?;
let aad = make_tls13_aad(total_len);
let mut plaintext: Vec<u8> = Vec::with_capacity(msg.payload.len() + 1);
{
let mut tmp = OutboundOpaque::with_capacity(msg.payload.len() + 1);
tmp.extend_from_chunks(&msg.payload);
tmp.extend_from_slice(&msg.typ.to_array());
plaintext.extend_from_slice(tmp.as_ref());
}
// sm4_encrypt_ccm 返回 ciphertext+tag 合并的 Vec
let combined = sm4_encrypt_ccm(&self.key, &nonce, &aad, &plaintext, 16)
.map_err(|_| Error::EncryptError)?;
let mut out = OutboundOpaque::with_capacity(combined.len());
out.extend_from_slice(&combined);
Ok(EncodedMessage {
typ: ContentType::ApplicationData,
version: ProtocolVersion::TLSv1_2,
payload: out,
})
}
fn encrypted_payload_len(&self, payload_len: usize) -> usize {
payload_len + 1 + 16
}
}
struct Sm4CcmDecrypter {
key: [u8; 16],
iv: Iv,
}
impl MessageDecrypter for Sm4CcmDecrypter {
fn decrypt<'a>(
&mut self,
mut msg: EncodedMessage<InboundOpaque<'a>>,
seq: u64,
) -> Result<EncodedMessage<&'a [u8]>, Error> {
let payload = &mut msg.payload;
if payload.len() < 16 {
return Err(Error::DecryptError);
}
let nonce = Nonce::new(&self.iv, seq).to_array::<NONCE_LEN>()?;
let aad = make_tls13_aad(payload.len());
// sm4_decrypt_ccm 接收 ciphertext_with_tag(末尾 tag_len 字节为 tag
let plaintext = sm4_decrypt_ccm(&self.key, &nonce, &aad, &payload[..], 16)
.map_err(|_| Error::DecryptError)?;
let plain_len = plaintext.len();
payload[..plain_len].copy_from_slice(&plaintext);
payload.truncate(plain_len);
msg.into_tls13_unpadded_message()
}
}
// ── 工具函数 ──────────────────────────────────────────────────────────────────
fn aead_key_to_16(key: &AeadKey) -> [u8; 16] {
let mut out = [0u8; 16];
out.copy_from_slice(&key.as_ref()[..16]);
out
}
+63
View File
@@ -0,0 +1,63 @@
//! SM2 验签 → rustls `SignatureVerificationAlgorithm`
use pki_types::{AlgorithmIdentifier, SignatureVerificationAlgorithm};
use rustls::crypto::{WebPkiSupportedAlgorithms, SignatureScheme};
use crate::sm2::{der::sig_from_der, verify_message, DEFAULT_ID};
/// rustls 支持的 SM2_SM3 签名验证算法集合
pub static SUPPORTED_SM2_ALGS: WebPkiSupportedAlgorithms = WebPkiSupportedAlgorithms {
all: &[&SM2_SM3_ALG],
mapping: &[(SignatureScheme::SM2_SM3, &[&SM2_SM3_ALG])],
};
static SM2_SM3_ALG: Sm2Sm3Algorithm = Sm2Sm3Algorithm;
#[derive(Debug)]
struct Sm2Sm3Algorithm;
// SM2 OID: 1.2.156.10197.1.301 (encoded as DER OID bytes)
// SM2withSM3 SignatureAlgorithm OID: 1.2.156.10197.1.501
// AlgorithmIdentifier for SM2withSM3
const SM2SM3_OID: &[u8] = &[
0x30, 0x0a, // SEQUENCE
0x06, 0x08, // OID
0x2a, 0x81, 0x1c, 0xcf, 0x55, 0x01, 0x83, 0x75, // 1.2.156.10197.1.501
];
impl SignatureVerificationAlgorithm for Sm2Sm3Algorithm {
fn public_key_alg_id(&self) -> AlgorithmIdentifier {
// id-ecPublicKey with SM2 curve parameter
AlgorithmIdentifier::from_slice(&[
0x30, 0x13, // SEQUENCE
0x06, 0x07, // OID id-ecPublicKey
0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01,
0x06, 0x08, // OID SM2
0x2a, 0x81, 0x1c, 0xcf, 0x55, 0x01, 0x82, 0x2d,
])
}
fn signature_alg_id(&self) -> AlgorithmIdentifier {
AlgorithmIdentifier::from_slice(SM2SM3_OID)
}
fn verify_signature(
&self,
public_key: &[u8],
message: &[u8],
signature: &[u8],
) -> Result<(), pki_types::InvalidSignature> {
// 将 65 字节公钥转换为固定数组
let pub_key_arr: &[u8; 65] = public_key
.try_into()
.map_err(|_| pki_types::InvalidSignature)?;
// DER 解码签名
let sig_raw =
sig_from_der(signature).map_err(|_| pki_types::InvalidSignature)?;
// SM2 验签(使用默认 IDGB/T 32918.2
verify_message(message, DEFAULT_ID, pub_key_arr, &sig_raw)
.map_err(|_| pki_types::InvalidSignature)
}
}
+95
View File
@@ -14,6 +14,18 @@
//! - **SEC1**RFC 5915):`ECPrivateKey SEQUENCE { version INTEGER(1), privateKey OCTET STRING, ... }`
//! - **PKCS#8**RFC 5958):`PrivateKeyInfo SEQUENCE { version INTEGER(0), algorithm, privateKey OCTET STRING(SEC1) }`
//!
//! ## 公钥 SPKI 格式
//! rustls `SigningKey::public_key()` 需要 `SubjectPublicKeyInfoDer`
//! ```text
//! SEQUENCE {
//! SEQUENCE {
//! OID id-ecPublicKey (1.2.840.10045.2.1)
//! OID SM2 (1.2.156.10197.1.301)
//! }
//! BIT STRING (04 || x(32B) || y(32B))
//! }
//! ```
//!
//! ## DER INTEGER 编码规则
//! - 去除前导零(但若最高位为 1,需在前补 0x00 防止被解析为负数)
//! - tag = 0x02length 占 1 字节(r/s < 256 位时长度 ≤ 33
@@ -259,6 +271,56 @@ pub fn private_key_from_pkcs8_der(der: &[u8]) -> Result<PrivateKey, Error> {
private_key_from_sec1_der(sec1_der)
}
// ── SM2 公钥 SPKI DER 编码 ────────────────────────────────────────────────────
/// 将 SM2 公钥(65 字节,04||x||y)编码为 SubjectPublicKeyInfo DER
///
/// 格式(RFC 5480):
/// ```text
/// SEQUENCE {
/// SEQUENCE {
/// OID 1.2.840.10045.2.1 (id-ecPublicKey, 7 字节)
/// OID 1.2.156.10197.1.301 (SM2, 8 字节)
/// }
/// BIT STRING 0x00 || pub_key (65 字节 + 1 字节前缀)
/// }
/// ```
///
/// 此格式是 rustls `SigningKey::public_key()` 所需的 `SubjectPublicKeyInfoDer`。
#[cfg(feature = "alloc")]
pub fn public_key_to_spki_der(pub_key: &[u8; 65]) -> Vec<u8> {
// OID 1.2.840.10045.2.1 (id-ecPublicKey): 06 07 2a 86 48 ce 3d 02 01
let oid_ec: &[u8] = &[0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01];
// OID 1.2.156.10197.1.301 (SM2): 06 08 2a 81 1c cf 55 01 82 2d
let oid_sm2: &[u8] = &[0x06, 0x08, 0x2a, 0x81, 0x1c, 0xcf, 0x55, 0x01, 0x82, 0x2d];
// AlgorithmIdentifier SEQUENCE
let alg_inner_len = oid_ec.len() + oid_sm2.len();
let mut alg = Vec::with_capacity(2 + alg_inner_len);
alg.push(0x30);
alg.push(alg_inner_len as u8);
alg.extend_from_slice(oid_ec);
alg.extend_from_slice(oid_sm2);
// BIT STRING: 0x03 <len> 0x00 <pub_key>
// Reason: 0x00 是 unused bits 字段,表示最后一字节无填充位
let bit_str_len = 1 + pub_key.len(); // 0x00 前缀 + 65 字节公钥
let mut bit_str = Vec::with_capacity(2 + bit_str_len);
bit_str.push(0x03);
bit_str.push(bit_str_len as u8);
bit_str.push(0x00); // unused bits = 0
bit_str.extend_from_slice(pub_key);
// 外层 SEQUENCE
let outer_len = alg.len() + bit_str.len();
let mut der = Vec::with_capacity(2 + outer_len);
der.push(0x30);
der.push(outer_len as u8);
der.extend_from_slice(&alg);
der.extend_from_slice(&bit_str);
der
}
#[cfg(test)]
mod tests {
use super::*;
@@ -435,4 +497,37 @@ mod tests {
der[0] = 0x04; // 破坏外层 SEQUENCE tag
assert!(private_key_from_pkcs8_der(&der).is_err());
}
// ── SPKI DER 测试 ──────────────────────────────────────────────────────────
#[cfg(feature = "alloc")]
#[test]
fn test_spki_der_structure() {
use crate::sm2::PrivateKey;
let pri = PrivateKey::from_bytes(&RAW_KEY).unwrap();
let pub_key = pri.public_key();
let spki = public_key_to_spki_der(&pub_key);
// 外层 SEQUENCE
assert_eq!(spki[0], 0x30, "外层 tag 应为 SEQUENCE");
// BIT STRING 内包含 04||x||y65字节)
// 确认公钥原始字节出现在 SPKI 中
let pos = spki.windows(65).position(|w| w == pub_key);
assert!(pos.is_some(), "SPKI 应包含原始公钥字节");
}
#[cfg(feature = "alloc")]
#[test]
fn test_spki_der_oid_ec() {
use crate::sm2::PrivateKey;
let pri = PrivateKey::from_bytes(&RAW_KEY).unwrap();
let pub_key = pri.public_key();
let spki = public_key_to_spki_der(&pub_key);
// id-ecPublicKey OID bytes
let oid_ec: &[u8] = &[0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01];
assert!(
spki.windows(oid_ec.len()).any(|w| w == oid_ec),
"SPKI 应包含 id-ecPublicKey OID"
);
}
}
+5 -3
View File
@@ -32,6 +32,11 @@ use crate::sm2::field::{
};
use crate::sm3::Sm3Hasher;
/// SM2 默认用户可辨别标识(GB/T 32918.2-2016 §A.2 示例值)
///
/// 当调用方无自定义 ID 时,应使用此常量作为 `sign_message` / `verify_message` 的 `id` 参数。
pub const DEFAULT_ID: &[u8] = b"1234567812345678";
// ── 私钥类型 ──────────────────────────────────────────────────────────────────
/// SM2 私钥(32 字节,离开作用域自动清零)
@@ -425,9 +430,6 @@ pub fn decrypt(pri_key: &PrivateKey, ciphertext: &[u8]) -> Result<Vec<u8>, Error
mod tests {
use super::*;
/// 默认用户 ID(GB/T 规范示例中常用的标准 ID)
const DEFAULT_ID: &[u8] = b"1234567812345678";
struct FakeRng([u8; 32]);
impl RngCore for FakeRng {
fn next_u32(&mut self) -> u32 {
+92
View File
@@ -219,8 +219,80 @@ pub fn hmac_sm3(key: &[u8], data: &[u8]) -> [u8; DIGEST_LEN] {
result
}
/// 流式 HMAC-SM3
///
/// 与 [`hmac_sm3`] 功能相同,但支持多次 [`update`](HmacSm3::update) 调用,
/// 适用于 rustls `hmac::Key::sign_concat` 等多切片场景。
///
/// # 安全性
/// `opad_key` 含派生自密钥的材料,结构体析构时由 `Zeroize` 自动清零。
#[derive(Clone)]
pub struct HmacSm3 {
/// 正在计算 inner hash(已喂入 ipad 前缀)
inner: Sm3Hasher,
/// 预计算的 opad XOR key64 字节)
opad_key: [u8; 64],
}
impl HmacSm3 {
/// 以给定密钥初始化 HMAC-SM3
pub fn new(key: &[u8]) -> Self {
use zeroize::Zeroize;
let mut k_pad = [0u8; 64];
if key.len() > 64 {
let h = Sm3Hasher::digest(key);
k_pad[..32].copy_from_slice(&h);
} else {
k_pad[..key.len()].copy_from_slice(key);
}
let mut ipad_key = [0u8; 64];
let mut opad_key = [0u8; 64];
for i in 0..64 {
ipad_key[i] = k_pad[i] ^ 0x36;
opad_key[i] = k_pad[i] ^ 0x5C;
}
k_pad.zeroize();
// Reason: 预喂 ipad 前缀,后续 update 只需追加消息数据
let mut inner = Sm3Hasher::new();
inner.update(&ipad_key);
ipad_key.zeroize();
Self { inner, opad_key }
}
/// 追加消息数据
pub fn update(&mut self, data: &[u8]) {
self.inner.update(data);
}
/// 完成计算,返回 32 字节 HMAC 值
pub fn finalize(self) -> [u8; DIGEST_LEN] {
use zeroize::Zeroize;
let inner_hash = self.inner.finalize();
let mut opad_key = self.opad_key;
let mut outer = Sm3Hasher::new();
outer.update(&opad_key);
outer.update(&inner_hash);
let result = outer.finalize();
opad_key.zeroize();
result
}
}
impl zeroize::Zeroize for HmacSm3 {
fn zeroize(&mut self) {
self.opad_key.zeroize();
// inner 的 Sm3Hasher 不含密钥材料,无需特殊清零
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "alloc")]
extern crate alloc;
use super::*;
/// GB/T 32905-2016 附录 A 示例 1SM3("abc")
@@ -321,6 +393,26 @@ mod tests {
assert_eq!(d1, d2);
}
/// HmacSm3 流式接口与 hmac_sm3 单次接口结果一致
#[test]
fn test_hmac_sm3_streaming_equals_oneshot() {
let key = b"streaming-key";
let parts: &[&[u8]] = &[b"hello", b" ", b"world"];
let mut all = alloc::vec![];
for p in parts {
all.extend_from_slice(p);
}
let expected = hmac_sm3(key, &all);
let mut h = HmacSm3::new(key);
for p in parts {
h.update(p);
}
let got = h.finalize();
assert_eq!(expected, got);
}
// 辅助:从十六进制字符串构造 [u8; 32]
fn hex_literal(s: &str) -> [u8; 32] {
let mut out = [0u8; 32];