发布 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:
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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"
|
||||
|
||||
@@ -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
@@ -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)。
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
| Version | Supported |
|
||||
|---------|-----------|
|
||||
| 0.3.x | Yes |
|
||||
| 0.2.x | Yes |
|
||||
| 0.1.x | Yes |
|
||||
| < 0.1 | No |
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
| 版本 | 是否支持 |
|
||||
|------|----------|
|
||||
| 0.3.x | 是 |
|
||||
| 0.2.x | 是 |
|
||||
| 0.1.x | 是 |
|
||||
| < 0.1 | 否 |
|
||||
|
||||
@@ -61,3 +61,6 @@ pub mod sm9;
|
||||
pub mod bls;
|
||||
|
||||
pub mod fpe;
|
||||
|
||||
#[cfg(feature = "rustls-provider")]
|
||||
pub mod rustls_provider;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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()))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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()))
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
// 收集明文 + ContentType(TLS 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)?;
|
||||
|
||||
// 将明文写回 payload(in-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
|
||||
}
|
||||
@@ -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 验签(使用默认 ID,GB/T 32918.2)
|
||||
verify_message(message, DEFAULT_ID, pub_key_arr, &sig_raw)
|
||||
.map_err(|_| pki_types::InvalidSignature)
|
||||
}
|
||||
}
|
||||
@@ -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 = 0x02,length 占 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||y(65字节)
|
||||
// 确认公钥原始字节出现在 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
@@ -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 {
|
||||
|
||||
@@ -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 key(64 字节)
|
||||
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 示例 1:SM3("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];
|
||||
|
||||
Reference in New Issue
Block a user