Skip to main content

gmcrypto_core/
pkcs8.rs

1//! PKCS#8 `OneAsymmetricKey` codec (RFC 5958) + PBES2 encryption (RFC 8018).
2//!
3//! Wire shapes:
4//!
5//! ```text
6//! OneAsymmetricKey ::= SEQUENCE {
7//!     version              INTEGER (0),
8//!     privateKeyAlgorithm  AlgorithmIdentifier,
9//!     privateKey           OCTET STRING -- DER-encoded ECPrivateKey
10//! }
11//!
12//! EncryptedPrivateKeyInfo ::= SEQUENCE {
13//!     encryptionAlgorithm  AlgorithmIdentifier,
14//!     encryptedData        OCTET STRING
15//! }
16//! ```
17//!
18//! For SM2 the `privateKeyAlgorithm` is `id-ecPublicKey` with
19//! `namedCurve = sm2p256v1`; `privateKey` wraps a DER-encoded RFC 5915
20//! [`crate::sec1::EcPrivateKey`].
21//!
22//! For the encrypted variant, `encryptionAlgorithm` is `id-PBES2`
23//! with `keyDerivationFunc = id-PBKDF2` (PRF = id-hmacWithSM3) and
24//! `encryptionScheme = sm4-cbc` (IV in the parameters OCTET STRING).
25//! `encryptedData` is the SM4-CBC ciphertext of the inner
26//! `OneAsymmetricKey`.
27//!
28//! # Failure-mode invariant (API)
29//!
30//! All decoders return `Result<_, Error>` with a single
31//! [`Error::Failed`] variant. The return type carries no distinction
32//! between "wrong password", "malformed PEM", and "valid PEM but
33//! bad inner `ECPrivateKey`" — the caller sees one uninformative
34//! shape on any failure.
35//!
36//! # Timing-side-channel posture
37//!
38//! Code paths over **secret material** are constant-time-designed:
39//! PBKDF2-HMAC-SM3 (covered by [`crate::hmac`]'s constant-time
40//! discipline), SM4-CBC decrypt + PKCS#7 strip
41//! ([`crate::sm4::mode_cbc::decrypt`]), and the inner
42//! [`Sm2PrivateKey::new`] range gate. The W2 dudect target
43//! `ct_pkcs8_decrypt` class-splits by **password bytes** (both
44//! classes ship valid blobs so both succeed via identical control
45//! flow); local 10K-sample run measures `|tau| ≈ 0.02`.
46//!
47//! Code paths over **public attacker-supplied wire bytes** (the
48//! PBES2 structural parse) early-return on malformed input. A
49//! structurally invalid blob fails in microseconds; a structurally
50//! valid blob with a wrong password runs full PBKDF2 + SM4-CBC + an
51//! inner parse before failing. **This wall-clock distinction is
52//! observable but not secret-dependent** — the attacker built the
53//! blob and already knows its structural validity. The dudect
54//! gate above covers the only secret-dependent timing class
55//! (password vs password under a valid blob).
56//!
57//! # KDF parameter validation
58//!
59//! [`decrypt`] rejects `iterations == 0` (RFC 8018 §5.2 requires
60//! `c ≥ 1`) and `iterations > 10_000_000` (denial-of-service
61//! bound; the upper limit is per the W2 risk-trap in
62//! `docs/v0.3-scope.md`). Any other malformed PBES2 parameter
63//! folds into [`Error::Failed`] identically.
64
65use crate::asn1::oid::{ID_EC_PUBLIC_KEY, ID_HMAC_WITH_SM3, ID_PBKDF2, PBES2, SM2P256V1, SM4_CBC};
66use crate::asn1::{reader, writer};
67use crate::kdf::pbkdf2_hmac_sm3;
68use crate::sec1;
69use crate::sm2::Sm2PrivateKey;
70use crate::sm4::mode_cbc;
71use alloc::vec::Vec;
72use crypto_bigint::U256;
73use subtle::ConstantTimeEq;
74use zeroize::Zeroize;
75
76/// PKCS#8 codec failure. Single uninformative variant per the
77/// project's failure-mode invariant.
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79pub enum Error {
80    /// Decoding, decryption, or inner-key reconstruction failed for
81    /// any reason — wrong password, malformed PBES2 parameters,
82    /// off-curve public key, out-of-range scalar, etc. Single
83    /// uninformative outcome.
84    Failed,
85}
86
87/// PKCS#8 version field (`v1 = 0`). RFC 5958 also defines `v2 = 1`
88/// when the optional `publicKey` BIT STRING is present; v0.3 emits
89/// `v1` and accepts both `v1` and `v2`.
90const PKCS8_V1: u8 = 0;
91const PKCS8_V2: u8 = 1;
92
93/// Default SM4-CBC key length (always 16 bytes for SM4).
94const SM4_KEY_LEN: usize = 16;
95/// SM4 block size = CBC IV size.
96const SM4_IV_LEN: usize = 16;
97
98/// PBKDF2 iteration count upper bound for [`decrypt`].
99///
100/// Rejects adversarial blobs that would burn unbounded CPU. v0.3
101/// picks `10_000_000` as the denial-of-service ceiling — well
102/// above any realistic production iteration count and still
103/// bounded.
104pub const PBKDF2_MAX_ITERATIONS: u32 = 10_000_000;
105
106/// Encode an SM2 private key as a DER-encoded **unencrypted**
107/// PKCS#8 `OneAsymmetricKey`.
108///
109/// The inner `ECPrivateKey` carries the scalar plus the optional
110/// `publicKey` field (uncompressed `04 || X || Y`); the outer
111/// privateKeyAlgorithm is `id-ecPublicKey` with `sm2p256v1`.
112///
113/// The intermediate `ECPrivateKey` body is zeroized before return.
114/// **The returned `Vec<u8>` contains the raw scalar bytes** —
115/// caller is responsible for wiping it before letting it leave
116/// their stack frame.
117#[must_use]
118pub fn encode(key: &Sm2PrivateKey) -> Vec<u8> {
119    let mut scalar_be = key.to_sec1_be();
120    let pub_uncompressed = {
121        let pub_key = crate::sm2::Sm2PublicKey::from_point(key.public_key());
122        pub_key.to_sec1_uncompressed()
123    };
124    let mut inner = sec1::encode(&scalar_be, Some(&pub_uncompressed));
125    scalar_be.zeroize();
126
127    // privateKeyAlgorithm SEQUENCE { id-ecPublicKey, namedCurve }
128    let mut alg_inner = Vec::with_capacity(ID_EC_PUBLIC_KEY.len() + SM2P256V1.len() + 4);
129    writer::write_oid(&mut alg_inner, ID_EC_PUBLIC_KEY);
130    writer::write_oid(&mut alg_inner, SM2P256V1);
131    let mut alg_seq = Vec::with_capacity(alg_inner.len() + 4);
132    writer::write_sequence(&mut alg_seq, &alg_inner);
133
134    let mut body = Vec::with_capacity(inner.len() + alg_seq.len() + 8);
135    writer::write_integer(&mut body, &[PKCS8_V1]);
136    body.extend_from_slice(&alg_seq);
137    writer::write_octet_string(&mut body, &inner);
138
139    let mut out = Vec::with_capacity(body.len() + 4);
140    writer::write_sequence(&mut out, &body);
141
142    // Wipe the secret-bearing intermediates. (Zeroize is a side-
143    // effecting write the optimizer must preserve via the
144    // `volatile_write` in `zeroize::Zeroize`; the buffer is read by
145    // SM4-CBC during the encrypt path's previous use.)
146    inner.zeroize();
147    body.zeroize();
148    out
149}
150
151/// Decode an unencrypted PKCS#8 `OneAsymmetricKey` blob into an
152/// [`Sm2PrivateKey`].
153///
154/// Validates the version (0 or 1), the privateKeyAlgorithm
155/// (id-ecPublicKey + sm2p256v1), the inner `ECPrivateKey` scalar
156/// (`d ∈ [1, n-2]`), and any optional public key (must match
157/// `d·G`).
158///
159/// # Errors
160///
161/// Returns [`Error::Failed`] for any malformed input or
162/// out-of-range scalar.
163pub fn decode(input: &[u8]) -> Result<Sm2PrivateKey, Error> {
164    let (body, rest) = reader::read_sequence(input).ok_or(Error::Failed)?;
165    if !rest.is_empty() {
166        return Err(Error::Failed);
167    }
168
169    // version INTEGER
170    let (version, body) = reader::read_integer(body).ok_or(Error::Failed)?;
171    if version != [PKCS8_V1] && version != [PKCS8_V2] {
172        return Err(Error::Failed);
173    }
174
175    // privateKeyAlgorithm SEQUENCE
176    let (alg_inner, body) = reader::read_sequence(body).ok_or(Error::Failed)?;
177    let (alg_oid, alg_inner) = reader::read_oid(alg_inner).ok_or(Error::Failed)?;
178    if alg_oid != ID_EC_PUBLIC_KEY {
179        return Err(Error::Failed);
180    }
181    let (curve_oid, alg_inner) = reader::read_oid(alg_inner).ok_or(Error::Failed)?;
182    if curve_oid != SM2P256V1 || !alg_inner.is_empty() {
183        return Err(Error::Failed);
184    }
185
186    // privateKey OCTET STRING { ECPrivateKey }
187    let (inner_bytes, body) = reader::read_octet_string(body).ok_or(Error::Failed)?;
188    let mut inner = sec1::decode(inner_bytes).ok_or(Error::Failed)?;
189
190    // Trailing PKCS#8 v2 attributes/publicKey are tolerated but not
191    // required; we reject any unrecognized tag to stay strict.
192    // (gmssl 3.1.1 emits PKCS#8 v1 by default, which has no trailing
193    // fields.)
194    if !body.is_empty() {
195        // PKCS#8 v2 may carry [0] Attributes and [1] BIT STRING
196        // publicKey. Skip them tolerantly: we don't need either,
197        // since the inner ECPrivateKey already carries the public
198        // point and the scalar is authoritative.
199        let mut tail = body;
200        while !tail.is_empty() {
201            // Try [0] attributes EXPLICIT.
202            if let Some((_, after)) = reader::read_context_tagged_explicit(tail, 0) {
203                tail = after;
204                continue;
205            }
206            // Try [1] publicKey EXPLICIT BIT STRING.
207            if let Some((_, after)) = reader::read_context_tagged_explicit(tail, 1) {
208                tail = after;
209                continue;
210            }
211            // Unknown tag — fold into Failed.
212            inner.scalar_be.zeroize();
213            return Err(Error::Failed);
214        }
215    }
216
217    let d = U256::from_be_slice(&inner.scalar_be);
218    inner.scalar_be.zeroize();
219    let key = Sm2PrivateKey::new(d);
220    let key: Option<Sm2PrivateKey> = key.into();
221    let key = key.ok_or(Error::Failed)?;
222
223    // If the inner ECPrivateKey carried a publicKey, cross-check it
224    // matches d·G — defends against stripped-public-key + stripped-
225    // scalar swaps that escape unencrypted PKCS#8 detection.
226    if let Some(stored_pub) = inner.public {
227        let derived = key.public_key();
228        if !bool::from(stored_pub.ct_eq(&derived)) {
229            return Err(Error::Failed);
230        }
231    }
232
233    Ok(key)
234}
235
236/// Encrypt an SM2 private key as a DER-encoded RFC 5958
237/// `EncryptedPrivateKeyInfo` blob using PBES2 (PBKDF2-HMAC-SM3 +
238/// SM4-CBC).
239///
240/// `salt` and `iv` must be **caller-supplied unpredictable
241/// CSPRNG output** — see `CLAUDE.md` and
242/// [`crate::sm4::mode_cbc::encrypt`]'s IV contract. Re-using the
243/// same `(salt, iv)` under the same password under two different
244/// keys is a key-recovery attack.
245///
246/// `iterations` should be at least 600,000 (OWASP 2024 PBKDF2
247/// baseline); v0.3 does not enforce a minimum to keep test
248/// vectors reproducible cheaply, but production callers must
249/// pick.
250///
251/// # Errors
252///
253/// Returns [`Error::Failed`] only if `iterations == 0` (RFC 8018
254/// `c ≥ 1`).
255pub fn encrypt(
256    key: &Sm2PrivateKey,
257    password: &[u8],
258    salt: &[u8],
259    iterations: u32,
260    iv: &[u8; SM4_IV_LEN],
261) -> Result<Vec<u8>, Error> {
262    if iterations == 0 {
263        return Err(Error::Failed);
264    }
265
266    // 1. Encode the inner OneAsymmetricKey.
267    let mut inner = encode(key);
268
269    // 2. Derive an SM4-CBC key: PBKDF2-HMAC-SM3(password, salt, iter, 16).
270    let mut sm4_key = [0u8; SM4_KEY_LEN];
271    pbkdf2_hmac_sm3(password, salt, iterations, &mut sm4_key).ok_or(Error::Failed)?;
272
273    // 3. Encrypt under SM4-CBC + PKCS#7 padding.
274    let ciphertext = mode_cbc::encrypt(&sm4_key, iv, &inner);
275
276    // Wipe sensitive intermediates.
277    inner.zeroize();
278    sm4_key.zeroize();
279
280    // 4. Build the EncryptedPrivateKeyInfo wrapper.
281    let pbes2_params = build_pbes2_params(salt, iterations, iv);
282    let mut alg_inner = Vec::with_capacity(PBES2.len() + pbes2_params.len() + 4);
283    writer::write_oid(&mut alg_inner, PBES2);
284    alg_inner.extend_from_slice(&pbes2_params);
285    let mut alg_seq = Vec::with_capacity(alg_inner.len() + 4);
286    writer::write_sequence(&mut alg_seq, &alg_inner);
287
288    let mut body = Vec::with_capacity(alg_seq.len() + ciphertext.len() + 8);
289    body.extend_from_slice(&alg_seq);
290    writer::write_octet_string(&mut body, &ciphertext);
291
292    let mut out = Vec::with_capacity(body.len() + 4);
293    writer::write_sequence(&mut out, &body);
294    Ok(out)
295}
296
297/// Build the PBES2 `parameters` SEQUENCE for [`encrypt`].
298///
299/// ```text
300/// PBES2-params ::= SEQUENCE {
301///     keyDerivationFunc  AlgorithmIdentifier { id-PBKDF2,  PBKDF2-params },
302///     encryptionScheme   AlgorithmIdentifier { sm4-cbc,    OCTET STRING (IV) }
303/// }
304///
305/// PBKDF2-params ::= SEQUENCE {
306///     salt            OCTET STRING,
307///     iterationCount  INTEGER,
308///     keyLength       INTEGER OPTIONAL,
309///     prf             AlgorithmIdentifier { id-hmacWithSM3, NULL }
310/// }
311/// ```
312fn build_pbes2_params(salt: &[u8], iterations: u32, iv: &[u8; SM4_IV_LEN]) -> Vec<u8> {
313    // PBKDF2-params.
314    let mut pbkdf2_inner = Vec::with_capacity(salt.len() + 32);
315    writer::write_octet_string(&mut pbkdf2_inner, salt);
316    writer::write_integer(&mut pbkdf2_inner, &iterations.to_be_bytes());
317    // PRF AlgorithmIdentifier { id-hmacWithSM3, NULL }
318    let mut prf_inner = Vec::with_capacity(ID_HMAC_WITH_SM3.len() + 4);
319    writer::write_oid(&mut prf_inner, ID_HMAC_WITH_SM3);
320    writer::write_null(&mut prf_inner);
321    let mut prf_seq = Vec::with_capacity(prf_inner.len() + 4);
322    writer::write_sequence(&mut prf_seq, &prf_inner);
323    pbkdf2_inner.extend_from_slice(&prf_seq);
324
325    let mut pbkdf2_seq = Vec::with_capacity(pbkdf2_inner.len() + 4);
326    writer::write_sequence(&mut pbkdf2_seq, &pbkdf2_inner);
327
328    // keyDerivationFunc AlgorithmIdentifier { id-PBKDF2, PBKDF2-params }.
329    let mut kdf_inner = Vec::with_capacity(ID_PBKDF2.len() + pbkdf2_seq.len() + 4);
330    writer::write_oid(&mut kdf_inner, ID_PBKDF2);
331    kdf_inner.extend_from_slice(&pbkdf2_seq);
332    let mut kdf_seq = Vec::with_capacity(kdf_inner.len() + 4);
333    writer::write_sequence(&mut kdf_seq, &kdf_inner);
334
335    // encryptionScheme AlgorithmIdentifier { sm4-cbc, OCTET STRING (IV) }.
336    let mut es_inner = Vec::with_capacity(SM4_CBC.len() + iv.len() + 4);
337    writer::write_oid(&mut es_inner, SM4_CBC);
338    writer::write_octet_string(&mut es_inner, iv);
339    let mut es_seq = Vec::with_capacity(es_inner.len() + 4);
340    writer::write_sequence(&mut es_seq, &es_inner);
341
342    let mut params_inner = Vec::with_capacity(kdf_seq.len() + es_seq.len());
343    params_inner.extend_from_slice(&kdf_seq);
344    params_inner.extend_from_slice(&es_seq);
345
346    let mut out = Vec::with_capacity(params_inner.len() + 4);
347    writer::write_sequence(&mut out, &params_inner);
348    out
349}
350
351/// Decrypt a DER-encoded RFC 5958 `EncryptedPrivateKeyInfo` blob.
352///
353/// Parses the PBES2 parameters, derives the SM4-CBC key from
354/// `password`, decrypts, and reconstructs the inner SM2 private
355/// key. Single uninformative outcome — no path to distinguish
356/// "wrong password" from "malformed PBES2 parameters" from
357/// "decrypt succeeded but inner key was malformed".
358///
359/// # Errors
360///
361/// Returns [`Error::Failed`] for any malformed input. Same shape
362/// regardless of where the failure occurred.
363pub fn decrypt(input: &[u8], password: &[u8]) -> Result<Sm2PrivateKey, Error> {
364    let parsed = parse_encrypted_blob(input).ok_or(Error::Failed)?;
365
366    // Derive the SM4-CBC key from password + salt + iterations.
367    let mut sm4_key = [0u8; SM4_KEY_LEN];
368    let derive_ok =
369        pbkdf2_hmac_sm3(password, parsed.salt, parsed.iterations, &mut sm4_key).is_some();
370    // Even if derive failed (iterations == 0 caught above by the
371    // 1..=PBKDF2_MAX_ITERATIONS gate, so this is unreachable in
372    // practice), continue through decrypt with a zero key to keep
373    // failure-mode shapes uniform.
374    let _ = derive_ok;
375
376    // SM4-CBC decrypt + PKCS#7 strip. Folds into Failed on any
377    // failure (length not multiple of 16, bad pad, etc.).
378    let plaintext = mode_cbc::decrypt(&sm4_key, &parsed.iv, parsed.ciphertext);
379    sm4_key.zeroize();
380
381    let mut plaintext = plaintext.ok_or(Error::Failed)?;
382
383    // Parse the inner unencrypted OneAsymmetricKey. Failure folds
384    // into the same Failed.
385    let result = decode(&plaintext);
386    plaintext.zeroize();
387    result
388}
389
390/// Internal: parsed PBES2 parameters + ciphertext.
391struct ParsedEncrypted<'a> {
392    salt: &'a [u8],
393    iterations: u32,
394    iv: [u8; SM4_IV_LEN],
395    ciphertext: &'a [u8],
396}
397
398/// Parse an `EncryptedPrivateKeyInfo` blob into validated PBES2
399/// parameters + the ciphertext slice. Returns `None` for any
400/// malformed input or unsupported algorithm.
401fn parse_encrypted_blob(input: &[u8]) -> Option<ParsedEncrypted<'_>> {
402    let (body, rest) = reader::read_sequence(input)?;
403    if !rest.is_empty() {
404        return None;
405    }
406    // encryptionAlgorithm SEQUENCE { id-PBES2, PBES2-params }.
407    let (alg_inner, body) = reader::read_sequence(body)?;
408    let (alg_oid, alg_inner) = reader::read_oid(alg_inner)?;
409    if alg_oid != PBES2 {
410        return None;
411    }
412    let (params_inner, alg_inner_rest) = reader::read_sequence(alg_inner)?;
413    if !alg_inner_rest.is_empty() {
414        return None;
415    }
416
417    // keyDerivationFunc SEQUENCE { id-PBKDF2, PBKDF2-params }.
418    let (kdf_seq, params_rest) = reader::read_sequence(params_inner)?;
419    let (kdf_oid, kdf_after) = reader::read_oid(kdf_seq)?;
420    if kdf_oid != ID_PBKDF2 {
421        return None;
422    }
423    let (pbkdf2_inner, kdf_seq_rest) = reader::read_sequence(kdf_after)?;
424    if !kdf_seq_rest.is_empty() {
425        return None;
426    }
427
428    // PBKDF2-params: salt, iterations, [keyLength], [PRF].
429    let (salt, pbkdf2_inner) = reader::read_octet_string(pbkdf2_inner)?;
430    let (iter_bytes, mut pbkdf2_inner) = reader::read_integer(pbkdf2_inner)?;
431    if iter_bytes.len() > 4 {
432        return None;
433    }
434    let mut iter_buf = [0u8; 4];
435    iter_buf[4 - iter_bytes.len()..].copy_from_slice(iter_bytes);
436    let iterations = u32::from_be_bytes(iter_buf);
437    if iterations == 0 || iterations > PBKDF2_MAX_ITERATIONS {
438        return None;
439    }
440    // Optional keyLength. SM4-CBC fixes 16 bytes; if present, must equal 16.
441    if let Some((kl_bytes, after)) = reader::read_integer(pbkdf2_inner) {
442        if kl_bytes.len() > 4 {
443            return None;
444        }
445        let mut kl_buf = [0u8; 4];
446        kl_buf[4 - kl_bytes.len()..].copy_from_slice(kl_bytes);
447        let key_length = u32::from_be_bytes(kl_buf) as usize;
448        if key_length != SM4_KEY_LEN {
449            return None;
450        }
451        pbkdf2_inner = after;
452    }
453    // Optional PRF. Default is HMAC-SHA-1 per RFC 8018; we accept
454    // only id-hmacWithSM3 (gmssl convention; required for SM2 PKCS#8).
455    // No PRF specified would default to HMAC-SHA1, which is invalid
456    // for SM2 PKCS#8 — reject.
457    if pbkdf2_inner.is_empty() {
458        return None;
459    }
460    let (prf_seq, prf_rest) = reader::read_sequence(pbkdf2_inner)?;
461    if !prf_rest.is_empty() {
462        return None;
463    }
464    let (prf_oid, prf_seq_rest) = reader::read_oid(prf_seq)?;
465    if prf_oid != ID_HMAC_WITH_SM3 {
466        return None;
467    }
468    // The PRF parameters MAY be NULL or absent.
469    if !prf_seq_rest.is_empty()
470        && (reader::read_null(prf_seq_rest).is_none() || prf_seq_rest.len() != 2)
471    {
472        return None;
473    }
474
475    // encryptionScheme SEQUENCE { sm4-cbc, OCTET STRING (IV) }.
476    let (es_seq, params_outer_rest) = reader::read_sequence(params_rest)?;
477    if !params_outer_rest.is_empty() {
478        return None;
479    }
480    let (es_oid, es_after) = reader::read_oid(es_seq)?;
481    if es_oid != SM4_CBC {
482        return None;
483    }
484    let (iv_bytes, es_seq_rest) = reader::read_octet_string(es_after)?;
485    if !es_seq_rest.is_empty() || iv_bytes.len() != SM4_IV_LEN {
486        return None;
487    }
488    let mut iv = [0u8; SM4_IV_LEN];
489    iv.copy_from_slice(iv_bytes);
490
491    // encryptedData OCTET STRING.
492    let (ciphertext, body_rest) = reader::read_octet_string(body)?;
493    if !body_rest.is_empty() {
494        return None;
495    }
496
497    Some(ParsedEncrypted {
498        salt,
499        iterations,
500        iv,
501        ciphertext,
502    })
503}
504
505#[cfg(test)]
506mod tests {
507    use super::*;
508    use crypto_bigint::U256;
509
510    fn sample_key() -> Sm2PrivateKey {
511        let d =
512            U256::from_be_hex("3945208F7B2144B13F36E38AC6D39F95889393692860B51A42FB81EF4DF7C5B8");
513        Sm2PrivateKey::new(d).expect("valid d")
514    }
515
516    /// Unencrypted PKCS#8 round-trip.
517    #[test]
518    fn round_trip_unencrypted() {
519        let key = sample_key();
520        let der = encode(&key);
521        let recovered = decode(&der).expect("decode");
522        // Same scalar → same public key.
523        assert!(bool::from(recovered.public_key().ct_eq(&key.public_key())));
524    }
525
526    /// Unencrypted decode rejects trailing junk.
527    #[test]
528    fn unencrypted_rejects_trailing_bytes() {
529        let key = sample_key();
530        let mut der = encode(&key);
531        der.push(0x00);
532        assert!(matches!(decode(&der), Err(Error::Failed)));
533    }
534
535    /// Unencrypted decode rejects mismatched optional public key.
536    #[test]
537    fn unencrypted_rejects_public_key_mismatch() {
538        // Build a PKCS#8 by hand whose inner ECPrivateKey has the
539        // SAMPLE-1 scalar but the SAMPLE-2 public key — must fail.
540        let d1 =
541            U256::from_be_hex("3945208F7B2144B13F36E38AC6D39F95889393692860B51A42FB81EF4DF7C5B8");
542        let d2 =
543            U256::from_be_hex("1649AB77A00637BD5E2EFE283FBF353534AA7F7CB89463F208DDBC2920BB0DA0");
544        let key1 = Sm2PrivateKey::new(d1).expect("d1");
545        let key2 = Sm2PrivateKey::new(d2).expect("d2");
546        let scalar1 = key1.to_sec1_be();
547        let pk2 = crate::sm2::Sm2PublicKey::from_point(key2.public_key()).to_sec1_uncompressed();
548        let inner_bad = sec1::encode(&scalar1, Some(&pk2));
549
550        // Wrap in unencrypted PKCS#8 manually (re-using what encode does).
551        let mut alg_inner = Vec::new();
552        writer::write_oid(&mut alg_inner, ID_EC_PUBLIC_KEY);
553        writer::write_oid(&mut alg_inner, SM2P256V1);
554        let mut alg_seq = Vec::new();
555        writer::write_sequence(&mut alg_seq, &alg_inner);
556        let mut body = Vec::new();
557        writer::write_integer(&mut body, &[PKCS8_V1]);
558        body.extend_from_slice(&alg_seq);
559        writer::write_octet_string(&mut body, &inner_bad);
560        let mut out = Vec::new();
561        writer::write_sequence(&mut out, &body);
562
563        assert!(matches!(decode(&out), Err(Error::Failed)));
564    }
565
566    /// Encrypted PKCS#8 round-trip with low iteration count.
567    #[test]
568    fn round_trip_encrypted() {
569        let key = sample_key();
570        let salt = [0xAB; 16];
571        let iv = [0xCD; SM4_IV_LEN];
572        let blob =
573            encrypt(&key, b"correct horse battery staple", &salt, 1024, &iv).expect("encrypt");
574        let recovered =
575            decrypt(&blob, b"correct horse battery staple").expect("decrypt with right password");
576        assert!(bool::from(recovered.public_key().ct_eq(&key.public_key())));
577    }
578
579    /// Encrypted decrypt with the wrong password fails into Failed.
580    #[test]
581    fn encrypted_wrong_password_fails() {
582        let key = sample_key();
583        let salt = [0xAB; 16];
584        let iv = [0xCD; SM4_IV_LEN];
585        let blob = encrypt(&key, b"right", &salt, 1024, &iv).expect("encrypt");
586        assert!(matches!(decrypt(&blob, b"wrong"), Err(Error::Failed)));
587    }
588
589    #[test]
590    fn encrypted_zero_iterations_rejected() {
591        let key = sample_key();
592        let salt = [0xAB; 16];
593        let iv = [0xCD; SM4_IV_LEN];
594        assert!(matches!(
595            encrypt(&key, b"pw", &salt, 0, &iv),
596            Err(Error::Failed)
597        ));
598    }
599
600    #[test]
601    fn decrypt_rejects_truncated_blob() {
602        assert!(matches!(decrypt(&[], b"pw"), Err(Error::Failed)));
603        assert!(matches!(decrypt(&[0x30, 0x00], b"pw"), Err(Error::Failed)));
604    }
605
606    #[test]
607    fn decrypt_rejects_excessive_iterations() {
608        // Build a malformed blob with iterations > PBKDF2_MAX_ITERATIONS.
609        let key = sample_key();
610        let salt = [0xAB; 16];
611        let iv = [0xCD; SM4_IV_LEN];
612        let blob = encrypt(&key, b"pw", &salt, 1024, &iv).expect("encrypt");
613        // Patch the iteration-count INTEGER. The structure is
614        // SEQ { SEQ { id-PBES2, SEQ { kdf, encScheme } }, OCTETSTRING }.
615        // Easier: round-trip parse to confirm the gate, by wrapping
616        // a hand-built blob with a forbidden iteration count.
617
618        let bad_iter: u32 = PBKDF2_MAX_ITERATIONS + 1;
619        let pbes2_params = build_pbes2_params(&salt, bad_iter, &iv);
620        let mut alg_inner = Vec::new();
621        writer::write_oid(&mut alg_inner, PBES2);
622        alg_inner.extend_from_slice(&pbes2_params);
623        let mut alg_seq = Vec::new();
624        writer::write_sequence(&mut alg_seq, &alg_inner);
625        // Borrow ciphertext bytes from the valid blob's tail.
626        // Simplest: parse the valid blob to recover ciphertext slice.
627        let parsed = parse_encrypted_blob(&blob).expect("baseline parse");
628        let mut body = Vec::new();
629        body.extend_from_slice(&alg_seq);
630        writer::write_octet_string(&mut body, parsed.ciphertext);
631        let mut bad_blob = Vec::new();
632        writer::write_sequence(&mut bad_blob, &body);
633        assert!(matches!(decrypt(&bad_blob, b"pw"), Err(Error::Failed)));
634    }
635}