Skip to main content

gmcrypto_core/
lib.rs

1//! Constant-time-designed pure-Rust SM2 / SM3 / SM4 primitives.
2//!
3//! `no_std` + `alloc`, no C dependency, MSRV 1.85. Every secret-touching path
4//! is written against [`subtle`](https://docs.rs/subtle)'s constant-time
5//! primitives — no `==`, no `if`, no `bool` on a secret-derived value — and
6//! guarded in CI by a [`dudect`](https://docs.rs/dudect-bencher)-based
7//! detectable-leak regression harness. Failure modes are deliberately
8//! indistinguishable: fallible operations return one opaque [`Error`] or
9//! `None`, never a reason.
10//!
11//! **This crate has not been independently audited.** Assurance is internal
12//! (KAT vectors, gmssl interop, the timing harness, a `cargo-fuzz` suite) and
13//! the project is solo-maintained with no support SLA. Read
14//! [`SECURITY.md`](https://github.com/frankxue831/gm-crypto-rs/blob/main/SECURITY.md)
15//! for the threat model and disclosure process, and the
16//! [`README`](https://github.com/frankxue831/gm-crypto-rs#readme) for scope,
17//! before relying on it.
18//!
19//! # Usage
20//!
21//! ```toml
22//! [dependencies]
23//! gmcrypto-core = "1.11"
24//! ```
25//!
26//! `default = []` — the base build is the primitives below with no optional
27//! dependency. Most of what this crate can do is opt-in; see
28//! [Crate features](#crate-features).
29//!
30//! ```rust
31//! use gmcrypto_core::sm2::{DEFAULT_SIGNER_ID, Sm2PrivateKey, sign_with_id, verify_with_id};
32//! use gmcrypto_core::{sm3, sm4};
33//! use getrandom::SysRng; // any `rand_core::TryCryptoRng`; this crate ships no RNG
34//!
35//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
36//! # let secret_32 = [0x42u8; 32];
37//! // SM3 (GB/T 32905): 32-byte digest.
38//! let digest = sm3::hash(b"hello");
39//!
40//! // SM2 (GB/T 32918): sign and verify under the GB/T default signer ID.
41//! let key = Sm2PrivateKey::from_bytes_be(&secret_32)   // your scalar, big-endian
42//!     .into_option().ok_or("scalar out of range")?;
43//! let sig = sign_with_id(&key, DEFAULT_SIGNER_ID, b"hello", &mut SysRng)?;
44//! assert!(verify_with_id(&key.public_key(), DEFAULT_SIGNER_ID, b"hello", &sig));
45//!
46//! // SM4-CBC (GB/T 32907), PKCS#7 padded. The IV is caller-supplied and must
47//! // be unpredictable per message; a fixed one here only because it is an example.
48//! let key = [0x42u8; sm4::KEY_SIZE];
49//! let iv = [0x24u8; sm4::BLOCK_SIZE];
50//! let ciphertext = sm4::mode_cbc::encrypt(&key, &iv, b"hello world");
51//! let recovered = sm4::mode_cbc::decrypt(&key, &iv, &ciphertext)
52//!     .ok_or("bad padding or length — one opaque None either way")?;
53//! assert_eq!(recovered, b"hello world");
54//! # Ok(()) }
55//! ```
56//!
57//! Signing and public-key encryption take any `rand_core::TryCryptoRng`; an
58//! RNG failure surfaces as the same opaque [`Error`] as any other failure.
59//!
60//! # Modules
61//!
62//! | | |
63//! |---|---|
64//! | [`sm2`] | SM2 sign / verify, encrypt / decrypt (GB/T 32918). With `sm2-key-exchange`: `sm2::key_exchange`, GM/T 0003.3 key agreement |
65//! | [`sm3`] | SM3 hash (GB/T 32905), single-shot and streaming |
66//! | [`sm4`] | SM4 block cipher (GB/T 32907) with ECB / CBC / CTR. With `sm4-aead`: GCM, CCM, incremental GCM, length-committed streaming CCM. With `sm4-xts`: XTS |
67//! | [`hmac`] | HMAC-SM3 (RFC 2104), single-shot and streaming |
68//! | [`kdf`] | PBKDF2-HMAC-SM3 (RFC 8018 §5.2) |
69//! | [`asn1`] | Strict-canonical DER for the two SM2 wire structures: RFC 3279 signatures, GM/T 0009 ciphertexts |
70//! | [`pem`], [`spki`], [`sec1`], [`pkcs8`] | RFC 7468 PEM, RFC 5280 `SubjectPublicKeyInfo`, RFC 5915 `ECPrivateKey`, RFC 5958 `OneAsymmetricKey` with PBES2 encryption |
71//! | `x509` | With `x509`: X.509-with-SM2 leaf parse and signature verify, linear chain verify. **No trust decisions** beyond structure |
72//! | `tlcp` | With `tlcp`: TLCP (GB/T 38636-2020) key schedule, record protection, and — with `x509` — `[sign, enc]` pair verification. **Not a protocol implementation** |
73//!
74//! # Crate features
75//!
76//! `default = []`: `no_std` + `alloc`, no optional dependency. Every feature
77//! is additive and opt-in. Items behind a feature are badged with it on
78//! docs.rs.
79//!
80//! **Capability features** — pure-core, no new dependency unless stated:
81//!
82//! - `sm4-aead` — SM4-GCM and SM4-CCM (`sm4::mode_gcm`, `sm4::mode_ccm`),
83//!   plus incremental-input GCM (`sm4::gcm_streaming`) and length-committed
84//!   streaming CCM (`sm4::ccm_streaming`). Pulls the
85//!   workspace-internal `gmcrypto-simd` for GHASH (CLMUL / PMULL, with a
86//!   constant-time software fallback).
87//! - `sm4-xts` — SM4-XTS sector mode (`sm4::mode_xts`), per GB/T 17964-2021:
88//!   bit-reflected α-doubling, **not** IEEE 1619. Single-shot and in-place
89//!   multi-sector. Confidentiality only — XTS does not authenticate.
90//! - `sm2-key-exchange` — GM/T 0003.3 key agreement (`sm2::key_exchange`):
91//!   consume-on-transition role state machines, single-use ephemerals, key
92//!   released only after the peer's confirmation tag verifies. The
93//!   standard-permitted no-confirmation completers are also provided for
94//!   protocols — TLCP among them — that carry confirmation themselves.
95//! - `x509` — X.509-with-SM2 certificate parse and signature verify (GM/T 0015
96//!   profile), strict DER, v3 only. Public inputs only, so no constant-time
97//!   obligation arises. Structural trust only — see the module docs.
98//! - `tlcp` — the TLCP (GB/T 38636-2020) crypto toolkit: `P_SM3` key
99//!   schedule, SM4-CBC and SM4-GCM record protection with a Lucky13-hardened
100//!   CBC deprotect, and with `x509` the `[sign, enc]` double-certificate pair
101//!   check. No handshake state machine, framing or I/O.
102//!
103//! **Implementation features** — byte-identical output, different code path:
104//!
105//! - `sm4-bitsliced` — routes the SM4 S-box through a table-less, gate-only
106//!   bitsliced inversion in GF(2^8). Constant-time by construction: no table
107//!   lookups, no branches on secret bits. The default path is a linear scan
108//!   with the same property; this one is faster under SIMD.
109//! - `sm4-bitsliced-simd` — packs that bitsliced S-box into AVX2 (`x86_64`) or
110//!   NEON (aarch64) lanes for the batch paths, with runtime detection and a
111//!   scalar fallback. Implies `sm4-bitsliced`; pulls `gmcrypto-simd`, where
112//!   the crate's only `unsafe` lives.
113//!
114//! **Ecosystem trait fits** — each pulls one pre-1.0 `RustCrypto` crate, so a
115//! breaking release of *that* crate is not covered by this crate's `SemVer`:
116//!
117//! - `digest-traits` — `digest::Digest` for [`sm3::Sm3`], `digest::Mac` for
118//!   [`hmac::HmacSm3`] (`digest = "0.11"`).
119//! - `cipher-traits` — `cipher::{BlockCipherEncrypt, BlockCipherDecrypt,
120//!   KeyInit}` for [`sm4::Sm4Cipher`] (`cipher = "0.5"`).
121//! - `aead-traits` — `aead::{AeadCore, AeadInOut, KeyInit}` for `sm4::Sm4Gcm`
122//!   and `sm4::Sm4Ccm`, which yields the `Vec`-returning `aead::Aead` through
123//!   that crate's blanket impl. Thin wrappers over `mode_gcm` / `mode_ccm`;
124//!   every failure becomes the one opaque `aead::Error`. Implies `sm4-aead`
125//!   (`aead = "0.6"`).
126//! - `crypto-bigint-scalar` — [`sm2::Sm2PrivateKey::from_scalar`], taking a
127//!   `crypto_bigint::U256` directly. The always-on `from_bytes_be` is the
128//!   recommended constructor; this exists for callers who already hold the
129//!   scalar as that type and accept `crypto-bigint`'s major-version contract.
130//!
131//! # `wasm32-unknown-unknown`
132//!
133//! Builds on the target, gated in CI at stable and MSRV. The crate does not
134//! pull `getrandom`'s `wasm_js` backend or `wasm-bindgen` into its default
135//! graph; wasm callers enable `wasm_js` in their own `Cargo.toml` and pass
136//! `getrandom::SysRng` — or any other `rand_core::TryCryptoRng` — to the SM2
137//! operations that need randomness.
138//!
139//! # Release notes
140//!
141//! Every published version is in
142//! [`CHANGELOG.md`](https://github.com/frankxue831/gm-crypto-rs/blob/main/CHANGELOG.md).
143//! The project is at 1.x and additive since 1.0.0; `cargo-semver-checks` gates
144//! breaking changes in CI, and the three workspace crates release together at
145//! one version.
146
147#![no_std]
148// v1.11.2 — "Available on crate feature `x` only" badges on docs.rs. The
149// `docsrs` cfg is set ONLY by `[package.metadata.docs.rs] rustdoc-args`, so
150// this is inert on stable and the `-D warnings` stable `cargo doc` gate in
151// api-stability.yml is unaffected. `doc_auto_cfg` was REMOVED in 1.92 and
152// merged into `doc_cfg` (rust-lang/rust#138907); under the merged feature the
153// auto-labelling is on by default, so no per-item `doc(cfg(...))` is needed —
154// 101 badges render from this one line, incl. compound gates like
155// `tlcp` + `x509`. Still an unstable feature: if a future nightly moves it
156// again, the docs.rs build for the affected version fails and the fix is a
157// republish.
158#![cfg_attr(docsrs, feature(doc_cfg))]
159#![deny(missing_docs)]
160
161extern crate alloc;
162
163pub mod asn1;
164pub mod hmac;
165pub mod kdf;
166pub mod pem;
167pub mod pkcs8;
168pub mod sec1;
169pub mod sm2;
170pub mod sm3;
171pub mod sm4;
172pub mod spki;
173// v1.3 — X.509-with-SM2 leaf certificate parse + signature verify. Opt-in
174// via the `x509` feature; default builds are byte-identical. NO trust
175// decisions. See docs/v1.3-x509-sm2-design.md.
176#[cfg(feature = "x509")]
177pub mod x509;
178
179// v1.6 — TLCP (GB/T 38636-2020) crypto toolkit. Key schedule only so
180// far; the toolkit grows per docs/tlcp-decomposition.md §7. Default
181// builds are byte-identical.
182#[cfg(feature = "tlcp")]
183pub mod tlcp;
184// Not public API / not SemVer — low-level in-crate trait surface kept pub for internal cross-module + dev-crate use; the public trait fit is the opt-in RustCrypto digest/cipher impls.
185#[doc(hidden)]
186pub mod traits;
187
188/// Internal helper: canonical 32-byte big-endian encoding of a `U256`.
189///
190/// `crypto-bigint`'s `Encoding::to_be_bytes` returns an `EncodedUint`
191/// wrapper, not a `[u8; 32]`. v0.22 reshaped the byte-adjacent public
192/// types (`asn1::sig` signatures, `asn1::ciphertext::Sm2Ciphertext`) to
193/// `[u8; 32]` so the public API names no `crypto-bigint` type; this pins
194/// the conversion in one place for the internal producers (sign / encrypt /
195/// raw-ciphertext). Not part of the public API.
196#[inline]
197pub(crate) fn u256_to_be32(v: &crypto_bigint::U256) -> [u8; 32] {
198    v.to_be_bytes().into()
199}
200
201/// Workspace-wide failure type.
202///
203/// Every fallible public surface in `gmcrypto-core` that does not
204/// return `Option` / `bool` / `subtle::CtOption` returns
205/// `Result<_, Error>`. The single `Failed` variant is deliberate per
206/// the **failure-mode invariant** (see `SECURITY.md`): distinguishing
207/// failure modes leaks information to padding-oracle / invalid-curve /
208/// password-oracle attackers.
209///
210/// Per-module aliases keep the established import paths working:
211/// `sm2::Error`, `pem::Error`, `pkcs8::Error` are type aliases for
212/// this one type. Prior to v0.5 these were separate per-module enums
213/// (`SignError`, `EncryptError`, `DecryptError`, `pem::Error`,
214/// `pkcs8::Error`) all with a single `Failed` variant; v0.5 unifies
215/// them per Q5.16 in `docs/v0.5-scope.md`.
216#[derive(Debug, Clone, Copy, PartialEq, Eq)]
217#[non_exhaustive]
218pub enum Error {
219    /// The operation failed. No further information is exposed —
220    /// distinguishing failure modes leaks attacker-useful signal.
221    Failed,
222}
223
224impl core::fmt::Display for Error {
225    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
226        f.write_str("gmcrypto-core operation failed")
227    }
228}
229
230impl core::error::Error for Error {}