Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 21e7e65b32 | |||
| 7c159343f8 |
+31
-3
@@ -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
|
||||
@@ -27,6 +52,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- **SM4 AEAD combined format**
|
||||
- `sm4_encrypt_gcm_combined` / `sm4_decrypt_gcm_combined`: TLS format (ciphertext||tag)
|
||||
- `sm4_encrypt_ccm_combined` / `sm4_decrypt_ccm_combined`: Same format for CCM
|
||||
|
||||
## [0.2.0] - 2025-03-08
|
||||
|
||||
### Added
|
||||
|
||||
- **BLS signatures** (`bls` module, requires `alloc` feature)
|
||||
- `bls_keygen` / `bls_sign` / `bls_verify`: minimal-signature-size variant (sig ∈ G1, pk ∈ G2)
|
||||
- `bls_aggregate` / `bls_aggregate_verify`: multi-message aggregate signatures
|
||||
@@ -67,9 +97,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
- MSRV raised to 1.83.0 (required by crypto-bigint 0.6.x for ConstMontyForm constant-time Montgomery arithmetic)
|
||||
- Use Rust 1.83+ built-in `div_ceil` method instead of manual implementation
|
||||
|
||||
### CI
|
||||
|
||||
- Optimized sanity_check.sh to skip test code, avoiding false positives
|
||||
|
||||
## [0.1.0] - 2025-03-07
|
||||
@@ -120,6 +147,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
+160
-3
@@ -118,6 +118,16 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
@@ -310,6 +320,12 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
@@ -368,7 +384,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"windows-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -422,18 +438,27 @@ 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",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.0"
|
||||
@@ -635,6 +660,53 @@ version = "0.8.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"getrandom",
|
||||
"libc",
|
||||
"untrusted",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.24.0-dev.0"
|
||||
source = "git+https://github.com/rustls/rustls.git?branch=main#c5a1a4a3f391718e0b5bf87de0cceabb67258ab7"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"rustls-pki-types",
|
||||
"rustls-webpki",
|
||||
"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 = "rustls-webpki"
|
||||
version = "0.104.0-alpha.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d58d0ba9664baf780379001ecb183f4b95dabec82d464bff14173dee21bf49e3"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.22"
|
||||
@@ -693,6 +765,12 @@ dependencies = [
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "sm9_core"
|
||||
version = "0.5.0"
|
||||
@@ -753,6 +831,12 @@ version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
@@ -836,7 +920,7 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -845,6 +929,15 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
@@ -854,6 +947,70 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.40"
|
||||
|
||||
+12
-3
@@ -1,15 +1,16 @@
|
||||
[package]
|
||||
name = "libsmx"
|
||||
version = "0.2.1"
|
||||
version = "0.3.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.83.0"
|
||||
authors = ["kintai <kintai@foxmail.com>"]
|
||||
license = "Apache-2.0"
|
||||
description = "Pure-Rust, no_std, constant-time SM2/SM3/SM4/SM9 Chinese cryptography (GB/T 32918/32905/32907/38635)"
|
||||
repository = "https://github.com/kintaiW/libsmx"
|
||||
documentation = "https://docs.rs/libsmx"
|
||||
homepage = "https://github.com/kintaiW/libsmx"
|
||||
homepage = "https://kintaiw.github.io/libsmx/"
|
||||
categories = ["cryptography", "no-std"]
|
||||
keywords = ["sm2", "sm3", "sm4", "sm9", "gmssl"]
|
||||
keywords = ["sm2", "sm3", "sm4", "sm9", "cryptography"]
|
||||
readme = "README.md"
|
||||
exclude = [
|
||||
"benches/",
|
||||
@@ -35,17 +36,25 @@ 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 = { git = "https://github.com/rustls/rustls.git", branch = "main", 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"
|
||||
criterion = { version = "0.5", features = ["html_reports"] }
|
||||
rand = "0.8"
|
||||
sm9_core = "0.5.0"
|
||||
# rustls-provider 集成测试依赖(通过 feature 条件编译保护)
|
||||
rustls = { git = "https://github.com/rustls/rustls.git", branch = "main", default-features = false, features = ["log", "webpki"] }
|
||||
pki-types = { package = "rustls-pki-types", version = "1", features = ["alloc"] }
|
||||
|
||||
[[bench]]
|
||||
name = "sm2_bench"
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
[](LICENSE)
|
||||
[](https://blog.rust-lang.org/2024/11/28/Rust-1.83.0.html)
|
||||
|
||||
Pure-Rust, `#![no_std]` implementation of Chinese commercial cryptography standards with constant-time operations throughout.
|
||||
Pure-Rust, `#![no_std]` implementation of Chinese commercial cryptography standards (国密/GuoMi) with constant-time operations throughout. Designed for embedded systems, WASM, and production use.
|
||||
|
||||
> **Security Notice**: This library has **not** been independently audited. While all secret-dependent operations are constant-time and `#![forbid(unsafe_code)]` is enforced, you should evaluate the risks before using it in production. Report vulnerabilities via [SECURITY.md](SECURITY.md) or email [kintai@foxmail.com](mailto:kintai@foxmail.com).
|
||||
|
||||
| Algorithm | Standard | Description |
|
||||
|-----------|----------|-------------|
|
||||
@@ -33,7 +35,7 @@ Add to `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
libsmx = "0.2"
|
||||
libsmx = "0.3"
|
||||
```
|
||||
|
||||
### SM3 Hash
|
||||
@@ -151,12 +153,20 @@ sm9_verify(msg, &h, &s, user_id, &sign_pub).unwrap();
|
||||
|---------|---------|-------------|
|
||||
| `alloc` | Yes | Enables `Vec`-returning APIs (SM2/SM9 encrypt/decrypt, SM4 modes) |
|
||||
| `std` | No | Enables `std::error::Error` impl and re-exports `rand_core/std` |
|
||||
| `rustls-provider` | No | Enables rustls `CryptoProvider` with RFC 8998 ShangMi TLS 1.3 cipher suites |
|
||||
|
||||
For `no_std` without `alloc`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
libsmx = { version = "0.2", default-features = false }
|
||||
libsmx = { version = "0.3", default-features = false }
|
||||
```
|
||||
|
||||
With rustls TLS 1.3 provider:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
libsmx = { version = "0.3", features = ["rustls-provider"] }
|
||||
```
|
||||
|
||||
## Benchmarks
|
||||
@@ -211,6 +221,12 @@ cargo bench
|
||||
|
||||
> **Disclaimer**: This library has **not** been independently audited. See [SECURITY.md](SECURITY.md) for vulnerability reporting.
|
||||
|
||||
## Contributing
|
||||
|
||||
Bug reports, feature requests, and pull requests are welcome on [GitHub](https://github.com/kintaiW/libsmx).
|
||||
|
||||
For **security vulnerabilities**, please do **not** open public issues. Instead, email [kintai@foxmail.com](mailto:kintai@foxmail.com) directly. See [SECURITY.md](SECURITY.md) for details.
|
||||
|
||||
## MSRV Policy
|
||||
|
||||
The minimum supported Rust version is **1.83.0**. MSRV bumps are treated as minor version changes.
|
||||
|
||||
+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 | 否 |
|
||||
|
||||
+894
@@ -0,0 +1,894 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="scroll-smooth">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>libsmx — Pure-Rust Chinese Cryptography</title>
|
||||
|
||||
<!-- SEO Meta -->
|
||||
<meta name="description" content="Pure-Rust, #![no_std] implementation of SM2/SM3/SM4/SM9/BLS/FPE Chinese commercial cryptography standards. Zero unsafe code, constant-time operations, side-channel resistant." />
|
||||
<meta name="keywords" content="Rust, SM2, SM3, SM4, SM9, BLS, FPE, Cryptography, No-Std, Chinese Cryptography, GB/T, constant-time, zero unsafe, side-channel resistant, AEAD, GCM, rustls" />
|
||||
<meta name="author" content="libsmx contributors" />
|
||||
<meta name="robots" content="index, follow" />
|
||||
<link rel="canonical" href="https://kintaiw.github.io/libsmx/" />
|
||||
|
||||
<!-- Open Graph -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:title" content="libsmx — Pure-Rust Chinese Cryptography" />
|
||||
<meta property="og:description" content="SM2/SM3/SM4/SM9/BLS/FPE in pure Rust. #![no_std], zero unsafe, constant-time everywhere." />
|
||||
<meta property="og:url" content="https://kintaiw.github.io/libsmx/" />
|
||||
<meta property="og:site_name" content="libsmx" />
|
||||
<!-- Replace with actual image URL after creating an OG image (1200x630 recommended) -->
|
||||
<meta property="og:image" content="https://kintaiw.github.io/libsmx/og-image.png" />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
|
||||
<!-- Twitter Card -->
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="libsmx — Pure-Rust Chinese Cryptography" />
|
||||
<meta name="twitter:description" content="SM2/SM3/SM4/SM9/BLS/FPE in pure Rust. #![no_std], zero unsafe, constant-time everywhere." />
|
||||
<meta name="twitter:image" content="https://kintaiw.github.io/libsmx/og-image.png" />
|
||||
|
||||
<!-- JSON-LD Structured Data -->
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "SoftwareSourceCode",
|
||||
"name": "libsmx",
|
||||
"description": "Pure-Rust, #![no_std] implementation of Chinese commercial cryptography standards (SM2/SM3/SM4/SM9/BLS/FPE) with constant-time operations and zero unsafe code.",
|
||||
"codeRepository": "https://github.com/kintaiW/libsmx",
|
||||
"programmingLanguage": "Rust",
|
||||
"license": "https://www.apache.org/licenses/LICENSE-2.0",
|
||||
"runtimePlatform": "Rust 1.83.0+",
|
||||
"url": "https://kintaiw.github.io/libsmx/",
|
||||
"keywords": ["SM2", "SM3", "SM4", "SM9", "BLS", "FPE", "cryptography", "no_std", "constant-time", "Rust"],
|
||||
"applicationCategory": "Security",
|
||||
"operatingSystem": "Cross-platform (no_std)"
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Tailwind CSS -->
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
brand: {
|
||||
DEFAULT: '#dea584',
|
||||
light: '#f0c6a0',
|
||||
dark: '#c4845a',
|
||||
},
|
||||
safe: {
|
||||
DEFAULT: '#10b981',
|
||||
light: '#34d399',
|
||||
},
|
||||
surface: {
|
||||
900: '#0f172a',
|
||||
800: '#1e293b',
|
||||
700: '#334155',
|
||||
600: '#475569',
|
||||
},
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ['Inter', 'system-ui', 'sans-serif'],
|
||||
mono: ['JetBrains Mono', 'Fira Code', 'monospace'],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- Google Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
|
||||
|
||||
<!-- Lucide Icons -->
|
||||
<script src="https://unpkg.com/lucide@latest"></script>
|
||||
|
||||
<!-- Prism.js (Tomorrow Night theme + Rust) -->
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css" rel="stylesheet" />
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-rust.min.js"></script>
|
||||
|
||||
<style>
|
||||
/* Fade-in animation */
|
||||
.fade-in {
|
||||
opacity: 0;
|
||||
transform: translateY(24px);
|
||||
transition: opacity 0.6s ease-out, transform 0.6s ease-out;
|
||||
}
|
||||
.fade-in.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Feature card hover */
|
||||
.feature-card {
|
||||
transition: transform 0.25s ease, box-shadow 0.25s ease;
|
||||
}
|
||||
.feature-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
/* Code window override */
|
||||
pre[class*="language-"] {
|
||||
margin: 0 !important;
|
||||
border-radius: 0 0 0.75rem 0.75rem !important;
|
||||
background: #1e293b !important;
|
||||
font-size: 0.875rem !important;
|
||||
line-height: 1.7 !important;
|
||||
}
|
||||
code[class*="language-"] {
|
||||
font-family: 'JetBrains Mono', 'Fira Code', monospace !important;
|
||||
font-size: 0.875rem !important;
|
||||
}
|
||||
|
||||
/* Benchmark bar */
|
||||
.bench-bar {
|
||||
height: 8px;
|
||||
border-radius: 4px;
|
||||
background: linear-gradient(90deg, #dea584, #10b981);
|
||||
transition: width 0.8s ease-out;
|
||||
}
|
||||
|
||||
/* Tab active state */
|
||||
.tab-btn.active {
|
||||
color: #dea584;
|
||||
border-bottom-color: #dea584;
|
||||
}
|
||||
|
||||
/* Scrollbar styling */
|
||||
::-webkit-scrollbar { width: 8px; height: 8px; }
|
||||
::-webkit-scrollbar-track { background: #1e293b; }
|
||||
::-webkit-scrollbar-thumb { background: #475569; border-radius: 4px; }
|
||||
::-webkit-scrollbar-thumb:hover { background: #64748b; }
|
||||
|
||||
/* Nav blur */
|
||||
.nav-blur {
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="bg-surface-900 text-gray-300 font-sans antialiased">
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- NAV -->
|
||||
<!-- ============================================================ -->
|
||||
<nav class="fixed top-0 left-0 right-0 z-50 nav-blur bg-surface-900/80 border-b border-white/5">
|
||||
<div class="max-w-6xl mx-auto px-6 h-16 flex items-center justify-between">
|
||||
<a href="#" class="flex items-center gap-2 text-white font-bold text-lg tracking-tight">
|
||||
<span class="text-brand"></></span> libsmx
|
||||
</a>
|
||||
<div class="hidden md:flex items-center gap-8 text-sm text-gray-400">
|
||||
<a href="#features" class="hover:text-white transition-colors">Features</a>
|
||||
<a href="#code" class="hover:text-white transition-colors">Code</a>
|
||||
<a href="#benchmarks" class="hover:text-white transition-colors">Benchmarks</a>
|
||||
<a href="#trust" class="hover:text-white transition-colors">Trust</a>
|
||||
<a href="https://docs.rs/libsmx" target="_blank" rel="noopener" class="hover:text-white transition-colors">Docs</a>
|
||||
<a href="https://github.com/kintaiW/libsmx" target="_blank" rel="noopener"
|
||||
class="flex items-center gap-1.5 text-white bg-white/10 hover:bg-white/15 px-4 py-1.5 rounded-lg transition-colors">
|
||||
<i data-lucide="github" class="w-4 h-4"></i> GitHub
|
||||
</a>
|
||||
</div>
|
||||
<!-- Mobile menu button -->
|
||||
<button id="mobile-menu-btn" class="md:hidden text-gray-400 hover:text-white">
|
||||
<i data-lucide="menu" class="w-6 h-6"></i>
|
||||
</button>
|
||||
</div>
|
||||
<!-- Mobile menu -->
|
||||
<div id="mobile-menu" class="hidden md:hidden bg-surface-800 border-t border-white/5 px-6 py-4 space-y-3 text-sm">
|
||||
<a href="#features" class="block text-gray-400 hover:text-white">Features</a>
|
||||
<a href="#code" class="block text-gray-400 hover:text-white">Code</a>
|
||||
<a href="#benchmarks" class="block text-gray-400 hover:text-white">Benchmarks</a>
|
||||
<a href="#trust" class="block text-gray-400 hover:text-white">Trust</a>
|
||||
<a href="https://docs.rs/libsmx" target="_blank" rel="noopener" class="block text-gray-400 hover:text-white">Docs</a>
|
||||
<a href="https://github.com/kintaiW/libsmx" target="_blank" rel="noopener" class="block text-gray-400 hover:text-white">GitHub</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- HERO -->
|
||||
<!-- ============================================================ -->
|
||||
<section class="relative min-h-screen flex items-center justify-center pt-16 overflow-hidden">
|
||||
<!-- Background gradient -->
|
||||
<div class="absolute inset-0 bg-gradient-to-b from-surface-900 via-surface-900 to-surface-800"></div>
|
||||
<div class="absolute top-1/4 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[800px] h-[800px] bg-brand/5 rounded-full blur-3xl"></div>
|
||||
<div class="absolute bottom-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-brand/20 to-transparent"></div>
|
||||
|
||||
<div class="relative z-10 max-w-4xl mx-auto px-6 text-center">
|
||||
<!-- Badges row -->
|
||||
<div class="flex flex-wrap justify-center gap-2 mb-8 fade-in">
|
||||
<a href="https://github.com/kintaiW/libsmx/actions/workflows/ci.yml" target="_blank" rel="noopener">
|
||||
<img src="https://github.com/kintaiW/libsmx/actions/workflows/ci.yml/badge.svg" alt="CI" loading="lazy" />
|
||||
</a>
|
||||
<a href="https://crates.io/crates/libsmx" target="_blank" rel="noopener">
|
||||
<img src="https://img.shields.io/crates/v/libsmx.svg" alt="Crates.io" loading="lazy" />
|
||||
</a>
|
||||
<a href="https://docs.rs/libsmx" target="_blank" rel="noopener">
|
||||
<img src="https://img.shields.io/docsrs/libsmx" alt="docs.rs" loading="lazy" />
|
||||
</a>
|
||||
<a href="https://codecov.io/gh/kintaiW/libsmx" target="_blank" rel="noopener">
|
||||
<img src="https://codecov.io/gh/kintaiW/libsmx/graph/badge.svg" alt="codecov" loading="lazy" />
|
||||
</a>
|
||||
<img src="https://img.shields.io/crates/l/libsmx.svg" alt="License" loading="lazy" />
|
||||
<img src="https://img.shields.io/badge/MSRV-1.83.0-blue.svg" alt="MSRV" loading="lazy" />
|
||||
</div>
|
||||
|
||||
<!-- Slogan -->
|
||||
<h1 class="text-4xl sm:text-5xl md:text-7xl font-black text-white leading-tight tracking-tight mb-6 fade-in">
|
||||
Pure-Rust Chinese Cryptography.
|
||||
<br />
|
||||
<span class="text-brand">Zero <code class="font-mono text-[0.85em]">unsafe</code></span>.
|
||||
<span class="text-safe">Constant-time</span>.
|
||||
</h1>
|
||||
|
||||
<!-- Subtitle -->
|
||||
<p class="text-lg sm:text-xl text-gray-400 max-w-2xl mx-auto mb-4 fade-in">
|
||||
SM2 / SM3 / SM4 / SM9 / BLS / FPE — six algorithms, fully implemented,
|
||||
strictly conforming to GB/T national standards.
|
||||
</p>
|
||||
<p class="text-base text-gray-500 max-w-xl mx-auto mb-10 fade-in">
|
||||
<code class="text-brand/80 font-mono text-sm">#![no_std]</code> ready.
|
||||
From bare-metal MCUs to WASM — runs everywhere.
|
||||
</p>
|
||||
|
||||
<!-- CTA Buttons -->
|
||||
<div class="flex flex-col sm:flex-row items-center justify-center gap-4 mb-12 fade-in">
|
||||
<button id="copy-btn" onclick="copyInstall()"
|
||||
class="group flex items-center gap-3 bg-brand text-surface-900 font-semibold px-6 py-3 rounded-xl hover:bg-brand-light transition-colors text-base">
|
||||
<i data-lucide="terminal" class="w-5 h-5"></i>
|
||||
<span class="font-mono">cargo add libsmx</span>
|
||||
<i data-lucide="copy" class="w-4 h-4 opacity-60 group-hover:opacity-100 transition-opacity"></i>
|
||||
</button>
|
||||
<a href="https://github.com/kintaiW/libsmx" target="_blank" rel="noopener"
|
||||
class="flex items-center gap-2 border border-white/20 text-white px-6 py-3 rounded-xl hover:bg-white/5 transition-colors text-base">
|
||||
<i data-lucide="github" class="w-5 h-5"></i> View on GitHub
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Scroll indicator -->
|
||||
<div class="animate-bounce fade-in">
|
||||
<i data-lucide="chevrons-down" class="w-6 h-6 text-gray-600 mx-auto"></i>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- ALGORITHM OVERVIEW -->
|
||||
<!-- ============================================================ -->
|
||||
<section class="py-20 bg-surface-800 border-y border-white/5">
|
||||
<div class="max-w-6xl mx-auto px-6">
|
||||
<div class="text-center mb-12 fade-in">
|
||||
<h2 class="text-sm font-semibold text-brand uppercase tracking-widest mb-3">Algorithm Coverage</h2>
|
||||
<p class="text-2xl sm:text-3xl font-bold text-white">Six Standards. One Crate.</p>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 fade-in">
|
||||
<!-- SM2 -->
|
||||
<div class="bg-surface-900/60 border border-white/5 rounded-xl p-5">
|
||||
<div class="flex items-center gap-3 mb-2">
|
||||
<span class="text-brand font-mono font-bold text-lg">SM2</span>
|
||||
<span class="text-xs text-gray-500 font-mono">GB/T 32918</span>
|
||||
</div>
|
||||
<p class="text-sm text-gray-400">Elliptic Curve PKC — sign, verify, encrypt, decrypt, key exchange, DER codec</p>
|
||||
</div>
|
||||
<!-- SM3 -->
|
||||
<div class="bg-surface-900/60 border border-white/5 rounded-xl p-5">
|
||||
<div class="flex items-center gap-3 mb-2">
|
||||
<span class="text-brand font-mono font-bold text-lg">SM3</span>
|
||||
<span class="text-xs text-gray-500 font-mono">GB/T 32905</span>
|
||||
</div>
|
||||
<p class="text-sm text-gray-400">256-bit cryptographic hash — streaming, one-shot, HMAC, HKDF</p>
|
||||
</div>
|
||||
<!-- SM4 -->
|
||||
<div class="bg-surface-900/60 border border-white/5 rounded-xl p-5">
|
||||
<div class="flex items-center gap-3 mb-2">
|
||||
<span class="text-brand font-mono font-bold text-lg">SM4</span>
|
||||
<span class="text-xs text-gray-500 font-mono">GB/T 32907</span>
|
||||
</div>
|
||||
<p class="text-sm text-gray-400">128-bit block cipher — ECB, CBC, OFB, CFB, CTR, GCM, CCM, XTS</p>
|
||||
</div>
|
||||
<!-- SM9 -->
|
||||
<div class="bg-surface-900/60 border border-white/5 rounded-xl p-5">
|
||||
<div class="flex items-center gap-3 mb-2">
|
||||
<span class="text-brand font-mono font-bold text-lg">SM9</span>
|
||||
<span class="text-xs text-gray-500 font-mono">GB/T 38635</span>
|
||||
</div>
|
||||
<p class="text-sm text-gray-400">Identity-based crypto — BN256 pairing, sign, encrypt, key encapsulation</p>
|
||||
</div>
|
||||
<!-- BLS -->
|
||||
<div class="bg-surface-900/60 border border-white/5 rounded-xl p-5">
|
||||
<div class="flex items-center gap-3 mb-2">
|
||||
<span class="text-brand font-mono font-bold text-lg">BLS</span>
|
||||
<span class="text-xs text-gray-500 font-mono">RFC 9380</span>
|
||||
</div>
|
||||
<p class="text-sm text-gray-400">BLS signatures — aggregate verification, Shamir threshold signing</p>
|
||||
</div>
|
||||
<!-- FPE -->
|
||||
<div class="bg-surface-900/60 border border-white/5 rounded-xl p-5">
|
||||
<div class="flex items-center gap-3 mb-2">
|
||||
<span class="text-brand font-mono font-bold text-lg">FPE</span>
|
||||
<span class="text-xs text-gray-500 font-mono">SP 800-38G</span>
|
||||
</div>
|
||||
<p class="text-sm text-gray-400">Format-preserving encryption — FNR algorithm, 7-round Feistel with SM4</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- FEATURES GRID -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="features" class="py-24">
|
||||
<div class="max-w-6xl mx-auto px-6">
|
||||
<div class="text-center mb-16 fade-in">
|
||||
<h2 class="text-sm font-semibold text-brand uppercase tracking-widest mb-3">Why libsmx</h2>
|
||||
<p class="text-2xl sm:text-3xl font-bold text-white">Built for Security Engineers</p>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<!-- Card 1 -->
|
||||
<div class="feature-card bg-surface-800 border border-white/5 rounded-2xl p-7 fade-in">
|
||||
<div class="w-12 h-12 rounded-xl bg-brand/10 flex items-center justify-center mb-5">
|
||||
<i data-lucide="shield-check" class="w-6 h-6 text-brand"></i>
|
||||
</div>
|
||||
<h3 class="text-white font-semibold text-lg mb-2">Full GB/T Compliance</h3>
|
||||
<p class="text-gray-400 text-sm leading-relaxed">
|
||||
Complete SM2/SM3/SM4/SM9 implementations, plus BLS signatures and format-preserving encryption. Every algorithm validated against official GB/T standard test vectors.
|
||||
</p>
|
||||
</div>
|
||||
<!-- Card 2 -->
|
||||
<div class="feature-card bg-surface-800 border border-white/5 rounded-2xl p-7 fade-in">
|
||||
<div class="w-12 h-12 rounded-xl bg-safe/10 flex items-center justify-center mb-5">
|
||||
<i data-lucide="timer" class="w-6 h-6 text-safe"></i>
|
||||
</div>
|
||||
<h3 class="text-white font-semibold text-lg mb-2">Constant-Time Everywhere</h3>
|
||||
<p class="text-gray-400 text-sm leading-relaxed">
|
||||
All secret-dependent ops use <code class="text-brand/80 font-mono text-xs">subtle</code> primitives. SM2 scalar multiplication iterates all 256 bits. SM4 S-box uses boolean-circuit bitslice — zero cache-timing leaks.
|
||||
</p>
|
||||
</div>
|
||||
<!-- Card 3 -->
|
||||
<div class="feature-card bg-surface-800 border border-white/5 rounded-2xl p-7 fade-in">
|
||||
<div class="w-12 h-12 rounded-xl bg-red-500/10 flex items-center justify-center mb-5">
|
||||
<i data-lucide="shield-off" class="w-6 h-6 text-red-400"></i>
|
||||
</div>
|
||||
<h3 class="text-white font-semibold text-lg mb-2">Zero <code class="font-mono">unsafe</code> Code</h3>
|
||||
<p class="text-gray-400 text-sm leading-relaxed">
|
||||
<code class="text-brand/80 font-mono text-xs">#![forbid(unsafe_code)]</code> enforced at the crate root. Private keys implement <code class="text-brand/80 font-mono text-xs">ZeroizeOnDrop</code> — automatic cleanup on scope exit.
|
||||
</p>
|
||||
</div>
|
||||
<!-- Card 4 -->
|
||||
<div class="feature-card bg-surface-800 border border-white/5 rounded-2xl p-7 fade-in">
|
||||
<div class="w-12 h-12 rounded-xl bg-blue-500/10 flex items-center justify-center mb-5">
|
||||
<i data-lucide="cpu" class="w-6 h-6 text-blue-400"></i>
|
||||
</div>
|
||||
<h3 class="text-white font-semibold text-lg mb-2"><code class="font-mono">#![no_std]</code> Ready</h3>
|
||||
<p class="text-gray-400 text-sm leading-relaxed">
|
||||
Runs on embedded MCUs, WASM runtimes, and bare-metal targets. Optional <code class="text-brand/80 font-mono text-xs">alloc</code> and <code class="text-brand/80 font-mono text-xs">std</code> feature flags. Zero runtime dependencies.
|
||||
</p>
|
||||
</div>
|
||||
<!-- Card 5 -->
|
||||
<div class="feature-card bg-surface-800 border border-white/5 rounded-2xl p-7 fade-in">
|
||||
<div class="w-12 h-12 rounded-xl bg-yellow-500/10 flex items-center justify-center mb-5">
|
||||
<i data-lucide="zap" class="w-6 h-6 text-yellow-400"></i>
|
||||
</div>
|
||||
<h3 class="text-white font-semibold text-lg mb-2">Production Performance</h3>
|
||||
<p class="text-gray-400 text-sm leading-relaxed">
|
||||
SM3 hashing at <strong class="text-white">374 MiB/s</strong>. SM2 signing in <strong class="text-white">258 µs</strong>. SM4-ECB at <strong class="text-white">27 MiB/s</strong> — all measured under constant-time constraints. No shortcuts.
|
||||
</p>
|
||||
</div>
|
||||
<!-- Card 6 -->
|
||||
<div class="feature-card bg-surface-800 border border-white/5 rounded-2xl p-7 fade-in">
|
||||
<div class="w-12 h-12 rounded-xl bg-purple-500/10 flex items-center justify-center mb-5">
|
||||
<i data-lucide="lock" class="w-6 h-6 text-purple-400"></i>
|
||||
</div>
|
||||
<h3 class="text-white font-semibold text-lg mb-2">rustls TLS 1.3</h3>
|
||||
<p class="text-gray-400 text-sm leading-relaxed">
|
||||
Feature-gated <code class="text-brand/80 font-mono text-xs">rustls-provider</code> delivers RFC 8998 ShangMi cipher suites (SM4-GCM-SM3 / SM4-CCM-SM3). Plug directly into the Rust TLS ecosystem.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- CODE DEMO -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="code" class="py-24 bg-surface-800 border-y border-white/5">
|
||||
<div class="max-w-6xl mx-auto px-6">
|
||||
<div class="text-center mb-16 fade-in">
|
||||
<h2 class="text-sm font-semibold text-brand uppercase tracking-widest mb-3">Code Examples</h2>
|
||||
<p class="text-2xl sm:text-3xl font-bold text-white">Clean API. Real Cryptography.</p>
|
||||
</div>
|
||||
|
||||
<!-- Tabs -->
|
||||
<div class="flex gap-1 mb-0 fade-in">
|
||||
<button onclick="switchTab('sm2')" id="tab-sm2"
|
||||
class="tab-btn active px-5 py-2.5 text-sm font-medium border-b-2 border-transparent rounded-t-lg bg-surface-900/40 hover:bg-surface-900/60 transition-colors">
|
||||
SM2 Sign / Verify
|
||||
</button>
|
||||
<button onclick="switchTab('sm4')" id="tab-sm4"
|
||||
class="tab-btn px-5 py-2.5 text-sm font-medium border-b-2 border-transparent rounded-t-lg bg-surface-900/40 hover:bg-surface-900/60 transition-colors text-gray-500">
|
||||
SM4-GCM AEAD
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- SM2 Tab Content -->
|
||||
<div id="panel-sm2" class="grid grid-cols-1 lg:grid-cols-5 gap-0 fade-in">
|
||||
<!-- Code (left, 3 cols) -->
|
||||
<div class="lg:col-span-3">
|
||||
<div class="bg-surface-900 rounded-tl-none rounded-tr-none lg:rounded-tr-none rounded-2xl overflow-hidden border border-white/5">
|
||||
<!-- IDE title bar -->
|
||||
<div class="flex items-center gap-2 px-4 py-3 bg-surface-900 border-b border-white/5">
|
||||
<span class="w-3 h-3 rounded-full bg-red-500/80"></span>
|
||||
<span class="w-3 h-3 rounded-full bg-yellow-500/80"></span>
|
||||
<span class="w-3 h-3 rounded-full bg-green-500/80"></span>
|
||||
<span class="text-xs text-gray-500 ml-2 font-mono">sm2_example.rs</span>
|
||||
</div>
|
||||
<pre class="!rounded-t-none"><code class="language-rust">use libsmx::sm2::{generate_keypair, get_z, get_e, sign, verify};
|
||||
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
|
||||
// Key generation
|
||||
let (pri_key, pub_key) = generate_keypair(&mut rng);
|
||||
|
||||
// Sign: compute Z value and message digest per GB/T 32918.2
|
||||
let z = get_z(b"1234567812345678", &pub_key);
|
||||
let e = get_e(&z, b"hello SM2");
|
||||
let sig = sign(&e, &pri_key, &mut rng);
|
||||
|
||||
// Verify
|
||||
verify(&e, &pub_key, &sig).expect("signature valid");</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Explanation (right, 2 cols) -->
|
||||
<div class="lg:col-span-2 bg-surface-900/50 border border-white/5 lg:border-l-0 rounded-b-2xl lg:rounded-bl-none lg:rounded-r-2xl p-6 space-y-5">
|
||||
<div>
|
||||
<div class="flex items-center gap-2 mb-1.5">
|
||||
<span class="w-5 h-5 rounded-full bg-brand/20 text-brand text-xs font-bold flex items-center justify-center">1</span>
|
||||
<span class="text-white font-medium text-sm">generate_keypair</span>
|
||||
</div>
|
||||
<p class="text-gray-400 text-sm pl-7">OS-level CSPRNG generates a 256-bit SM2 key pair on the standard curve (GB/T 32918.5).</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex items-center gap-2 mb-1.5">
|
||||
<span class="w-5 h-5 rounded-full bg-brand/20 text-brand text-xs font-bold flex items-center justify-center">2</span>
|
||||
<span class="text-white font-medium text-sm">get_z</span>
|
||||
</div>
|
||||
<p class="text-gray-400 text-sm pl-7">Computes the user-distinguishing Z value per the national standard — SM3 hash of ID length, ID, curve params, and public key coordinates.</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex items-center gap-2 mb-1.5">
|
||||
<span class="w-5 h-5 rounded-full bg-brand/20 text-brand text-xs font-bold flex items-center justify-center">3</span>
|
||||
<span class="text-white font-medium text-sm">get_e → sign</span>
|
||||
</div>
|
||||
<p class="text-gray-400 text-sm pl-7">Digest <code class="text-brand/80 font-mono text-xs">e = SM3(Z || M)</code>, then sign. Scalar multiplication runs all 256 rounds — constant-time against timing attacks.</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex items-center gap-2 mb-1.5">
|
||||
<span class="w-5 h-5 rounded-full bg-brand/20 text-brand text-xs font-bold flex items-center justify-center">4</span>
|
||||
<span class="text-white font-medium text-sm">verify</span>
|
||||
</div>
|
||||
<p class="text-gray-400 text-sm pl-7">Constant-time verification returns <code class="text-brand/80 font-mono text-xs">Result<()></code>. Failures leak zero timing information.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SM4 Tab Content (hidden by default) -->
|
||||
<div id="panel-sm4" class="hidden grid grid-cols-1 lg:grid-cols-5 gap-0 fade-in">
|
||||
<!-- Code (left, 3 cols) -->
|
||||
<div class="lg:col-span-3">
|
||||
<div class="bg-surface-900 rounded-2xl overflow-hidden border border-white/5">
|
||||
<div class="flex items-center gap-2 px-4 py-3 bg-surface-900 border-b border-white/5">
|
||||
<span class="w-3 h-3 rounded-full bg-red-500/80"></span>
|
||||
<span class="w-3 h-3 rounded-full bg-yellow-500/80"></span>
|
||||
<span class="w-3 h-3 rounded-full bg-green-500/80"></span>
|
||||
<span class="text-xs text-gray-500 ml-2 font-mono">sm4_gcm_example.rs</span>
|
||||
</div>
|
||||
<pre class="!rounded-t-none"><code class="language-rust">use libsmx::sm4::{sm4_encrypt_gcm, sm4_decrypt_gcm};
|
||||
|
||||
let key = [0u8; 16];
|
||||
let nonce = [0u8; 12];
|
||||
let aad = b"additional data";
|
||||
let plaintext = b"secret message";
|
||||
|
||||
let (ciphertext, tag) = sm4_encrypt_gcm(
|
||||
&key, &nonce, aad, plaintext
|
||||
);
|
||||
|
||||
let decrypted = sm4_decrypt_gcm(
|
||||
&key, &nonce, aad, &ciphertext, &tag
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(decrypted, plaintext);</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Explanation (right, 2 cols) -->
|
||||
<div class="lg:col-span-2 bg-surface-900/50 border border-white/5 lg:border-l-0 rounded-b-2xl lg:rounded-bl-none lg:rounded-r-2xl p-6 space-y-5">
|
||||
<div>
|
||||
<div class="flex items-center gap-2 mb-1.5">
|
||||
<span class="w-5 h-5 rounded-full bg-safe/20 text-safe text-xs font-bold flex items-center justify-center">1</span>
|
||||
<span class="text-white font-medium text-sm">128-bit Key + 96-bit Nonce</span>
|
||||
</div>
|
||||
<p class="text-gray-400 text-sm pl-7">Standard AEAD parameters per NIST GCM specification. The 128-bit key matches SM4's native block size.</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex items-center gap-2 mb-1.5">
|
||||
<span class="w-5 h-5 rounded-full bg-safe/20 text-safe text-xs font-bold flex items-center justify-center">2</span>
|
||||
<span class="text-white font-medium text-sm">Additional Authenticated Data</span>
|
||||
</div>
|
||||
<p class="text-gray-400 text-sm pl-7"><code class="text-brand/80 font-mono text-xs">aad</code> is authenticated but not encrypted — ideal for protocol headers that need integrity without confidentiality.</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex items-center gap-2 mb-1.5">
|
||||
<span class="w-5 h-5 rounded-full bg-safe/20 text-safe text-xs font-bold flex items-center justify-center">3</span>
|
||||
<span class="text-white font-medium text-sm">(ciphertext, tag)</span>
|
||||
</div>
|
||||
<p class="text-gray-400 text-sm pl-7">Returns ciphertext + 128-bit authentication tag. The tag ensures tamper detection via GMAC.</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex items-center gap-2 mb-1.5">
|
||||
<span class="w-5 h-5 rounded-full bg-safe/20 text-safe text-xs font-bold flex items-center justify-center">4</span>
|
||||
<span class="text-white font-medium text-sm">Constant-time Tag Verification</span>
|
||||
</div>
|
||||
<p class="text-gray-400 text-sm pl-7">Decryption verifies the tag in constant time. Any tampering returns <code class="text-brand/80 font-mono text-xs">Err</code> — no timing oracle.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- BENCHMARKS -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="benchmarks" class="py-24">
|
||||
<div class="max-w-6xl mx-auto px-6">
|
||||
<div class="text-center mb-16 fade-in">
|
||||
<h2 class="text-sm font-semibold text-brand uppercase tracking-widest mb-3">Performance</h2>
|
||||
<p class="text-2xl sm:text-3xl font-bold text-white">Security Is Not a Performance Trade-off</p>
|
||||
<p class="text-gray-400 mt-3 text-sm">All measurements are constant-time. Linux x86_64, single core. Run <code class="font-mono text-brand/80">cargo bench</code> for your own numbers.</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
|
||||
<!-- Throughput -->
|
||||
<div class="bg-surface-800 border border-white/5 rounded-2xl p-6 fade-in">
|
||||
<h3 class="text-white font-semibold mb-1 flex items-center gap-2">
|
||||
<i data-lucide="gauge" class="w-5 h-5 text-brand"></i> Throughput
|
||||
</h3>
|
||||
<p class="text-xs text-gray-500 mb-5">64 KiB data, constant-time</p>
|
||||
|
||||
<!-- SM3 -->
|
||||
<div class="mb-5">
|
||||
<div class="flex justify-between text-sm mb-1.5">
|
||||
<span class="text-gray-300 font-mono">SM3 Hash</span>
|
||||
<span class="text-brand font-semibold">374 MiB/s</span>
|
||||
</div>
|
||||
<div class="w-full bg-surface-700 rounded-full h-2">
|
||||
<div class="bench-bar" style="width: 100%"></div>
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 mt-1">167 µs @ 64 KiB</div>
|
||||
</div>
|
||||
|
||||
<!-- SM4-ECB -->
|
||||
<div>
|
||||
<div class="flex justify-between text-sm mb-1.5">
|
||||
<span class="text-gray-300 font-mono">SM4-ECB</span>
|
||||
<span class="text-brand font-semibold">27 MiB/s</span>
|
||||
</div>
|
||||
<div class="w-full bg-surface-700 rounded-full h-2">
|
||||
<div class="bench-bar" style="width: 7.2%"></div>
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 mt-1">2.32 ms @ 64 KiB</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SM2 -->
|
||||
<div class="bg-surface-800 border border-white/5 rounded-2xl p-6 fade-in">
|
||||
<h3 class="text-white font-semibold mb-1 flex items-center gap-2">
|
||||
<i data-lucide="key-round" class="w-5 h-5 text-brand"></i> SM2 (256-bit ECC)
|
||||
</h3>
|
||||
<p class="text-xs text-gray-500 mb-5">Elliptic curve operations</p>
|
||||
|
||||
<div class="space-y-3">
|
||||
<div class="flex justify-between items-center text-sm">
|
||||
<span class="text-gray-400">Key Generation</span>
|
||||
<span class="text-white font-mono font-medium">221 µs</span>
|
||||
</div>
|
||||
<div class="w-full bg-surface-700 rounded-full h-1.5">
|
||||
<div class="bench-bar" style="width: 34.6%"></div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center text-sm">
|
||||
<span class="text-gray-400">Sign</span>
|
||||
<span class="text-white font-mono font-medium">258 µs</span>
|
||||
</div>
|
||||
<div class="w-full bg-surface-700 rounded-full h-1.5">
|
||||
<div class="bench-bar" style="width: 40.4%"></div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center text-sm">
|
||||
<span class="text-gray-400">Verify</span>
|
||||
<span class="text-white font-mono font-medium">316 µs</span>
|
||||
</div>
|
||||
<div class="w-full bg-surface-700 rounded-full h-1.5">
|
||||
<div class="bench-bar" style="width: 49.5%"></div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center text-sm">
|
||||
<span class="text-gray-400">Encrypt</span>
|
||||
<span class="text-white font-mono font-medium">639 µs</span>
|
||||
</div>
|
||||
<div class="w-full bg-surface-700 rounded-full h-1.5">
|
||||
<div class="bench-bar" style="width: 100%"></div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center text-sm">
|
||||
<span class="text-gray-400">Decrypt</span>
|
||||
<span class="text-white font-mono font-medium">417 µs</span>
|
||||
</div>
|
||||
<div class="w-full bg-surface-700 rounded-full h-1.5">
|
||||
<div class="bench-bar" style="width: 65.3%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SM9 -->
|
||||
<div class="bg-surface-800 border border-white/5 rounded-2xl p-6 fade-in">
|
||||
<h3 class="text-white font-semibold mb-1 flex items-center gap-2">
|
||||
<i data-lucide="fingerprint" class="w-5 h-5 text-brand"></i> SM9 (BN256 Pairing)
|
||||
</h3>
|
||||
<p class="text-xs text-gray-500 mb-5">Identity-based cryptography</p>
|
||||
|
||||
<div class="space-y-3">
|
||||
<div class="flex justify-between items-center text-sm">
|
||||
<span class="text-gray-400">Master Keygen</span>
|
||||
<span class="text-white font-mono font-medium">753 µs</span>
|
||||
</div>
|
||||
<div class="w-full bg-surface-700 rounded-full h-1.5">
|
||||
<div class="bench-bar" style="width: 13.7%"></div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center text-sm">
|
||||
<span class="text-gray-400">User Keygen</span>
|
||||
<span class="text-white font-mono font-medium">324 µs</span>
|
||||
</div>
|
||||
<div class="w-full bg-surface-700 rounded-full h-1.5">
|
||||
<div class="bench-bar" style="width: 5.9%"></div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center text-sm">
|
||||
<span class="text-gray-400">Sign</span>
|
||||
<span class="text-white font-mono font-medium">3.44 ms</span>
|
||||
</div>
|
||||
<div class="w-full bg-surface-700 rounded-full h-1.5">
|
||||
<div class="bench-bar" style="width: 62.5%"></div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center text-sm">
|
||||
<span class="text-gray-400">Verify</span>
|
||||
<span class="text-white font-mono font-medium">5.50 ms</span>
|
||||
</div>
|
||||
<div class="w-full bg-surface-700 rounded-full h-1.5">
|
||||
<div class="bench-bar" style="width: 100%"></div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center text-sm">
|
||||
<span class="text-gray-400">Encrypt</span>
|
||||
<span class="text-white font-mono font-medium">4.68 ms</span>
|
||||
</div>
|
||||
<div class="w-full bg-surface-700 rounded-full h-1.5">
|
||||
<div class="bench-bar" style="width: 85.1%"></div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center text-sm">
|
||||
<span class="text-gray-400">Decrypt</span>
|
||||
<span class="text-white font-mono font-medium">1.54 ms</span>
|
||||
</div>
|
||||
<div class="w-full bg-surface-700 rounded-full h-1.5">
|
||||
<div class="bench-bar" style="width: 28%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- TRUST -->
|
||||
<!-- ============================================================ -->
|
||||
<section id="trust" class="py-24 bg-surface-800 border-y border-white/5">
|
||||
<div class="max-w-6xl mx-auto px-6">
|
||||
<div class="text-center mb-16 fade-in">
|
||||
<h2 class="text-sm font-semibold text-brand uppercase tracking-widest mb-3">Trust</h2>
|
||||
<p class="text-2xl sm:text-3xl font-bold text-white">Why Trust libsmx?</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-12">
|
||||
<!-- Zero Unsafe -->
|
||||
<div class="bg-surface-900/60 border border-white/5 rounded-2xl p-7 text-center fade-in">
|
||||
<div class="w-14 h-14 rounded-full bg-red-500/10 flex items-center justify-center mx-auto mb-5">
|
||||
<i data-lucide="shield-ban" class="w-7 h-7 text-red-400"></i>
|
||||
</div>
|
||||
<h3 class="text-white font-semibold text-lg mb-3">Zero <code class="font-mono">unsafe</code></h3>
|
||||
<p class="text-gray-400 text-sm leading-relaxed">
|
||||
<code class="text-brand/80 font-mono text-xs">#![forbid(unsafe_code)]</code> is enforced at the crate root — compile-time guarantee. Private keys implement <code class="text-brand/80 font-mono text-xs">ZeroizeOnDrop</code>. GCM/CCM tags verified in constant time.
|
||||
</p>
|
||||
</div>
|
||||
<!-- MSRV -->
|
||||
<div class="bg-surface-900/60 border border-white/5 rounded-2xl p-7 text-center fade-in">
|
||||
<div class="w-14 h-14 rounded-full bg-blue-500/10 flex items-center justify-center mx-auto mb-5">
|
||||
<i data-lucide="settings" class="w-7 h-7 text-blue-400"></i>
|
||||
</div>
|
||||
<h3 class="text-white font-semibold text-lg mb-3">MSRV Policy</h3>
|
||||
<p class="text-gray-400 text-sm leading-relaxed">
|
||||
Minimum supported Rust version: <strong class="text-white">1.83.0</strong>. MSRV bumps are treated as minor version changes. CI matrix enforces compatibility. No nightly required — ever.
|
||||
</p>
|
||||
</div>
|
||||
<!-- License -->
|
||||
<div class="bg-surface-900/60 border border-white/5 rounded-2xl p-7 text-center fade-in">
|
||||
<div class="w-14 h-14 rounded-full bg-brand/10 flex items-center justify-center mx-auto mb-5">
|
||||
<i data-lucide="scale" class="w-7 h-7 text-brand"></i>
|
||||
</div>
|
||||
<h3 class="text-white font-semibold text-lg mb-3">Apache-2.0</h3>
|
||||
<p class="text-gray-400 text-sm leading-relaxed">
|
||||
Business-friendly license. No copyleft obligations. Free to use in commercial products, internal systems, and government compliance projects.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Disclaimer -->
|
||||
<div class="bg-yellow-500/5 border border-yellow-500/20 rounded-xl p-5 text-center fade-in">
|
||||
<p class="text-yellow-200/80 text-sm">
|
||||
<strong>Disclaimer:</strong> This library has <strong>not</strong> been independently audited.
|
||||
Report vulnerabilities via <a href="https://github.com/kintaiW/libsmx/blob/main/SECURITY.md" target="_blank" rel="noopener" class="text-brand underline hover:text-brand-light">SECURITY.md</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- QUICK START -->
|
||||
<!-- ============================================================ -->
|
||||
<section class="py-24">
|
||||
<div class="max-w-3xl mx-auto px-6 text-center fade-in">
|
||||
<h2 class="text-2xl sm:text-3xl font-bold text-white mb-4">Get Started in Seconds</h2>
|
||||
<p class="text-gray-400 mb-8">Add libsmx to your project and start using Chinese cryptography today.</p>
|
||||
|
||||
<div class="bg-surface-800 border border-white/5 rounded-2xl overflow-hidden inline-block w-full max-w-lg">
|
||||
<div class="flex items-center gap-2 px-4 py-3 bg-surface-900 border-b border-white/5">
|
||||
<span class="w-3 h-3 rounded-full bg-red-500/80"></span>
|
||||
<span class="w-3 h-3 rounded-full bg-yellow-500/80"></span>
|
||||
<span class="w-3 h-3 rounded-full bg-green-500/80"></span>
|
||||
<span class="text-xs text-gray-500 ml-2 font-mono">terminal</span>
|
||||
</div>
|
||||
<div class="p-6 text-left">
|
||||
<p class="font-mono text-sm text-gray-400 mb-2"># Add to your project</p>
|
||||
<p class="font-mono text-sm text-white mb-4">$ <span class="text-brand">cargo add libsmx</span></p>
|
||||
<p class="font-mono text-sm text-gray-400 mb-2"># Or with no_std (no alloc)</p>
|
||||
<p class="font-mono text-sm text-white mb-4">$ <span class="text-brand">cargo add libsmx --no-default-features</span></p>
|
||||
<p class="font-mono text-sm text-gray-400 mb-2"># With rustls TLS provider</p>
|
||||
<p class="font-mono text-sm text-white">$ <span class="text-brand">cargo add libsmx --features rustls-provider</span></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 flex flex-col sm:flex-row items-center justify-center gap-4">
|
||||
<a href="https://docs.rs/libsmx" target="_blank" rel="noopener"
|
||||
class="flex items-center gap-2 bg-brand text-surface-900 font-semibold px-6 py-3 rounded-xl hover:bg-brand-light transition-colors">
|
||||
<i data-lucide="book-open" class="w-5 h-5"></i> Read the Docs
|
||||
</a>
|
||||
<a href="https://github.com/kintaiW/libsmx" target="_blank" rel="noopener"
|
||||
class="flex items-center gap-2 border border-white/20 text-white px-6 py-3 rounded-xl hover:bg-white/5 transition-colors">
|
||||
<i data-lucide="github" class="w-5 h-5"></i> Star on GitHub
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- FOOTER -->
|
||||
<!-- ============================================================ -->
|
||||
<footer class="py-12 border-t border-white/5">
|
||||
<div class="max-w-6xl mx-auto px-6">
|
||||
<div class="flex flex-col items-center gap-6">
|
||||
<!-- Links -->
|
||||
<div class="flex flex-wrap justify-center gap-6 text-sm text-gray-400">
|
||||
<a href="https://github.com/kintaiW/libsmx" target="_blank" rel="noopener" class="hover:text-white transition-colors flex items-center gap-1.5">
|
||||
<i data-lucide="github" class="w-4 h-4"></i> GitHub
|
||||
</a>
|
||||
<a href="https://docs.rs/libsmx" target="_blank" rel="noopener" class="hover:text-white transition-colors flex items-center gap-1.5">
|
||||
<i data-lucide="book-open" class="w-4 h-4"></i> API Docs
|
||||
</a>
|
||||
<a href="https://crates.io/crates/libsmx" target="_blank" rel="noopener" class="hover:text-white transition-colors flex items-center gap-1.5">
|
||||
<i data-lucide="package" class="w-4 h-4"></i> Crates.io
|
||||
</a>
|
||||
<a href="https://github.com/kintaiW/libsmx/blob/main/CHANGELOG.md" target="_blank" rel="noopener" class="hover:text-white transition-colors flex items-center gap-1.5">
|
||||
<i data-lucide="file-text" class="w-4 h-4"></i> Changelog
|
||||
</a>
|
||||
</div>
|
||||
<!-- Version info -->
|
||||
<p class="text-xs text-gray-600 font-mono">
|
||||
libsmx v0.3.0 · MSRV 1.83.0 · Apache-2.0
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- SCRIPTS -->
|
||||
<!-- ============================================================ -->
|
||||
<script>
|
||||
// Initialize Lucide icons
|
||||
lucide.createIcons();
|
||||
|
||||
// Copy install command
|
||||
function copyInstall() {
|
||||
navigator.clipboard.writeText('cargo add libsmx').then(() => {
|
||||
const btn = document.getElementById('copy-btn');
|
||||
const original = btn.innerHTML;
|
||||
btn.innerHTML = '<span class="flex items-center gap-2"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg> Copied!</span>';
|
||||
setTimeout(() => {
|
||||
btn.innerHTML = original;
|
||||
lucide.createIcons();
|
||||
}, 2000);
|
||||
});
|
||||
}
|
||||
|
||||
// Tab switching
|
||||
function switchTab(tab) {
|
||||
// Hide all panels
|
||||
document.getElementById('panel-sm2').classList.add('hidden');
|
||||
document.getElementById('panel-sm4').classList.add('hidden');
|
||||
// Deactivate all tabs
|
||||
document.querySelectorAll('.tab-btn').forEach(btn => {
|
||||
btn.classList.remove('active');
|
||||
btn.classList.add('text-gray-500');
|
||||
});
|
||||
// Show selected panel
|
||||
const panel = document.getElementById('panel-' + tab);
|
||||
panel.classList.remove('hidden');
|
||||
// Activate selected tab
|
||||
const tabBtn = document.getElementById('tab-' + tab);
|
||||
tabBtn.classList.add('active');
|
||||
tabBtn.classList.remove('text-gray-500');
|
||||
// Re-highlight code if Prism is available
|
||||
if (window.Prism) {
|
||||
Prism.highlightAllUnder(panel);
|
||||
}
|
||||
}
|
||||
|
||||
// Fade-in on scroll (Intersection Observer)
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add('visible');
|
||||
}
|
||||
});
|
||||
},
|
||||
{ threshold: 0.1, rootMargin: '0px 0px -40px 0px' }
|
||||
);
|
||||
document.querySelectorAll('.fade-in').forEach((el) => observer.observe(el));
|
||||
|
||||
// Mobile menu toggle
|
||||
const mobileMenuBtn = document.getElementById('mobile-menu-btn');
|
||||
const mobileMenu = document.getElementById('mobile-menu');
|
||||
if (mobileMenuBtn && mobileMenu) {
|
||||
mobileMenuBtn.addEventListener('click', () => {
|
||||
mobileMenu.classList.toggle('hidden');
|
||||
});
|
||||
// Close menu when clicking a link
|
||||
mobileMenu.querySelectorAll('a').forEach(link => {
|
||||
link.addEventListener('click', () => {
|
||||
mobileMenu.classList.add('hidden');
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 157 KiB |
@@ -0,0 +1,164 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630" viewBox="0 0 1200 630">
|
||||
<defs>
|
||||
<!-- Background gradient -->
|
||||
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0%" stop-color="#0f172a"/>
|
||||
<stop offset="100%" stop-color="#1e293b"/>
|
||||
</linearGradient>
|
||||
<!-- Brand accent glow -->
|
||||
<radialGradient id="glow" cx="0.25" cy="0.5" r="0.5">
|
||||
<stop offset="0%" stop-color="#dea584" stop-opacity="0.12"/>
|
||||
<stop offset="100%" stop-color="#dea584" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<!-- Green glow -->
|
||||
<radialGradient id="glowGreen" cx="0.75" cy="0.6" r="0.4">
|
||||
<stop offset="0%" stop-color="#10b981" stop-opacity="0.08"/>
|
||||
<stop offset="100%" stop-color="#10b981" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Background -->
|
||||
<rect width="1200" height="630" fill="url(#bg)"/>
|
||||
<rect width="1200" height="630" fill="url(#glow)"/>
|
||||
<rect width="1200" height="630" fill="url(#glowGreen)"/>
|
||||
|
||||
<!-- Subtle grid pattern -->
|
||||
<g opacity="0.03" stroke="#ffffff" stroke-width="0.5">
|
||||
<line x1="0" y1="0" x2="0" y2="630" transform="translate(60,0)"/>
|
||||
<line x1="0" y1="0" x2="0" y2="630" transform="translate(120,0)"/>
|
||||
<line x1="0" y1="0" x2="0" y2="630" transform="translate(180,0)"/>
|
||||
<line x1="0" y1="0" x2="0" y2="630" transform="translate(240,0)"/>
|
||||
<line x1="0" y1="0" x2="0" y2="630" transform="translate(300,0)"/>
|
||||
<line x1="0" y1="0" x2="0" y2="630" transform="translate(360,0)"/>
|
||||
<line x1="0" y1="0" x2="0" y2="630" transform="translate(420,0)"/>
|
||||
<line x1="0" y1="0" x2="0" y2="630" transform="translate(480,0)"/>
|
||||
<line x1="0" y1="0" x2="0" y2="630" transform="translate(540,0)"/>
|
||||
<line x1="0" y1="0" x2="0" y2="630" transform="translate(600,0)"/>
|
||||
<line x1="0" y1="0" x2="0" y2="630" transform="translate(660,0)"/>
|
||||
<line x1="0" y1="0" x2="0" y2="630" transform="translate(720,0)"/>
|
||||
<line x1="0" y1="0" x2="0" y2="630" transform="translate(780,0)"/>
|
||||
<line x1="0" y1="0" x2="0" y2="630" transform="translate(840,0)"/>
|
||||
<line x1="0" y1="0" x2="0" y2="630" transform="translate(900,0)"/>
|
||||
<line x1="0" y1="0" x2="0" y2="630" transform="translate(960,0)"/>
|
||||
<line x1="0" y1="0" x2="0" y2="630" transform="translate(1020,0)"/>
|
||||
<line x1="0" y1="0" x2="0" y2="630" transform="translate(1080,0)"/>
|
||||
<line x1="0" y1="0" x2="0" y2="630" transform="translate(1140,0)"/>
|
||||
<line x1="0" y1="0" x2="1200" y2="0" transform="translate(0,60)"/>
|
||||
<line x1="0" y1="0" x2="1200" y2="0" transform="translate(0,120)"/>
|
||||
<line x1="0" y1="0" x2="1200" y2="0" transform="translate(0,180)"/>
|
||||
<line x1="0" y1="0" x2="1200" y2="0" transform="translate(0,240)"/>
|
||||
<line x1="0" y1="0" x2="1200" y2="0" transform="translate(0,300)"/>
|
||||
<line x1="0" y1="0" x2="1200" y2="0" transform="translate(0,360)"/>
|
||||
<line x1="0" y1="0" x2="1200" y2="0" transform="translate(0,420)"/>
|
||||
<line x1="0" y1="0" x2="1200" y2="0" transform="translate(0,480)"/>
|
||||
<line x1="0" y1="0" x2="1200" y2="0" transform="translate(0,540)"/>
|
||||
<line x1="0" y1="0" x2="1200" y2="0" transform="translate(0,600)"/>
|
||||
</g>
|
||||
|
||||
<!-- ========== PANDA (right side) ========== -->
|
||||
<g transform="translate(880, 200)">
|
||||
<!-- Left ear (black) -->
|
||||
<ellipse cx="60" cy="30" rx="52" ry="48" fill="#1a1a2e"/>
|
||||
<ellipse cx="60" cy="30" rx="42" ry="38" fill="#2d2d44"/>
|
||||
<!-- Right ear (black) -->
|
||||
<ellipse cx="220" cy="30" rx="52" ry="48" fill="#1a1a2e"/>
|
||||
<ellipse cx="220" cy="30" rx="42" ry="38" fill="#2d2d44"/>
|
||||
|
||||
<!-- Head (white/light) -->
|
||||
<ellipse cx="140" cy="120" rx="120" ry="110" fill="#e8e8ef"/>
|
||||
<ellipse cx="140" cy="120" rx="120" ry="110" fill="none" stroke="#d0d0dd" stroke-width="1"/>
|
||||
|
||||
<!-- Left eye patch (dark) -->
|
||||
<ellipse cx="90" cy="105" rx="42" ry="35" fill="#1a1a2e" transform="rotate(-10, 90, 105)"/>
|
||||
<!-- Right eye patch (dark) -->
|
||||
<ellipse cx="190" cy="105" rx="42" ry="35" fill="#1a1a2e" transform="rotate(10, 190, 105)"/>
|
||||
|
||||
<!-- Left eye (white + pupil) -->
|
||||
<ellipse cx="92" cy="108" rx="16" ry="18" fill="#ffffff"/>
|
||||
<ellipse cx="95" cy="110" rx="9" ry="10" fill="#1a1a2e"/>
|
||||
<ellipse cx="97" cy="107" rx="3" ry="3.5" fill="#ffffff"/>
|
||||
<!-- Right eye (white + pupil) -->
|
||||
<ellipse cx="188" cy="108" rx="16" ry="18" fill="#ffffff"/>
|
||||
<ellipse cx="185" cy="110" rx="9" ry="10" fill="#1a1a2e"/>
|
||||
<ellipse cx="183" cy="107" rx="3" ry="3.5" fill="#ffffff"/>
|
||||
|
||||
<!-- Nose -->
|
||||
<ellipse cx="140" cy="140" rx="12" ry="8" fill="#2d2d44"/>
|
||||
|
||||
<!-- Mouth -->
|
||||
<path d="M 128 150 Q 134 160 140 152 Q 146 160 152 150" fill="none" stroke="#2d2d44" stroke-width="2.5" stroke-linecap="round"/>
|
||||
|
||||
<!-- Blush (brand color) -->
|
||||
<ellipse cx="72" cy="135" rx="18" ry="11" fill="#dea584" opacity="0.3"/>
|
||||
<ellipse cx="208" cy="135" rx="18" ry="11" fill="#dea584" opacity="0.3"/>
|
||||
|
||||
<!-- Shield body (panda is holding a shield) -->
|
||||
<g transform="translate(85, 175)">
|
||||
<!-- Shield shape -->
|
||||
<path d="M 55 0 L 105 12 L 105 60 Q 105 100 55 115 Q 5 100 5 60 L 5 12 Z" fill="#1e293b" stroke="#dea584" stroke-width="2.5"/>
|
||||
<!-- Lock icon on shield -->
|
||||
<rect x="38" y="42" width="34" height="28" rx="4" fill="#dea584" opacity="0.9"/>
|
||||
<path d="M 45 42 L 45 34 Q 45 22 55 22 Q 65 22 65 34 L 65 42" fill="none" stroke="#dea584" stroke-width="3" stroke-linecap="round" opacity="0.9"/>
|
||||
<circle cx="55" cy="54" r="4" fill="#0f172a"/>
|
||||
<rect x="53" y="56" width="4" height="8" rx="1" fill="#0f172a"/>
|
||||
</g>
|
||||
|
||||
<!-- Paws holding shield -->
|
||||
<ellipse cx="72" cy="210" rx="22" ry="16" fill="#1a1a2e"/>
|
||||
<ellipse cx="208" cy="210" rx="22" ry="16" fill="#1a1a2e"/>
|
||||
</g>
|
||||
|
||||
<!-- ========== TEXT (left side) ========== -->
|
||||
|
||||
<!-- Brand name -->
|
||||
<text x="80" y="175" font-family="monospace, 'Courier New'" font-size="72" font-weight="900" fill="#ffffff" letter-spacing="-2">
|
||||
<tspan fill="#dea584"></></tspan> libsmx
|
||||
</text>
|
||||
|
||||
<!-- Tagline -->
|
||||
<text x="80" y="230" font-family="sans-serif, Arial, Helvetica" font-size="28" fill="#94a3b8" letter-spacing="0.5">
|
||||
Pure-Rust Chinese Cryptography
|
||||
</text>
|
||||
|
||||
<!-- Feature badges -->
|
||||
<!-- Badge: Zero unsafe -->
|
||||
<g transform="translate(80, 270)">
|
||||
<rect width="180" height="40" rx="20" fill="#ef4444" fill-opacity="0.12" stroke="#ef4444" stroke-opacity="0.3" stroke-width="1"/>
|
||||
<text x="90" y="26" font-family="monospace, 'Courier New'" font-size="15" fill="#fca5a5" text-anchor="middle" font-weight="600">Zero unsafe</text>
|
||||
</g>
|
||||
|
||||
<!-- Badge: Constant-time -->
|
||||
<g transform="translate(280, 270)">
|
||||
<rect width="200" height="40" rx="20" fill="#10b981" fill-opacity="0.12" stroke="#10b981" stroke-opacity="0.3" stroke-width="1"/>
|
||||
<text x="100" y="26" font-family="monospace, 'Courier New'" font-size="15" fill="#6ee7b7" text-anchor="middle" font-weight="600">Constant-time</text>
|
||||
</g>
|
||||
|
||||
<!-- Badge: #![no_std] -->
|
||||
<g transform="translate(500, 270)">
|
||||
<rect width="170" height="40" rx="20" fill="#3b82f6" fill-opacity="0.12" stroke="#3b82f6" stroke-opacity="0.3" stroke-width="1"/>
|
||||
<text x="85" y="26" font-family="monospace, 'Courier New'" font-size="15" fill="#93c5fd" text-anchor="middle" font-weight="600">#![no_std]</text>
|
||||
</g>
|
||||
|
||||
<!-- Algorithm list -->
|
||||
<text x="80" y="365" font-family="monospace, 'Courier New'" font-size="20" fill="#64748b">
|
||||
<tspan fill="#dea584">SM2</tspan> · <tspan fill="#dea584">SM3</tspan> · <tspan fill="#dea584">SM4</tspan> · <tspan fill="#dea584">SM9</tspan> · <tspan fill="#dea584">BLS</tspan> · <tspan fill="#dea584">FPE</tspan>
|
||||
</text>
|
||||
|
||||
<!-- Performance highlights -->
|
||||
<g transform="translate(80, 400)">
|
||||
<text font-family="sans-serif, Arial, Helvetica" font-size="16" fill="#64748b">
|
||||
<tspan x="0" dy="0">SM3 <tspan fill="#10b981" font-weight="700">374 MiB/s</tspan></tspan>
|
||||
<tspan x="200" dy="0">SM2 Sign <tspan fill="#10b981" font-weight="700">258 µs</tspan></tspan>
|
||||
<tspan x="430" dy="0">SM4 <tspan fill="#10b981" font-weight="700">27 MiB/s</tspan></tspan>
|
||||
</text>
|
||||
</g>
|
||||
|
||||
<!-- Decorative bottom line -->
|
||||
<rect x="0" y="625" width="1200" height="5" fill="url(#bg)"/>
|
||||
<line x1="0" y1="626" x2="1200" y2="626" stroke="#dea584" stroke-opacity="0.3" stroke-width="1"/>
|
||||
|
||||
<!-- Bottom bar: URL -->
|
||||
<text x="80" y="580" font-family="monospace, 'Courier New'" font-size="16" fill="#475569">
|
||||
github.com/kintaiW/libsmx
|
||||
</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.3 KiB |
@@ -61,3 +61,6 @@ pub mod sm9;
|
||||
pub mod bls;
|
||||
|
||||
pub mod fpe;
|
||||
|
||||
#[cfg(feature = "rustls-provider")]
|
||||
pub mod rustls_provider;
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
//! 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);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rustls::crypto::hash::Hash;
|
||||
|
||||
use super::SM3;
|
||||
|
||||
// GB/T 32905 标准向量:"abc" → SM3 哈希
|
||||
const ABC_DIGEST: &[u8] = &[
|
||||
0x66, 0xc7, 0xf0, 0xf4, 0x62, 0xee, 0xed, 0xd9, 0xd1, 0xf2, 0xd4, 0x6b, 0xdc, 0x10, 0xe4,
|
||||
0xe2, 0x41, 0x67, 0xc4, 0x87, 0x5c, 0xf2, 0xf7, 0xa2, 0x29, 0x7d, 0xa0, 0x2b, 0x8f, 0x4b,
|
||||
0xa8, 0xe0,
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn test_hash_abc() {
|
||||
let out = SM3.hash(b"abc");
|
||||
assert_eq!(out.as_ref(), ABC_DIGEST);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_context_update_finish() {
|
||||
let mut ctx = SM3.start();
|
||||
ctx.update(b"ab");
|
||||
ctx.update(b"c");
|
||||
let out = ctx.finish();
|
||||
assert_eq!(out.as_ref(), ABC_DIGEST);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_context_fork_finish() {
|
||||
let mut ctx = SM3.start();
|
||||
ctx.update(b"abc");
|
||||
// fork_finish 不消耗 ctx,fork 后原 ctx 可继续 finish
|
||||
let out1 = ctx.fork_finish();
|
||||
let out2 = ctx.finish();
|
||||
assert_eq!(out1.as_ref(), ABC_DIGEST);
|
||||
assert_eq!(out2.as_ref(), ABC_DIGEST);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_context_fork() {
|
||||
let mut ctx = SM3.start();
|
||||
ctx.update(b"abc");
|
||||
let forked = ctx.fork();
|
||||
let out1 = forked.finish();
|
||||
let out2 = ctx.finish();
|
||||
assert_eq!(out1.as_ref(), ABC_DIGEST);
|
||||
assert_eq!(out2.as_ref(), ABC_DIGEST);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_output_len() {
|
||||
assert_eq!(SM3.output_len(), 32);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
//! 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
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rustls::crypto::hmac::Hmac;
|
||||
|
||||
use super::HMAC_SM3;
|
||||
|
||||
// RFC 2104 HMAC-SM3 已知正确值(用 libsmx hmac_sm3 预计算)
|
||||
// key = b"key", data = b"The quick brown fox jumps over the lazy dog"
|
||||
// 通过 crate::sm3::hmac_sm3 验证
|
||||
fn reference_hmac(key: &[u8], data: &[u8]) -> [u8; 32] {
|
||||
crate::sm3::hmac_sm3(key, data)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sign_concat_single_chunk() {
|
||||
let key = b"secret";
|
||||
let data = b"hello world";
|
||||
let k = HMAC_SM3.with_key(key);
|
||||
let tag = k.sign_concat(data, &[], &[]);
|
||||
assert_eq!(tag.as_ref(), reference_hmac(key, data));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sign_concat_split_matches_whole() {
|
||||
// 分段 "hel" + ["lo "] + "world" 应与整体 "hello world" 结果相同
|
||||
let key = b"secret";
|
||||
let k = HMAC_SM3.with_key(key);
|
||||
let tag = k.sign_concat(b"hel", &[b"lo "], b"world");
|
||||
assert_eq!(tag.as_ref(), reference_hmac(key, b"hello world"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tag_len() {
|
||||
let k = HMAC_SM3.with_key(b"k");
|
||||
assert_eq!(k.tag_len(), 32);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
//! 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()),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rustls::crypto::kx::NamedGroup;
|
||||
|
||||
use super::CURVE_SM2;
|
||||
|
||||
#[test]
|
||||
fn test_name() {
|
||||
assert_eq!(CURVE_SM2.name(), NamedGroup::curveSM2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ecdhe_roundtrip() {
|
||||
// 模拟 TLS 握手:A 生成临时密钥对,B 生成临时密钥对,双方计算共享密钥
|
||||
let kx_a = CURVE_SM2.start().unwrap().into_single();
|
||||
let kx_b = CURVE_SM2.start().unwrap().into_single();
|
||||
|
||||
let pub_a = kx_a.pub_key().to_vec();
|
||||
let pub_b = kx_b.pub_key().to_vec();
|
||||
|
||||
// 65 字节非压缩公钥(04 || x || y)
|
||||
assert_eq!(pub_a.len(), 65);
|
||||
assert_eq!(pub_b.len(), 65);
|
||||
assert_eq!(pub_a[0], 0x04);
|
||||
|
||||
let secret_a = kx_a.complete(&pub_b).unwrap();
|
||||
let secret_b = kx_b.complete(&pub_a).unwrap();
|
||||
|
||||
// 两端共享密钥必须相同(32 字节 x 坐标)
|
||||
assert_eq!(secret_a.secret_bytes(), secret_b.secret_bytes());
|
||||
assert_eq!(secret_a.secret_bytes().len(), 32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_peer_key_rejected() {
|
||||
let kx = CURVE_SM2.start().unwrap().into_single();
|
||||
// 无效公钥(全零)应报错
|
||||
let result = kx.complete(&[0u8; 65]);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_no_ffdhe_group() {
|
||||
assert!(CURVE_SM2.ffdhe_group().is_none());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
//! 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,165 @@
|
||||
//! 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
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::boxed::Box;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use rustls::crypto::SignatureScheme;
|
||||
|
||||
use super::load_private_key;
|
||||
use crate::sm2::{der::sig_from_der, generate_keypair, verify_message, PrivateKey, DEFAULT_ID};
|
||||
|
||||
/// 将 SM2 私钥编码为最小 SEC1 DER(无可选字段)
|
||||
/// ECPrivateKey ::= SEQUENCE { version INTEGER(1), privateKey OCTET STRING(32B) }
|
||||
fn encode_sec1_der(pri_key: &PrivateKey) -> Vec<u8> {
|
||||
let key_bytes = pri_key.as_bytes();
|
||||
// SEQUENCE body = INTEGER(1)[3B] + OCTET STRING(32B)[34B] = 37 字节
|
||||
let mut der = Vec::with_capacity(39);
|
||||
der.extend_from_slice(&[
|
||||
0x30, 0x25, // SEQUENCE, length 37
|
||||
0x02, 0x01, 0x01, // INTEGER(1) = version
|
||||
0x04, 0x20, // OCTET STRING, length 32
|
||||
]);
|
||||
der.extend_from_slice(key_bytes);
|
||||
der
|
||||
}
|
||||
|
||||
fn make_test_key_der() -> (PrivateKey, [u8; 65], pki_types::PrivateKeyDer<'static>) {
|
||||
let mut rng = super::super::kx::Sm2Rng;
|
||||
let (pri_key, pub_key) = generate_keypair(&mut rng);
|
||||
let sec1 = encode_sec1_der(&pri_key);
|
||||
let key_der = pki_types::PrivateKeyDer::Sec1(pki_types::PrivateSec1KeyDer::from(sec1));
|
||||
(pri_key, pub_key, key_der)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_choose_scheme_sm2_sm3() {
|
||||
let (_, _, key_der) = make_test_key_der();
|
||||
let signing_key = load_private_key(key_der).unwrap();
|
||||
let signer = signing_key.choose_scheme(&[SignatureScheme::SM2_SM3]);
|
||||
assert!(signer.is_some());
|
||||
assert_eq!(signer.unwrap().scheme(), SignatureScheme::SM2_SM3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_choose_scheme_unmatched_returns_none() {
|
||||
let (_, _, key_der) = make_test_key_der();
|
||||
let signing_key = load_private_key(key_der).unwrap();
|
||||
// 不含 SM2_SM3 时应返回 None
|
||||
let signer = signing_key.choose_scheme(&[SignatureScheme::ECDSA_NISTP256_SHA256]);
|
||||
assert!(signer.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_public_key_is_spki() {
|
||||
let (_, _, key_der) = make_test_key_der();
|
||||
let signing_key = load_private_key(key_der).unwrap();
|
||||
let spki = signing_key.public_key();
|
||||
assert!(spki.is_some());
|
||||
// SPKI DER 以 SEQUENCE (0x30) 开头
|
||||
let spki_bytes: &[u8] = spki.as_ref().unwrap().as_ref();
|
||||
assert_eq!(spki_bytes[0], 0x30);
|
||||
// 完整 SM2 SPKI:alg(21B) + BIT STRING(68B) = 89B,外层 SEQUENCE header(2B) = 91B
|
||||
assert_eq!(spki_bytes.len(), 91);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sign_produces_valid_der_signature() {
|
||||
let (_, pub_key, key_der) = make_test_key_der();
|
||||
let signing_key = load_private_key(key_der).unwrap();
|
||||
|
||||
let signer = signing_key
|
||||
.choose_scheme(&[SignatureScheme::SM2_SM3])
|
||||
.unwrap();
|
||||
let message = b"hello SM2";
|
||||
let sig_der = Box::new(signer).sign(message).unwrap();
|
||||
|
||||
// 解析 DER 签名并用底层 API 验签
|
||||
let sig_raw = sig_from_der(&sig_der).unwrap();
|
||||
verify_message(message, DEFAULT_ID, &pub_key, &sig_raw).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -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,113 @@
|
||||
//! SM2 验签 → rustls `SignatureVerificationAlgorithm`
|
||||
|
||||
use pki_types::{AlgorithmIdentifier, SignatureVerificationAlgorithm};
|
||||
use rustls::crypto::{SignatureScheme, WebPkiSupportedAlgorithms};
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use pki_types::SignatureVerificationAlgorithm;
|
||||
|
||||
use super::SM2_SM3_ALG;
|
||||
use crate::sm2::{der::sig_to_der, generate_keypair, sign_message, DEFAULT_ID};
|
||||
|
||||
fn sign_with_der(message: &[u8]) -> ([u8; 65], Vec<u8>) {
|
||||
let mut rng = crate::rustls_provider::kx::Sm2Rng;
|
||||
let (pri_key, pub_key) = generate_keypair(&mut rng);
|
||||
let sig_raw = sign_message(message, DEFAULT_ID, &pri_key, &mut rng);
|
||||
(pub_key, sig_to_der(&sig_raw))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_verify_valid_signature() {
|
||||
let msg = b"test message";
|
||||
let (pub_key, sig_der) = sign_with_der(msg);
|
||||
SM2_SM3_ALG
|
||||
.verify_signature(&pub_key, msg, &sig_der)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_verify_wrong_message_fails() {
|
||||
let (pub_key, sig_der) = sign_with_der(b"original");
|
||||
let result = SM2_SM3_ALG.verify_signature(&pub_key, b"tampered", &sig_der);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_verify_wrong_pubkey_fails() {
|
||||
let msg = b"hello";
|
||||
let (_, sig_der) = sign_with_der(msg);
|
||||
// 用另一个公钥验证
|
||||
let mut rng = crate::rustls_provider::kx::Sm2Rng;
|
||||
let (_, other_pub) = generate_keypair(&mut rng);
|
||||
let result = SM2_SM3_ALG.verify_signature(&other_pub, msg, &sig_der);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_verify_invalid_pubkey_len_fails() {
|
||||
let (_, sig_der) = sign_with_der(b"msg");
|
||||
// 公钥长度不是 65 字节时应报错
|
||||
let result = SM2_SM3_ALG.verify_signature(&[0u8; 32], b"msg", &sig_der);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
@@ -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,6 +219,76 @@ 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 {
|
||||
use super::*;
|
||||
@@ -321,6 +391,22 @@ 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 expected = hmac_sm3(key, b"hello world");
|
||||
|
||||
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];
|
||||
|
||||
@@ -0,0 +1,333 @@
|
||||
//! rustls CryptoProvider 端到端集成测试(阶段 L.2)
|
||||
//!
|
||||
//! 使用 Raw Public Key(RFC 7250)模式,无需 X.509 证书,
|
||||
//! 直接用 SM2 密钥对作为身份凭证完成 TLS 1.3 握手自回环测试。
|
||||
|
||||
#![cfg(feature = "rustls-provider")]
|
||||
|
||||
use std::io::{Read, Write};
|
||||
use std::sync::Arc;
|
||||
|
||||
use pki_types::{PrivateSec1KeyDer, ServerName, SubjectPublicKeyInfoDer};
|
||||
use rustls::client::danger::{
|
||||
HandshakeSignatureValid, PeerVerified, ServerIdentity, ServerVerifier,
|
||||
};
|
||||
use rustls::crypto::{CipherSuite, CryptoProvider, SignatureScheme};
|
||||
use rustls::enums::CertificateType;
|
||||
use rustls::server::danger::SignatureVerificationInput;
|
||||
use rustls::{ClientConfig, ClientConnection, Connection, ServerConfig, ServerConnection};
|
||||
|
||||
use libsmx::rustls_provider;
|
||||
use libsmx::sm2::{
|
||||
der::{public_key_to_spki_der, sig_from_der},
|
||||
generate_keypair, verify_message, DEFAULT_ID,
|
||||
};
|
||||
|
||||
// ── 测试用 RNG ────────────────────────────────────────────────────────────────
|
||||
|
||||
struct TestRng;
|
||||
|
||||
impl rand_core::RngCore for TestRng {
|
||||
fn next_u32(&mut self) -> u32 {
|
||||
let mut b = [0u8; 4];
|
||||
getrandom::getrandom(&mut b).unwrap();
|
||||
u32::from_le_bytes(b)
|
||||
}
|
||||
fn next_u64(&mut self) -> u64 {
|
||||
let mut b = [0u8; 8];
|
||||
getrandom::getrandom(&mut b).unwrap();
|
||||
u64::from_le_bytes(b)
|
||||
}
|
||||
fn fill_bytes(&mut self, dest: &mut [u8]) {
|
||||
getrandom::getrandom(dest).unwrap();
|
||||
}
|
||||
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> {
|
||||
getrandom::getrandom(dest).map_err(|_| {
|
||||
use core::num::NonZeroU32;
|
||||
rand_core::Error::from(NonZeroU32::new(1).unwrap())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ── 测试工具 ──────────────────────────────────────────────────────────────────
|
||||
|
||||
/// 生成 SM2 密钥对,返回 (SEC1 DER 私钥, SPKI DER 公钥)
|
||||
fn make_sm2_keypair() -> (Vec<u8>, Vec<u8>) {
|
||||
let mut rng = TestRng;
|
||||
let (pri_key, pub_key) = generate_keypair(&mut rng);
|
||||
|
||||
// 最小 SEC1 DER: SEQUENCE { INTEGER(1), OCTET STRING(32B) }
|
||||
let mut sec1 = Vec::with_capacity(39);
|
||||
sec1.extend_from_slice(&[
|
||||
0x30, 0x25, // SEQUENCE, length 37
|
||||
0x02, 0x01, 0x01, // INTEGER(1) version
|
||||
0x04, 0x20, // OCTET STRING, length 32
|
||||
]);
|
||||
sec1.extend_from_slice(pri_key.as_bytes());
|
||||
|
||||
let spki = public_key_to_spki_der(&pub_key);
|
||||
(sec1, spki)
|
||||
}
|
||||
|
||||
/// 在内存中传输 TLS 记录(left → right)
|
||||
fn transfer(left: &mut impl Connection, right: &mut impl Connection) -> usize {
|
||||
let mut buf = [0u8; 65536];
|
||||
let mut total = 0;
|
||||
while left.wants_write() {
|
||||
let n = left.write_tls(&mut &mut buf[..]).unwrap();
|
||||
if n == 0 {
|
||||
break;
|
||||
}
|
||||
total += n;
|
||||
let mut offs = 0;
|
||||
while offs < n {
|
||||
offs += right.read_tls(&mut &buf[offs..n]).unwrap();
|
||||
}
|
||||
}
|
||||
total
|
||||
}
|
||||
|
||||
/// 驱动完整 TLS 握手
|
||||
fn do_handshake(client: &mut ClientConnection, server: &mut ServerConnection) {
|
||||
while client.is_handshaking() || server.is_handshaking() {
|
||||
transfer(client, server);
|
||||
server.process_new_packets().unwrap();
|
||||
transfer(server, client);
|
||||
client.process_new_packets().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// ── 自定义 ServerVerifier(测试用,接受任意 SM2 Raw Public Key)──────────────
|
||||
|
||||
#[derive(Debug)]
|
||||
struct AcceptAllSm2ServerVerifier;
|
||||
|
||||
impl AcceptAllSm2ServerVerifier {
|
||||
fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl ServerVerifier for AcceptAllSm2ServerVerifier {
|
||||
fn verify_identity(
|
||||
&self,
|
||||
_identity: &ServerIdentity<'_>,
|
||||
) -> Result<PeerVerified, rustls::Error> {
|
||||
// Reason: 测试中不验证服务端身份(Raw Public Key 模式,无 CA 信任链)
|
||||
Ok(PeerVerified::assertion())
|
||||
}
|
||||
|
||||
fn verify_tls12_signature(
|
||||
&self,
|
||||
_input: &SignatureVerificationInput<'_>,
|
||||
) -> Result<HandshakeSignatureValid, rustls::Error> {
|
||||
Err(rustls::Error::General("TLS 1.2 not supported".into()))
|
||||
}
|
||||
|
||||
fn verify_tls13_signature(
|
||||
&self,
|
||||
input: &SignatureVerificationInput<'_>,
|
||||
) -> Result<HandshakeSignatureValid, rustls::Error> {
|
||||
// Reason: webpki 不支持 SM2 OID,直接调用 libsmx SM2 验签实现,
|
||||
// 绕过 webpki 的 OID 匹配逻辑
|
||||
use rustls::SignerPublicKey;
|
||||
let spki_der: &[u8] = match input.signer {
|
||||
SignerPublicKey::RawPublicKey(spki) => spki.as_ref(),
|
||||
_ => return Err(rustls::Error::General("expected Raw Public Key".into())),
|
||||
};
|
||||
// SM2 SPKI 结构(91字节):
|
||||
// SEQUENCE(2B) + AlgId SEQUENCE(21B) + BIT STRING tag+len+pad(3B) + 公钥(65B)
|
||||
// 公钥从偏移 26 开始,共 65 字节
|
||||
if spki_der.len() != 91 {
|
||||
return Err(rustls::Error::General(format!(
|
||||
"unexpected SPKI length: {}",
|
||||
spki_der.len()
|
||||
)));
|
||||
}
|
||||
let pub_key_bytes: &[u8; 65] = spki_der[26..91]
|
||||
.try_into()
|
||||
.map_err(|_| rustls::Error::General("bad pubkey slice".into()))?;
|
||||
|
||||
let sig_der = input.signature.signature();
|
||||
let sig_raw = sig_from_der(sig_der)
|
||||
.map_err(|_| rustls::Error::General("invalid DER signature".into()))?;
|
||||
|
||||
verify_message(input.message, DEFAULT_ID, pub_key_bytes, &sig_raw)
|
||||
.map(|_| HandshakeSignatureValid::assertion())
|
||||
.map_err(|_| rustls::Error::General("SM2 signature verification failed".into()))
|
||||
}
|
||||
|
||||
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
|
||||
vec![SignatureScheme::SM2_SM3]
|
||||
}
|
||||
|
||||
fn request_ocsp_response(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn supported_certificate_types(&self) -> &'static [CertificateType] {
|
||||
&[CertificateType::RawPublicKey]
|
||||
}
|
||||
|
||||
fn hash_config(&self, _: &mut dyn core::hash::Hasher) {}
|
||||
}
|
||||
|
||||
// ── 辅助:用 Raw Public Key 模式构建 server/client Config ────────────────────
|
||||
|
||||
fn make_configs(
|
||||
provider: &CryptoProvider,
|
||||
server_sec1: Vec<u8>,
|
||||
server_spki: Vec<u8>,
|
||||
) -> (ServerConfig, ClientConfig) {
|
||||
// 服务端:用 new_unchecked 绕过 webpki 对 SM2 OID 的校验
|
||||
let server_key_der = pki_types::PrivateKeyDer::Sec1(PrivateSec1KeyDer::from(server_sec1));
|
||||
let server_signing_key = provider
|
||||
.key_provider
|
||||
.load_private_key(server_key_der)
|
||||
.unwrap();
|
||||
let identity = Arc::new(rustls::crypto::Identity::RawPublicKey(
|
||||
SubjectPublicKeyInfoDer::from(server_spki),
|
||||
));
|
||||
// Reason: Credentials::new_unchecked 跳过 webpki 验证,因为 SM2 OID 尚未被
|
||||
// rustls-webpki 支持,但我们的 SignatureVerificationAlgorithm 实现是正确的
|
||||
let creds = rustls::crypto::Credentials::new_unchecked(identity, server_signing_key);
|
||||
let cert_resolver = Arc::new(rustls::crypto::SingleCredential::from(creds));
|
||||
let server_config = ServerConfig::builder(provider.clone().into())
|
||||
.with_no_client_auth()
|
||||
.with_server_credential_resolver(cert_resolver)
|
||||
.unwrap();
|
||||
|
||||
// 客户端:自定义 verifier 跳过证书链校验
|
||||
let verifier = Arc::new(AcceptAllSm2ServerVerifier::new());
|
||||
let client_config = ClientConfig::builder(provider.clone().into())
|
||||
.dangerous()
|
||||
.with_custom_certificate_verifier(verifier)
|
||||
.with_no_client_auth()
|
||||
.unwrap();
|
||||
|
||||
(server_config, client_config)
|
||||
}
|
||||
|
||||
// ── 测试用例 ──────────────────────────────────────────────────────────────────
|
||||
|
||||
/// TLS 1.3 SM4-GCM-SM3 握手自回环(Raw Public Key 模式)
|
||||
#[test]
|
||||
fn test_tls13_sm4_gcm_sm3_handshake() {
|
||||
let provider = rustls_provider::provider();
|
||||
let (server_sec1, server_spki) = make_sm2_keypair();
|
||||
let (server_config, client_config) = make_configs(&provider, server_sec1, server_spki);
|
||||
|
||||
let server_name = ServerName::try_from("localhost").unwrap();
|
||||
let client_arc = Arc::new(client_config);
|
||||
let mut client = client_arc.connect(server_name).build().unwrap();
|
||||
let mut server = ServerConnection::new(Arc::new(server_config)).unwrap();
|
||||
|
||||
do_handshake(&mut client, &mut server);
|
||||
|
||||
assert_eq!(
|
||||
client.negotiated_cipher_suite().unwrap().suite(),
|
||||
CipherSuite::TLS13_SM4_GCM_SM3,
|
||||
);
|
||||
|
||||
// server → client 数据传输
|
||||
let plaintext = b"Hello, SM2/SM4/SM3!";
|
||||
server.writer().write_all(plaintext).unwrap();
|
||||
transfer(&mut server, &mut client);
|
||||
client.process_new_packets().unwrap();
|
||||
let mut received = vec![0u8; plaintext.len()];
|
||||
client.reader().read_exact(&mut received).unwrap();
|
||||
assert_eq!(received, plaintext.to_vec());
|
||||
}
|
||||
|
||||
/// TLS 1.3 SM4-CCM-SM3 握手自回环
|
||||
#[test]
|
||||
fn test_tls13_sm4_ccm_sm3_handshake() {
|
||||
use std::borrow::Cow;
|
||||
|
||||
static CCM_SUITES: &[&rustls::Tls13CipherSuite] =
|
||||
&[libsmx::rustls_provider::tls13::TLS13_SM4_CCM_SM3];
|
||||
|
||||
let provider = CryptoProvider {
|
||||
tls13_cipher_suites: Cow::Borrowed(CCM_SUITES),
|
||||
..rustls_provider::provider()
|
||||
};
|
||||
|
||||
let (server_sec1, server_spki) = make_sm2_keypair();
|
||||
let (server_config, client_config) = make_configs(&provider, server_sec1, server_spki);
|
||||
|
||||
let server_name = ServerName::try_from("localhost").unwrap();
|
||||
let client_arc = Arc::new(client_config);
|
||||
let mut client = client_arc.connect(server_name).build().unwrap();
|
||||
let mut server = ServerConnection::new(Arc::new(server_config)).unwrap();
|
||||
|
||||
do_handshake(&mut client, &mut server);
|
||||
|
||||
assert_eq!(
|
||||
client.negotiated_cipher_suite().unwrap().suite(),
|
||||
CipherSuite::TLS13_SM4_CCM_SM3,
|
||||
);
|
||||
|
||||
// client → server 数据传输
|
||||
let msg = b"SM4-CCM test";
|
||||
client.writer().write_all(msg).unwrap();
|
||||
transfer(&mut client, &mut server);
|
||||
server.process_new_packets().unwrap();
|
||||
let mut buf = vec![0u8; msg.len()];
|
||||
server.reader().read_exact(&mut buf).unwrap();
|
||||
assert_eq!(buf, msg.to_vec());
|
||||
}
|
||||
|
||||
/// 验证握手后密钥交换组为 curveSM2
|
||||
#[test]
|
||||
fn test_kx_group_is_curve_sm2() {
|
||||
let provider = rustls_provider::provider();
|
||||
let (server_sec1, server_spki) = make_sm2_keypair();
|
||||
let (server_config, client_config) = make_configs(&provider, server_sec1, server_spki);
|
||||
|
||||
let server_name = ServerName::try_from("localhost").unwrap();
|
||||
let client_arc = Arc::new(client_config);
|
||||
let mut client = client_arc.connect(server_name).build().unwrap();
|
||||
let mut server = ServerConnection::new(Arc::new(server_config)).unwrap();
|
||||
|
||||
do_handshake(&mut client, &mut server);
|
||||
|
||||
assert_eq!(
|
||||
client
|
||||
.negotiated_key_exchange_group()
|
||||
.map(|g: &dyn rustls::crypto::kx::SupportedKxGroup| g.name()),
|
||||
Some(rustls::crypto::kx::NamedGroup::curveSM2),
|
||||
);
|
||||
}
|
||||
|
||||
/// 双向数据传输(client ↔ server 各发一条消息)
|
||||
#[test]
|
||||
fn test_bidirectional_data_transfer() {
|
||||
let provider = rustls_provider::provider();
|
||||
let (server_sec1, server_spki) = make_sm2_keypair();
|
||||
let (server_config, client_config) = make_configs(&provider, server_sec1, server_spki);
|
||||
|
||||
let server_name = ServerName::try_from("localhost").unwrap();
|
||||
let client_arc = Arc::new(client_config);
|
||||
let mut client = client_arc.connect(server_name).build().unwrap();
|
||||
let mut server = ServerConnection::new(Arc::new(server_config)).unwrap();
|
||||
|
||||
do_handshake(&mut client, &mut server);
|
||||
|
||||
// client → server
|
||||
let c2s = b"from client";
|
||||
client.writer().write_all(c2s).unwrap();
|
||||
transfer(&mut client, &mut server);
|
||||
server.process_new_packets().unwrap();
|
||||
let mut buf = vec![0u8; c2s.len()];
|
||||
server.reader().read_exact(&mut buf).unwrap();
|
||||
assert_eq!(buf, c2s.to_vec());
|
||||
|
||||
// server → client
|
||||
let s2c = b"from server";
|
||||
server.writer().write_all(s2c).unwrap();
|
||||
transfer(&mut server, &mut client);
|
||||
client.process_new_packets().unwrap();
|
||||
let mut buf2 = vec![0u8; s2c.len()];
|
||||
client.reader().read_exact(&mut buf2).unwrap();
|
||||
assert_eq!(buf2, s2c.to_vec());
|
||||
}
|
||||
Reference in New Issue
Block a user