From 7c159343f8370a3d1ec7148bc111cd9ee624c351 Mon Sep 17 00:00:00 2001 From: huangxt Date: Mon, 9 Mar 2026 10:13:09 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8F=91=E5=B8=83=20v0.3.0=EF=BC=9Arustls=20Cr?= =?UTF-8?q?yptoProvider=20=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增功能: - 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 更新版本支持表 --- CHANGELOG.md | 26 ++++ CHANGELOG.zh-CN.md | 26 ++++ Cargo.lock | 24 ++- Cargo.toml | 7 +- README.md | 4 +- README.zh-CN.md | 4 +- SECURITY.md | 1 + SECURITY.zh-CN.md | 1 + src/lib.rs | 3 + src/rustls_provider/hash.rs | 51 +++++++ src/rustls_provider/hmac.rs | 45 ++++++ src/rustls_provider/kx.rs | 83 +++++++++++ src/rustls_provider/mod.rs | 76 ++++++++++ src/rustls_provider/sign.rs | 80 ++++++++++ src/rustls_provider/tls13.rs | 270 ++++++++++++++++++++++++++++++++++ src/rustls_provider/verify.rs | 63 ++++++++ src/sm2/der.rs | 95 ++++++++++++ src/sm2/mod.rs | 8 +- src/sm3/mod.rs | 92 ++++++++++++ 19 files changed, 950 insertions(+), 9 deletions(-) create mode 100644 src/rustls_provider/hash.rs create mode 100644 src/rustls_provider/hmac.rs create mode 100644 src/rustls_provider/kx.rs create mode 100644 src/rustls_provider/mod.rs create mode 100644 src/rustls_provider/sign.rs create mode 100644 src/rustls_provider/tls13.rs create mode 100644 src/rustls_provider/verify.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index e910192..97af202 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/CHANGELOG.zh-CN.md b/CHANGELOG.zh-CN.md index 39ac6f4..e289c83 100644 --- a/CHANGELOG.zh-CN.md +++ b/CHANGELOG.zh-CN.md @@ -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 diff --git a/Cargo.lock b/Cargo.lock index 079fa37..4af12e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index fd48767..047c372 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/README.md b/README.md index 04faf29..b059c1d 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/README.zh-CN.md b/README.zh-CN.md index 1027cdf..d2c67da 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -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)。 diff --git a/SECURITY.md b/SECURITY.md index a0d2b9a..6602216 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -4,6 +4,7 @@ | Version | Supported | |---------|-----------| +| 0.3.x | Yes | | 0.2.x | Yes | | 0.1.x | Yes | | < 0.1 | No | diff --git a/SECURITY.zh-CN.md b/SECURITY.zh-CN.md index 1de5f40..055692c 100644 --- a/SECURITY.zh-CN.md +++ b/SECURITY.zh-CN.md @@ -4,6 +4,7 @@ | 版本 | 是否支持 | |------|----------| +| 0.3.x | 是 | | 0.2.x | 是 | | 0.1.x | 是 | | < 0.1 | 否 | diff --git a/src/lib.rs b/src/lib.rs index ba9a368..c7eb490 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,3 +61,6 @@ pub mod sm9; pub mod bls; pub mod fpe; + +#[cfg(feature = "rustls-provider")] +pub mod rustls_provider; diff --git a/src/rustls_provider/hash.rs b/src/rustls_provider/hash.rs new file mode 100644 index 0000000..5be7fb7 --- /dev/null +++ b/src/rustls_provider/hash.rs @@ -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 { + 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 { + Box::new(Sm3Context(self.0.clone())) + } + + fn finish(self: Box) -> crypto::hash::Output { + crypto::hash::Output::new(&self.0.finalize()) + } + + fn update(&mut self, data: &[u8]) { + self.0.update(data); + } +} diff --git a/src/rustls_provider/hmac.rs b/src/rustls_provider/hmac.rs new file mode 100644 index 0000000..4755578 --- /dev/null +++ b/src/rustls_provider/hmac.rs @@ -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 { + Box::new(Sm3HmacKey { key: key.to_vec() }) + } + + fn hash_output_len(&self) -> usize { + 32 + } +} + +struct Sm3HmacKey { + key: Vec, +} + +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 + } +} diff --git a/src/rustls_provider/kx.rs b/src/rustls_provider/kx.rs new file mode 100644 index 0000000..fbc1a0c --- /dev/null +++ b/src/rustls_provider/kx.rs @@ -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 { + // 使用 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, +} + +impl ActiveKeyExchange for Sm2KeyExchange { + fn complete(self: Box, peer_pub_key: &[u8]) -> Result { + // 标准 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())) + }) + } +} diff --git a/src/rustls_provider/mod.rs b/src/rustls_provider/mod.rs new file mode 100644 index 0000000..90499b2 --- /dev/null +++ b/src/rustls_provider/mod.rs @@ -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, Error> { + sign::load_private_key(key_der) + } +} + +// ── TicketerFactory ─────────────────────────────────────────────────────────── + +#[derive(Debug)] +struct SmTicketerFactory; + +impl TicketerFactory for SmTicketerFactory { + fn ticketer(&self) -> Result, Error> { + // Reason: TLS session ticket 加密暂不支持,返回错误; + // 后续可用 SM4-GCM 实现 ticket 加密 + Err(Error::General("SM ticket factory not yet implemented".into())) + } +} diff --git a/src/rustls_provider/sign.rs b/src/rustls_provider/sign.rs new file mode 100644 index 0000000..b243705 --- /dev/null +++ b/src/rustls_provider/sign.rs @@ -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, 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> { + if offered.contains(&SignatureScheme::SM2_SM3) { + Some(Box::new(Sm2Signer { + pri_key: self.pri_key.clone(), + })) + } else { + None + } + } + + fn public_key(&self) -> Option> { + 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, message: &[u8]) -> Result, 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 + } +} diff --git a/src/rustls_provider/tls13.rs b/src/rustls_provider/tls13.rs new file mode 100644 index 0000000..ac89350 --- /dev/null +++ b/src/rustls_provider/tls13.rs @@ -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 { + Box::new(Sm4GcmEncrypter { + key: aead_key_to_16(&key), + iv, + }) + } + + fn decrypter(&self, key: AeadKey, iv: Iv) -> Box { + 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 { + Ok(ConnectionTrafficSecrets::Sm4Gcm { key, iv }) + } +} + +struct Sm4GcmEncrypter { + key: [u8; 16], + iv: Iv, +} + +impl MessageEncrypter for Sm4GcmEncrypter { + fn encrypt( + &mut self, + msg: EncodedMessage>, + seq: u64, + ) -> Result, Error> { + let total_len = self.encrypted_payload_len(msg.payload.len()); + let nonce = Nonce::new(&self.iv, seq).to_array::()?; + let aad = make_tls13_aad(total_len); + + // 收集明文 + ContentType(TLS 1.3 inner plaintext 格式) + let mut plaintext: Vec = 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>, + seq: u64, + ) -> Result, Error> { + let payload = &mut msg.payload; + if payload.len() < 16 { + return Err(Error::DecryptError); + } + + let nonce = Nonce::new(&self.iv, seq).to_array::()?; + // 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 { + Box::new(Sm4CcmEncrypter { + key: aead_key_to_16(&key), + iv, + }) + } + + fn decrypter(&self, key: AeadKey, iv: Iv) -> Box { + 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 { + Ok(ConnectionTrafficSecrets::Sm4Ccm { key, iv }) + } +} + +struct Sm4CcmEncrypter { + key: [u8; 16], + iv: Iv, +} + +impl MessageEncrypter for Sm4CcmEncrypter { + fn encrypt( + &mut self, + msg: EncodedMessage>, + seq: u64, + ) -> Result, Error> { + let total_len = self.encrypted_payload_len(msg.payload.len()); + let nonce = Nonce::new(&self.iv, seq).to_array::()?; + let aad = make_tls13_aad(total_len); + + let mut plaintext: Vec = 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>, + seq: u64, + ) -> Result, Error> { + let payload = &mut msg.payload; + if payload.len() < 16 { + return Err(Error::DecryptError); + } + + let nonce = Nonce::new(&self.iv, seq).to_array::()?; + 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 +} diff --git a/src/rustls_provider/verify.rs b/src/rustls_provider/verify.rs new file mode 100644 index 0000000..f4571c1 --- /dev/null +++ b/src/rustls_provider/verify.rs @@ -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) + } +} diff --git a/src/sm2/der.rs b/src/sm2/der.rs index b80f1ee..f81d592 100644 --- a/src/sm2/der.rs +++ b/src/sm2/der.rs @@ -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 { 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 { + // 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 0x00 + // 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" + ); + } } diff --git a/src/sm2/mod.rs b/src/sm2/mod.rs index a51f7bd..bca2a89 100644 --- a/src/sm2/mod.rs +++ b/src/sm2/mod.rs @@ -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, 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 { diff --git a/src/sm3/mod.rs b/src/sm3/mod.rs index 198a284..fe99df9 100644 --- a/src/sm3/mod.rs +++ b/src/sm3/mod.rs @@ -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];