发布 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/),
|
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).
|
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
|
## [0.2.1] - 2025-03-08
|
||||||
|
|
||||||
### Added
|
### 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
|
- 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
|
- 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.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.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
|
[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/),
|
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),
|
||||||
本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
|
本项目遵循 [语义化版本](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
|
## [0.2.1] - 2025-03-08
|
||||||
|
|
||||||
### 新增
|
### 新增
|
||||||
@@ -118,6 +143,7 @@
|
|||||||
- XTS:拒绝非 16 字节对齐输入,而非静默截断
|
- XTS:拒绝非 16 字节对齐输入,而非静默截断
|
||||||
- SM9 `hash_to_range`:用常量时间条件选择替换可变迭代 `while` 循环
|
- 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.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.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
|
[0.1.1]: https://github.com/kintaiW/libsmx/releases/tag/v0.1.1
|
||||||
|
|||||||
Generated
+23
-1
@@ -422,13 +422,16 @@ checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libsmx"
|
name = "libsmx"
|
||||||
version = "0.2.1"
|
version = "0.3.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"criterion",
|
"criterion",
|
||||||
"crypto-bigint",
|
"crypto-bigint",
|
||||||
|
"getrandom",
|
||||||
"hex",
|
"hex",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"rand_core 0.6.4",
|
"rand_core 0.6.4",
|
||||||
|
"rustls",
|
||||||
|
"rustls-pki-types",
|
||||||
"sm9_core",
|
"sm9_core",
|
||||||
"subtle",
|
"subtle",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
@@ -635,6 +638,25 @@ version = "0.8.10"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
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]]
|
[[package]]
|
||||||
name = "rustversion"
|
name = "rustversion"
|
||||||
version = "1.0.22"
|
version = "1.0.22"
|
||||||
|
|||||||
+6
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "libsmx"
|
name = "libsmx"
|
||||||
version = "0.2.1"
|
version = "0.3.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.83.0"
|
rust-version = "1.83.0"
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
@@ -35,11 +35,16 @@ crypto-bigint = { version = "0.6", default-features = false }
|
|||||||
subtle = { version = "2.6", default-features = false }
|
subtle = { version = "2.6", default-features = false }
|
||||||
zeroize = { version = "1.8", default-features = false, features = ["derive"] }
|
zeroize = { version = "1.8", default-features = false, features = ["derive"] }
|
||||||
rand_core = { version = "0.6", default-features = false }
|
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]
|
[features]
|
||||||
default = ["alloc"]
|
default = ["alloc"]
|
||||||
alloc = []
|
alloc = []
|
||||||
std = ["alloc", "rand_core/std"]
|
std = ["alloc", "rand_core/std"]
|
||||||
|
# rustls CryptoProvider 集成(RFC 8998 国密 TLS 套件)
|
||||||
|
rustls-provider = ["alloc", "dep:rustls", "dep:pki-types", "dep:getrandom"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ Add to `Cargo.toml`:
|
|||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libsmx = "0.2"
|
libsmx = "0.3"
|
||||||
```
|
```
|
||||||
|
|
||||||
### SM3 Hash
|
### SM3 Hash
|
||||||
@@ -156,7 +156,7 @@ For `no_std` without `alloc`:
|
|||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libsmx = { version = "0.2", default-features = false }
|
libsmx = { version = "0.3", default-features = false }
|
||||||
```
|
```
|
||||||
|
|
||||||
## Benchmarks
|
## Benchmarks
|
||||||
|
|||||||
+2
-2
@@ -33,7 +33,7 @@
|
|||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libsmx = "0.2"
|
libsmx = "0.3"
|
||||||
```
|
```
|
||||||
|
|
||||||
### SM3 哈希
|
### SM3 哈希
|
||||||
@@ -188,7 +188,7 @@ assert_eq!(decrypted, plaintext);
|
|||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libsmx = { version = "0.2", default-features = false }
|
libsmx = { version = "0.3", default-features = false }
|
||||||
```
|
```
|
||||||
|
|
||||||
无 `alloc` 时,SM3 哈希、SM3 HMAC、SM2 签名/验签、SM4 ECB 仍可用(固定大小数组 API)。
|
无 `alloc` 时,SM3 哈希、SM3 HMAC、SM2 签名/验签、SM4 ECB 仍可用(固定大小数组 API)。
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
| Version | Supported |
|
| Version | Supported |
|
||||||
|---------|-----------|
|
|---------|-----------|
|
||||||
|
| 0.3.x | Yes |
|
||||||
| 0.2.x | Yes |
|
| 0.2.x | Yes |
|
||||||
| 0.1.x | Yes |
|
| 0.1.x | Yes |
|
||||||
| < 0.1 | No |
|
| < 0.1 | No |
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
| 版本 | 是否支持 |
|
| 版本 | 是否支持 |
|
||||||
|------|----------|
|
|------|----------|
|
||||||
|
| 0.3.x | 是 |
|
||||||
| 0.2.x | 是 |
|
| 0.2.x | 是 |
|
||||||
| 0.1.x | 是 |
|
| 0.1.x | 是 |
|
||||||
| < 0.1 | 否 |
|
| < 0.1 | 否 |
|
||||||
|
|||||||
@@ -61,3 +61,6 @@ pub mod sm9;
|
|||||||
pub mod bls;
|
pub mod bls;
|
||||||
|
|
||||||
pub mod fpe;
|
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, ... }`
|
//! - **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) }`
|
//! - **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 编码规则
|
//! ## DER INTEGER 编码规则
|
||||||
//! - 去除前导零(但若最高位为 1,需在前补 0x00 防止被解析为负数)
|
//! - 去除前导零(但若最高位为 1,需在前补 0x00 防止被解析为负数)
|
||||||
//! - tag = 0x02,length 占 1 字节(r/s < 256 位时长度 ≤ 33)
|
//! - 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)
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -435,4 +497,37 @@ mod tests {
|
|||||||
der[0] = 0x04; // 破坏外层 SEQUENCE tag
|
der[0] = 0x04; // 破坏外层 SEQUENCE tag
|
||||||
assert!(private_key_from_pkcs8_der(&der).is_err());
|
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;
|
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 字节,离开作用域自动清零)
|
/// SM2 私钥(32 字节,离开作用域自动清零)
|
||||||
@@ -425,9 +430,6 @@ pub fn decrypt(pri_key: &PrivateKey, ciphertext: &[u8]) -> Result<Vec<u8>, Error
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// 默认用户 ID(GB/T 规范示例中常用的标准 ID)
|
|
||||||
const DEFAULT_ID: &[u8] = b"1234567812345678";
|
|
||||||
|
|
||||||
struct FakeRng([u8; 32]);
|
struct FakeRng([u8; 32]);
|
||||||
impl RngCore for FakeRng {
|
impl RngCore for FakeRng {
|
||||||
fn next_u32(&mut self) -> u32 {
|
fn next_u32(&mut self) -> u32 {
|
||||||
|
|||||||
@@ -219,8 +219,80 @@ pub fn hmac_sm3(key: &[u8], data: &[u8]) -> [u8; DIGEST_LEN] {
|
|||||||
result
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
extern crate alloc;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// GB/T 32905-2016 附录 A 示例 1:SM3("abc")
|
/// GB/T 32905-2016 附录 A 示例 1:SM3("abc")
|
||||||
@@ -321,6 +393,26 @@ mod tests {
|
|||||||
assert_eq!(d1, d2);
|
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]
|
// 辅助:从十六进制字符串构造 [u8; 32]
|
||||||
fn hex_literal(s: &str) -> [u8; 32] {
|
fn hex_literal(s: &str) -> [u8; 32] {
|
||||||
let mut out = [0u8; 32];
|
let mut out = [0u8; 32];
|
||||||
|
|||||||
Reference in New Issue
Block a user