Skip to main content

gmcrypto_core/
x509.rs

1//! X.509-with-SM2: leaf certificate parse + SM2-with-SM3 signature verify
2//! (v1.3; GM/T 0015 profile over the RFC 5280 structure).
3//!
4//! **Parsing and single-certificate verify make NO trust decisions.**
5//! [`Certificate::from_der`] tells you what a certificate *says*;
6//! `verify_signature` tells you whether its SM2-with-SM3 signature over those
7//! exact wire bytes verifies against a caller-supplied issuer public key — and
8//! nothing more. At the parse / single-verify layer there is:
9//!
10//! - **no chain building** and no trust-anchor concept;
11//! - **no time/validity decision** ([`X509Time`] values are exposed; this
12//!   library has no clock — comparing them to "now" is the caller's call);
13//! - **no extension interpretation at parse time** — `from_der` shape-checks
14//!   extensions and exposes them raw; *interpretation* of `keyUsage` /
15//!   `basicConstraints` happens only in [`verify_chain`] (below), never in
16//!   `from_der`;
17//! - **no hostname matching, no revocation (CRL/OCSP), no policy logic.**
18//!
19//! A `true` from `verify_signature` means exactly "this issuer key signed
20//! these tbsCertificate bytes" — it does NOT mean the certificate is valid,
21//! current, or trustworthy.
22//!
23//! **v1.8 adds a deliberately narrow chain check.** [`verify_chain`] walks a
24//! caller-ordered linear chain to a caller-trusted anchor (per-edge SM2
25//! signature + raw-Name linking + intermediate CA-ness + an optional
26//! caller-supplied comparison time + a depth cap), and [`KeyUsage`] /
27//! [`BasicConstraints`] expose the two interpreted extensions. This is a
28//! *structural* trust check — "does this chain link to a CA you trust" — and
29//! still **NOT** endpoint authentication (whether the cert pair is *the peer
30//! you dialed* is the caller's, permanently), **NOT** a clock decision (the
31//! caller passes the time), **NOT** revocation / policy / name-constraints /
32//! EKU. The TLCP [sign, enc] pair profile lives in `tlcp::chain`.
33//!
34//! Accepted profile (single `None` for everything else, per the
35//! failure-mode invariant): DER X.509 **v3**, signature algorithm
36//! `sm2-sign-with-sm3` (1.2.156.10197.1.501, parameters absent or NULL,
37//! outer == inner), SPKI `id-ecPublicKey` + `sm2p256v1` (enforced by the
38//! existing [`crate::spki::decode`]). See `docs/v1.3-x509-sm2-design.md`.
39
40extern crate alloc;
41
42use crate::asn1::oid;
43use crate::asn1::reader;
44use crate::sm2::{DEFAULT_SIGNER_ID, Sm2PublicKey, verify_with_id};
45use alloc::vec::Vec;
46
47const TAG_UTC_TIME: u8 = 0x17;
48const TAG_GENERALIZED_TIME: u8 = 0x18;
49const TAG_BOOLEAN: u8 = 0x01;
50
51/// A calendar timestamp parsed from a certificate `Validity` field
52/// (`UTCTime` or `GeneralizedTime`, Zulu only).
53///
54/// Plain field-wise data with derived ordering — **this library has no
55/// clock**, so any "is the certificate currently valid" decision is the
56/// caller's, made against a time source the caller trusts.
57///
58/// Documented tolerance: RFC 5280 §4.1.2.5 says pre-2050 dates MUST be
59/// `UTCTime`; a `GeneralizedTime` pre-2050 date is nevertheless accepted here
60/// (the encoding choice is signature-covered either way).
61#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
62pub struct X509Time {
63    /// Full year (`UTCTime` pivot per RFC 5280 §4.1.2.5.1: `YY >= 50` → 19YY).
64    pub year: u16,
65    /// Month, 1–12.
66    pub month: u8,
67    /// Day of month, 1–31 (calendar-exact day-per-month is NOT checked).
68    pub day: u8,
69    /// Hour, 0–23.
70    pub hour: u8,
71    /// Minute, 0–59.
72    pub minute: u8,
73    /// Second, 0–59.
74    pub second: u8,
75}
76
77fn two_digits(b: &[u8]) -> Option<u8> {
78    match b {
79        [a @ b'0'..=b'9', c @ b'0'..=b'9'] => Some((a - b'0') * 10 + (c - b'0')),
80        _ => None,
81    }
82}
83
84/// Parse one `Time` value (either tag), returning `(time, rest_after_tlv)`.
85fn read_time(input: &[u8]) -> Option<(X509Time, &[u8])> {
86    let (year, body, rest) = if let Some((v, rest)) = reader::read_tlv(input, TAG_UTC_TIME) {
87        if v.len() != 13 {
88            return None;
89        }
90        let yy = u16::from(two_digits(&v[0..2])?);
91        (if yy >= 50 { 1900 + yy } else { 2000 + yy }, &v[2..], rest)
92    } else {
93        let (v, rest) = reader::read_tlv(input, TAG_GENERALIZED_TIME)?;
94        if v.len() != 15 {
95            return None;
96        }
97        (
98            u16::from(two_digits(&v[0..2])?) * 100 + u16::from(two_digits(&v[2..4])?),
99            &v[4..],
100            rest,
101        )
102    };
103    // body = MMDDHHMMSS + 'Z' (Zulu only; offsets/fractions rejected by length).
104    if body.len() != 11 || body[10] != b'Z' {
105        return None;
106    }
107    let (month, day) = (two_digits(&body[0..2])?, two_digits(&body[2..4])?);
108    let (hour, minute, second) = (
109        two_digits(&body[4..6])?,
110        two_digits(&body[6..8])?,
111        two_digits(&body[8..10])?,
112    );
113    if !(1..=12).contains(&month)
114        || !(1..=31).contains(&day)
115        || hour > 23
116        || minute > 59
117        || second > 59
118    {
119        return None;
120    }
121    Some((
122        X509Time {
123            year,
124            month,
125            day,
126            hour,
127            minute,
128            second,
129        },
130        rest,
131    ))
132}
133
134/// Read an `AlgorithmIdentifier` that MUST be `sm2-sign-with-sm3` with absent
135/// or NULL parameters (scope Q3.6). Returns `(full_algid_tlv_span, rest)` —
136/// the full span feeds the outer==inner byte-equality rule (design §5.5),
137/// which also rejects a mixed absent/NULL pair.
138fn read_sm2_sig_algid(input: &[u8]) -> Option<(&[u8], &[u8])> {
139    let (body, rest) = reader::read_sequence(input)?;
140    let span = &input[..input.len() - rest.len()];
141    let (oid_bytes, after_oid) = reader::read_oid(body)?;
142    if oid_bytes != oid::SM2_SIGN_WITH_SM3 {
143        return None;
144    }
145    if after_oid.is_empty() {
146        Some((span, rest))
147    } else {
148        let after_null = reader::read_null(after_oid)?;
149        if after_null.is_empty() {
150            Some((span, rest))
151        } else {
152            None
153        }
154    }
155}
156
157/// One parsed `Extension` plus the unconsumed `rest` of the
158/// SEQUENCE-OF-`Extension` body after it.
159struct ParsedExt<'a> {
160    oid: &'a [u8],
161    critical: bool,
162    value: &'a [u8],
163    rest: &'a [u8],
164}
165
166/// Parse ONE `Extension` from the front of a SEQUENCE-OF-`Extension` body.
167/// `None` on a malformed element. The framing — `SEQUENCE { extnID OID,
168/// [critical BOOLEAN (one content byte)], extnValue OCTET STRING }`, exactly
169/// consumed — is the single source of truth shared by
170/// [`check_extensions_shape`], [`find_extension`], and
171/// [`has_unknown_critical`].
172fn next_extension(exts: &[u8]) -> Option<ParsedExt<'_>> {
173    let (ext, rest) = reader::read_sequence(exts)?;
174    let (oid, after_oid) = reader::read_oid(ext)?;
175    let (critical, after_bool) = match reader::read_tlv(after_oid, TAG_BOOLEAN) {
176        Some((b, rb)) => {
177            if b.len() != 1 {
178                return None;
179            }
180            (b[0] != 0, rb)
181        }
182        None => (false, after_oid),
183    };
184    let (value, after_value) = reader::read_octet_string(after_bool)?;
185    if !after_value.is_empty() {
186        return None;
187    }
188    Some(ParsedExt {
189        oid,
190        critical,
191        value,
192        rest,
193    })
194}
195
196/// Shape-check the `[3]` extensions content ONE level deep, with ZERO
197/// interpretation (design §5.9): `Extensions ::= SEQUENCE SIZE(1..) OF
198/// Extension`. extnID values and `critical` flags are never evaluated.
199fn check_extensions_shape(content: &[u8]) -> Option<()> {
200    let (seq, rest) = reader::read_sequence(content)?;
201    if !rest.is_empty() || seq.is_empty() {
202        return None;
203    }
204    let mut exts = seq;
205    while !exts.is_empty() {
206        exts = next_extension(exts)?.rest;
207    }
208    Some(())
209}
210
211/// The X.509 `keyUsage` extension (RFC 5280 §4.2.1.3) as a bitfield.
212///
213/// A public `BIT STRING` read — **NOT** a trust decision. *Which* bits a
214/// role requires is decided by [`verify_chain`] / `tlcp::chain::verify_pair`,
215/// not by parsing. Bit 0 is the most-significant bit of the first content
216/// byte (DER `BIT STRING` order); bits 0–8 are named below.
217#[derive(Clone, Copy, Debug, PartialEq, Eq)]
218pub struct KeyUsage {
219    bits: u16,
220}
221
222impl KeyUsage {
223    /// Parse the DER `BIT STRING` (the `extnValue` content of a `keyUsage`
224    /// extension). `None` on malformed input.
225    fn parse(bit_string_tlv: &[u8]) -> Option<Self> {
226        let (unused, value, rest) = reader::read_bit_string(bit_string_tlv)?;
227        if !rest.is_empty() || unused > 7 {
228            return None;
229        }
230        let mut bits = 0u16;
231        for i in 0u16..9 {
232            let (byte, off) = ((i / 8) as usize, 7 - (i % 8));
233            if byte < value.len() && (value[byte] >> off) & 1 == 1 {
234                bits |= 1 << i;
235            }
236        }
237        Some(Self { bits })
238    }
239    const fn has(self, i: u16) -> bool {
240        self.bits & (1 << i) != 0
241    }
242    /// `digitalSignature` (bit 0).
243    #[must_use]
244    pub const fn digital_signature(self) -> bool {
245        self.has(0)
246    }
247    /// `nonRepudiation` / `contentCommitment` (bit 1).
248    #[must_use]
249    pub const fn content_commitment(self) -> bool {
250        self.has(1)
251    }
252    /// `keyEncipherment` (bit 2).
253    #[must_use]
254    pub const fn key_encipherment(self) -> bool {
255        self.has(2)
256    }
257    /// `dataEncipherment` (bit 3).
258    #[must_use]
259    pub const fn data_encipherment(self) -> bool {
260        self.has(3)
261    }
262    /// `keyAgreement` (bit 4).
263    #[must_use]
264    pub const fn key_agreement(self) -> bool {
265        self.has(4)
266    }
267    /// `keyCertSign` (bit 5).
268    #[must_use]
269    pub const fn key_cert_sign(self) -> bool {
270        self.has(5)
271    }
272    /// `cRLSign` (bit 6).
273    #[must_use]
274    pub const fn crl_sign(self) -> bool {
275        self.has(6)
276    }
277    /// `encipherOnly` (bit 7).
278    #[must_use]
279    pub const fn encipher_only(self) -> bool {
280        self.has(7)
281    }
282    /// `decipherOnly` (bit 8).
283    #[must_use]
284    pub const fn decipher_only(self) -> bool {
285        self.has(8)
286    }
287    /// The raw `keyUsage` bitmask (bit 0 = `digitalSignature` in the low bit;
288    /// matches the named accessors above).
289    ///
290    /// **Not public API and not SemVer-covered** — exists so the C FFI reader
291    /// can return the bitmask as a single `u16`.
292    #[doc(hidden)]
293    #[must_use]
294    pub const fn bits(&self) -> u16 {
295        self.bits
296    }
297}
298
299/// The X.509 `basicConstraints` extension (RFC 5280 §4.2.1.9): the CA flag
300/// plus an optional path-length constraint.
301///
302/// `path_len` is **parsed but NOT enforced** by [`verify_chain`] — v1.8 uses
303/// a fixed [`MAX_CHAIN_DEPTH`] cap instead (scope D-4).
304#[derive(Clone, Copy, Debug, PartialEq, Eq)]
305pub struct BasicConstraints {
306    /// The `cA` boolean (DEFAULT `FALSE`).
307    pub is_ca: bool,
308    /// The `pathLenConstraint`, if present. Parsed, not enforced.
309    pub path_len: Option<u32>,
310}
311
312impl BasicConstraints {
313    /// Parse the DER `SEQUENCE { cA BOOLEAN DEFAULT FALSE,
314    /// pathLenConstraint INTEGER OPTIONAL }` (the `extnValue` content of a
315    /// `basicConstraints` extension). `None` on malformed input.
316    fn parse(seq_tlv: &[u8]) -> Option<Self> {
317        let (content, rest) = reader::read_sequence(seq_tlv)?;
318        if !rest.is_empty() {
319            return None;
320        }
321        let (is_ca, after) = match reader::read_tlv(content, TAG_BOOLEAN) {
322            Some((b, r)) => {
323                if b.len() != 1 {
324                    return None;
325                }
326                (b[0] != 0, r)
327            }
328            None => (false, content),
329        };
330        let path_len = if after.is_empty() {
331            None
332        } else {
333            let (int, r) = reader::read_integer(after)?;
334            if !r.is_empty() || int.len() > 4 {
335                return None;
336            }
337            let mut v = 0u32;
338            for &byte in int {
339                v = (v << 8) | u32::from(byte);
340            }
341            Some(v)
342        };
343        Some(Self { is_ca, path_len })
344    }
345}
346
347/// Find the extension with `extn_id` in the `Extensions` SEQUENCE TLV,
348/// returning its `extnValue` content. `None` if absent. The blob was
349/// already shape-checked by [`Certificate::from_der`]; this re-walk is
350/// defensive (returns `None` on any malformed element). Returns the FIRST
351/// match (RFC 5280 forbids duplicate extensions; `from_der` does not dedup).
352fn find_extension<'a>(ext_tlv: &'a [u8], extn_id: &[u8]) -> Option<&'a [u8]> {
353    let (seq, _) = reader::read_sequence(ext_tlv)?;
354    let mut exts = seq;
355    while !exts.is_empty() {
356        let ext = next_extension(exts)?;
357        if ext.oid == extn_id {
358            return Some(ext.value);
359        }
360        exts = ext.rest;
361    }
362    None
363}
364
365/// `true` iff any extension is `critical` AND its `extnID` is not in `known`
366/// (RFC 5280 §4.2 — refuse a critical constraint we do not process; scope
367/// Q8.7b). Fail-closed on malformed input is not needed (`from_der` already
368/// shape-checked) but the walk is defensive.
369fn has_unknown_critical(ext_tlv: &[u8], known: &[&[u8]]) -> bool {
370    let Some((seq, _)) = reader::read_sequence(ext_tlv) else {
371        return false;
372    };
373    let mut exts = seq;
374    while !exts.is_empty() {
375        let Some(ext) = next_extension(exts) else {
376            return false;
377        };
378        if ext.critical && !known.contains(&ext.oid) {
379            return true;
380        }
381        exts = ext.rest;
382    }
383    false
384}
385
386/// A parsed X.509 v3 certificate (GM/T 0015 profile: SM2-with-SM3
387/// signature, sm2p256v1 SPKI).
388///
389/// Parsing makes **no trust decisions** — see the module docs for the full
390/// list of what this type deliberately does NOT do. Field bytes are owned
391/// copies of the exact wire encoding.
392pub struct Certificate {
393    tbs: Vec<u8>,
394    serial: Vec<u8>,
395    issuer: Vec<u8>,
396    subject: Vec<u8>,
397    extensions: Option<Vec<u8>>,
398    sig: Vec<u8>,
399    not_before: X509Time,
400    not_after: X509Time,
401    subject_key: Sm2PublicKey,
402}
403
404impl Certificate {
405    /// Strict-DER parse of an X.509 **v3** certificate in the accepted
406    /// profile (module docs). Single `None` for EVERY malformed or
407    /// out-of-profile input — the workspace failure-mode invariant.
408    ///
409    /// Parsing performs **no trust decisions**; a `Some` means only "the
410    /// bytes frame as a well-formed certificate in the accepted profile and
411    /// carry a valid SM2 subject public key".
412    #[must_use]
413    pub fn from_der(der: &[u8]) -> Option<Self> {
414        let (cert, rest) = reader::read_sequence(der)?;
415        if !rest.is_empty() {
416            return None;
417        }
418
419        // tbsCertificate — capture the exact wire TLV span (design §5.2);
420        // verification never re-encodes.
421        let (tbs_content, after_tbs) = reader::read_sequence(cert)?;
422        let tbs_span = &cert[..cert.len() - after_tbs.len()];
423
424        // ---- inside tbsCertificate ----
425        // version [0] EXPLICIT INTEGER, value exactly 2 (v3 only, Q3.11);
426        // the wrapper content must contain ONLY the INTEGER.
427        let (ver_content, cur) = reader::read_context_tagged_explicit(tbs_content, 0)?;
428        let (ver_int, ver_rest) = reader::read_integer(ver_content)?;
429        if ver_int != [2] || !ver_rest.is_empty() {
430            return None;
431        }
432
433        // serialNumber — strict INTEGER. Negative serials are REJECTED by
434        // the reader (deliberate deviation from RFC 5280's "gracefully
435        // handle"); the returned value bytes are DER-pad-stripped and the
436        // 1..=20 bound applies to those stripped bytes (design §5.4).
437        let (serial, cur) = reader::read_integer(cur)?;
438        if serial.is_empty() || serial.len() > 20 {
439            return None;
440        }
441
442        // inner signature AlgorithmIdentifier (full span kept for the
443        // outer==inner byte-equality rule).
444        let (algid_inner, cur) = read_sm2_sig_algid(cur)?;
445
446        // issuer Name — raw TLV span, no interpretation (Q3.7).
447        let (_, after_issuer) = reader::read_sequence(cur)?;
448        let issuer = &cur[..cur.len() - after_issuer.len()];
449        let cur = after_issuer;
450
451        // validity — SEQUENCE of exactly two Times, body exactly consumed.
452        let (val_content, cur) = reader::read_sequence(cur)?;
453        let (not_before, val_rest) = read_time(val_content)?;
454        let (not_after, val_rest) = read_time(val_rest)?;
455        if !val_rest.is_empty() {
456            return None;
457        }
458
459        // subject Name — raw TLV span.
460        let (_, after_subject) = reader::read_sequence(cur)?;
461        let subject = &cur[..cur.len() - after_subject.len()];
462        let cur = after_subject;
463
464        // subjectPublicKeyInfo — the full TLV span goes to the existing
465        // spki::decode, which enforces id-ecPublicKey + sm2p256v1 + an
466        // on-curve, non-identity point.
467        let (_, after_spki) = reader::read_sequence(cur)?;
468        let spki_span = &cur[..cur.len() - after_spki.len()];
469        let subject_key = crate::spki::decode(spki_span)?;
470        let cur = after_spki;
471
472        // optional issuerUniqueID [1] / subjectUniqueID [2] — skipped.
473        let cur = match reader::read_context_tagged_implicit(cur, 1) {
474            Some((_, r)) => r,
475            None => cur,
476        };
477        let cur = match reader::read_context_tagged_implicit(cur, 2) {
478            Some((_, r)) => r,
479            None => cur,
480        };
481
482        // optional extensions [3] — shape-checked one level deep, kept raw,
483        // NEVER interpreted (critical flags included; design §5.9).
484        let (extensions, cur) = match reader::read_context_tagged_explicit(cur, 3) {
485            Some((ext_content, r)) => {
486                check_extensions_shape(ext_content)?;
487                (Some(ext_content), r)
488            }
489            None => (None, cur),
490        };
491        // tbsCertificate content fully consumed.
492        if !cur.is_empty() {
493            return None;
494        }
495
496        // ---- after tbsCertificate ----
497        // outer AlgorithmIdentifier: full-TLV-span byte equality with the
498        // inner one (design §5.5 — mixed absent/NULL params rejected).
499        let (algid_outer, after_alg) = read_sm2_sig_algid(after_tbs)?;
500        if algid_outer != algid_inner {
501            return None;
502        }
503
504        // signatureValue: BIT STRING with 0 unused bits, last element. The
505        // content's SEQUENCE{r,s} semantics are checked exclusively by
506        // decode_sig inside verify_with_id (design §5.10) — a cert with
507        // garbage here PARSES but never VERIFIES.
508        let (unused, sig, after_sig) = reader::read_bit_string(after_alg)?;
509        if unused != 0 || !after_sig.is_empty() {
510            return None;
511        }
512
513        Some(Self {
514            tbs: tbs_span.to_vec(),
515            serial: serial.to_vec(),
516            issuer: issuer.to_vec(),
517            subject: subject.to_vec(),
518            extensions: extensions.map(<[u8]>::to_vec),
519            sig: sig.to_vec(),
520            not_before,
521            not_after,
522            subject_key,
523        })
524    }
525
526    /// Verify this certificate's SM2-with-SM3 signature against `issuer`
527    /// over the **exact `tbsCertificate` wire bytes**, using the GM /
528    /// RFC 8998 default ID `"1234567812345678"`.
529    ///
530    /// **This is NOT certificate validation.** `true` means exactly "this
531    /// issuer key signed these tbs bytes" — no chain, no time/validity
532    /// check, no keyUsage/basicConstraints/critical-extension evaluation,
533    /// no revocation. See the module docs.
534    #[must_use]
535    pub fn verify_signature(&self, issuer: &Sm2PublicKey) -> bool {
536        self.verify_signature_with_id(issuer, DEFAULT_SIGNER_ID)
537    }
538
539    /// As [`Certificate::verify_signature`], with a caller-supplied SM2 ID
540    /// (RFC 8998 §3.2.1 mandates the default, but CA practice can vary).
541    #[must_use]
542    pub fn verify_signature_with_id(&self, issuer: &Sm2PublicKey, id: &[u8]) -> bool {
543        verify_with_id(issuer, id, &self.tbs, &self.sig)
544    }
545
546    /// The subject's SM2 public key — infallible by construction (the SPKI
547    /// was validated on-curve during parse).
548    #[must_use]
549    pub const fn subject_public_key(&self) -> Sm2PublicKey {
550        self.subject_key
551    }
552
553    /// The exact `TBSCertificate` TLV bytes as they appeared on the wire
554    /// (the bytes the signature covers).
555    #[must_use]
556    pub fn tbs_raw(&self) -> &[u8] {
557        &self.tbs
558    }
559
560    /// The serial number as unsigned big-endian value bytes (the DER
561    /// disambiguating pad is stripped), 1..=20 bytes. Negative serials are
562    /// rejected at parse.
563    #[must_use]
564    pub fn serial_raw(&self) -> &[u8] {
565        &self.serial
566    }
567
568    /// The issuer `Name` as its full raw DER TLV — no interpretation; use
569    /// byte equality for matching (Q3.7).
570    #[must_use]
571    pub fn issuer_raw(&self) -> &[u8] {
572        &self.issuer
573    }
574
575    /// The subject `Name` as its full raw DER TLV — no interpretation.
576    #[must_use]
577    pub fn subject_raw(&self) -> &[u8] {
578        &self.subject
579    }
580
581    /// The `[3]` extensions content (the `Extensions` SEQUENCE TLV) if
582    /// present — shape-checked at parse but NEVER interpreted, `critical`
583    /// flags included. A caller building trust logic on top inherits
584    /// RFC 5280 §4.2's "MUST reject unrecognized critical extensions"
585    /// obligation.
586    #[must_use]
587    pub fn extensions_raw(&self) -> Option<&[u8]> {
588        self.extensions.as_deref()
589    }
590
591    /// `notBefore` — exposed, never compared to a clock (the caller owns
592    /// any validity-period decision).
593    #[must_use]
594    pub const fn not_before(&self) -> X509Time {
595        self.not_before
596    }
597
598    /// `notAfter` — exposed, never compared to a clock.
599    #[must_use]
600    pub const fn not_after(&self) -> X509Time {
601        self.not_after
602    }
603
604    /// Whether `subject` and `issuer` are byte-identical raw DER Names
605    /// (the RFC 5280 self-issued notion, byte-strict). NOT a statement
606    /// that the certificate is self-SIGNED — use
607    /// [`Certificate::verify_signature`] with the cert's own
608    /// [`Certificate::subject_public_key`] for that.
609    #[must_use]
610    pub fn is_self_issued(&self) -> bool {
611        self.issuer == self.subject
612    }
613
614    /// The parsed `keyUsage` extension if present and well-formed, else
615    /// `None` (absent or malformed are not distinguished — a public
616    /// structural read, not a trust-reason channel). NOT a trust decision.
617    #[must_use]
618    pub fn key_usage(&self) -> Option<KeyUsage> {
619        KeyUsage::parse(find_extension(self.extensions.as_deref()?, oid::KEY_USAGE)?)
620    }
621
622    /// The parsed `basicConstraints` extension if present and well-formed,
623    /// else `None`. `path_len` is exposed but NOT enforced by
624    /// [`verify_chain`] (scope D-4). NOT a trust decision.
625    #[must_use]
626    pub fn basic_constraints(&self) -> Option<BasicConstraints> {
627        BasicConstraints::parse(find_extension(
628            self.extensions.as_deref()?,
629            oid::BASIC_CONSTRAINTS,
630        )?)
631    }
632
633    /// Whether the `subject` Name is empty (a `SEQUENCE` with no content).
634    ///
635    /// `pub(crate)` for `tlcp::chain`'s pair-binding check (gated on its sole
636    /// consumer, the `tlcp` feature), so that layer need not re-parse
637    /// certificate DER. The `subject` TLV was validated as a SEQUENCE at
638    /// parse; a decode failure here counts as empty (fail-closed).
639    #[cfg(feature = "tlcp")]
640    pub(crate) fn subject_is_empty(&self) -> bool {
641        reader::read_sequence(&self.subject).is_none_or(|(content, _)| content.is_empty())
642    }
643}
644
645/// Maximum total certificates (leaf + intermediates) in a chain accepted by
646/// [`verify_chain`].
647///
648/// A denial-of-service / over-restriction guard — **NOT** a
649/// `pathLenConstraint` substitute (scope D-4).
650pub const MAX_CHAIN_DEPTH: usize = 8;
651
652/// The extensions this profile PROCESSES: a *critical* extension whose OID
653/// is not here is refused by [`verify_chain`] (scope Q8.7b).
654const KNOWN_EXTS: &[&[u8]] = &[oid::KEY_USAGE, oid::BASIC_CONSTRAINTS];
655
656fn within_window(cert: &Certificate, at: Option<X509Time>) -> bool {
657    at.is_none_or(|t| cert.not_before <= t && t <= cert.not_after)
658}
659
660/// An issuer (intermediate) must be a CA: `basicConstraints CA=TRUE` AND
661/// `keyUsage` present with `keyCertSign` (stricter than gotlcp, which skips
662/// `keyCertSign`; RFC 5280 §4.2.1.3).
663fn is_ca_issuer(cert: &Certificate) -> bool {
664    cert.basic_constraints().is_some_and(|bc| bc.is_ca)
665        && cert.key_usage().is_some_and(KeyUsage::key_cert_sign)
666}
667
668/// Verify a single linear certificate chain links to a caller-trusted
669/// anchor.
670///
671/// `chain` is leaf-first in issuing order (`chain[0]` = the leaf,
672/// `chain[1..]` = intermediates). `anchors` are certificates the caller
673/// **declares** trusted. `at_time` (`Some`) enforces the validity window on
674/// every certificate, including the matched anchor.
675///
676/// Returns `true` iff: there are 1 to [`MAX_CHAIN_DEPTH`] certificates; no
677/// certificate carries an unknown *critical* extension; every adjacent edge
678/// links by raw issuer↔subject Name byte-equality AND its SM2 signature
679/// verifies; every intermediate is a CA (`CA=TRUE` + `keyCertSign`); the
680/// topmost issuer is a trusted anchor (Name match + signature — **every**
681/// same-Name anchor is tried, so CA key-rollover resolves by which key
682/// actually signed); and (if `Some`) every certificate is within its window.
683///
684/// **⚠ This is NOT certificate validation and NOT endpoint authentication.**
685/// It does not check the *leaf's* role keyUsage (that is
686/// `tlcp::chain::verify_pair`'s job), and the matched anchor is trusted by
687/// fiat — checked ONLY by Name + signature + window, never keyUsage / CA /
688/// leaf-role, even when it coincides with the leaf. A `true` says the chain
689/// links to a trusted CA, never "this is the peer I dialed" (endpoint
690/// identity binding is the caller's, permanently). See the module docs.
691#[must_use]
692pub fn verify_chain(
693    chain: &[Certificate],
694    anchors: &[Certificate],
695    at_time: Option<X509Time>,
696) -> bool {
697    let chain: alloc::vec::Vec<&Certificate> = chain.iter().collect();
698    let anchors: alloc::vec::Vec<&Certificate> = anchors.iter().collect();
699    verify_chain_refs(&chain, &anchors, at_time)
700}
701
702/// Reference-slice form of [`verify_chain`] — the canonical implementation.
703///
704/// **Not public API and not SemVer-covered.** It exists so the C FFI shim
705/// (`gmcrypto-c`) can verify an array of certificate *handles* without
706/// fabricating a contiguous `&[Certificate]` (`Certificate` is not `Clone`).
707/// Behaviour is byte-for-byte identical to [`verify_chain`].
708#[doc(hidden)]
709#[must_use]
710pub fn verify_chain_refs(
711    chain: &[&Certificate],
712    anchors: &[&Certificate],
713    at_time: Option<X509Time>,
714) -> bool {
715    if chain.is_empty() || chain.len() > MAX_CHAIN_DEPTH {
716        return false;
717    }
718    for &cert in chain {
719        if cert
720            .extensions
721            .as_deref()
722            .is_some_and(|e| has_unknown_critical(e, KNOWN_EXTS))
723        {
724            return false;
725        }
726        if !within_window(cert, at_time) {
727            return false;
728        }
729    }
730    // Intermediate edges: chain[i] is issued by chain[i+1], which must be a CA.
731    for i in 0..chain.len() - 1 {
732        let (subj, iss) = (chain[i], chain[i + 1]);
733        if subj.issuer_raw() != iss.subject_raw() {
734            return false;
735        }
736        if !subj.verify_signature(&iss.subject_public_key()) {
737            return false;
738        }
739        if !is_ca_issuer(iss) {
740            return false;
741        }
742    }
743    // Top edge: chain.last() must be issued by a trusted anchor. Name match is
744    // necessary, NOT sufficient — try every same-Name anchor, the signature
745    // (and window) decides.
746    let top = chain[chain.len() - 1];
747    anchors.iter().any(|a| {
748        a.subject_raw() == top.issuer_raw()
749            && within_window(a, at_time)
750            && top.verify_signature(&a.subject_public_key())
751    })
752}
753
754/// Test-only certificate minting — shared by `x509`'s `verify_chain` tests
755/// and `tlcp::chain`'s `verify_pair` tests. Builds DER parseable by
756/// [`Certificate::from_der`] and signs the tbs with a real SM2 key.
757#[cfg(test)]
758pub(crate) mod test_support {
759    use crate::sm2::{DEFAULT_SIGNER_ID, Sm2PrivateKey, Sm2PublicKey, sign_with_id};
760    use alloc::vec::Vec;
761    use getrandom::SysRng;
762
763    /// DER TLV with a definite (short- or long-form) length.
764    #[allow(clippy::cast_possible_truncation)]
765    pub fn der(tag: u8, content: &[u8]) -> Vec<u8> {
766        let mut out = alloc::vec![tag];
767        let n = content.len();
768        if n < 128 {
769            out.push(n as u8);
770        } else {
771            let mut len_bytes = Vec::new();
772            let mut v = n;
773            while v > 0 {
774                len_bytes.push((v & 0xff) as u8);
775                v >>= 8;
776            }
777            len_bytes.reverse();
778            out.push(0x80 | len_bytes.len() as u8);
779            out.extend_from_slice(&len_bytes);
780        }
781        out.extend_from_slice(content);
782        out
783    }
784
785    /// A structurally-valid `Name`: a SEQUENCE whose content is `label` (the
786    /// x509 layer treats Names as opaque byte-equal TLVs).
787    pub fn name(label: &[u8]) -> Vec<u8> {
788        der(0x30, label)
789    }
790
791    /// A test SM2 key from a small nonzero scalar seed (always in `[1, n-1]`).
792    pub fn key(seed: u8) -> Sm2PrivateKey {
793        let mut b = [0u8; 32];
794        b[31] = seed.max(1);
795        Option::from(Sm2PrivateKey::from_bytes_be(&b)).expect("valid test scalar")
796    }
797
798    /// A `keyUsage` Extension TLV asserting `bits` (0-based MSB-first).
799    pub fn ku_ext(bits: &[u8], critical: bool) -> Vec<u8> {
800        let mut val = [0u8; 2];
801        for &b in bits {
802            val[(b / 8) as usize] |= 1 << (7 - (b % 8));
803        }
804        let nbytes = usize::from(bits.iter().any(|&b| b >= 8)) + 1;
805        let mut bs = alloc::vec![0u8]; // unused = 0
806        bs.extend_from_slice(&val[..nbytes]);
807        extension(
808            crate::asn1::oid::KEY_USAGE,
809            critical,
810            &der(0x04, &der(0x03, &bs)),
811        )
812    }
813
814    /// A `basicConstraints` Extension TLV.
815    #[allow(clippy::cast_possible_truncation)]
816    pub fn bc_ext(is_ca: bool, path_len: Option<u32>, critical: bool) -> Vec<u8> {
817        let mut seq = Vec::new();
818        if is_ca {
819            seq.extend_from_slice(&[0x01, 0x01, 0xFF]);
820        }
821        if let Some(p) = path_len {
822            seq.extend_from_slice(&der(0x02, &[p as u8]));
823        }
824        extension(
825            crate::asn1::oid::BASIC_CONSTRAINTS,
826            critical,
827            &der(0x04, &der(0x30, &seq)),
828        )
829    }
830
831    /// An arbitrary Extension TLV by raw OID content + inner extnValue bytes.
832    pub fn raw_ext(oid_bytes: &[u8], critical: bool, value_inner: &[u8]) -> Vec<u8> {
833        extension(oid_bytes, critical, &der(0x04, value_inner))
834    }
835
836    fn extension(oid_bytes: &[u8], critical: bool, octet_string_tlv: &[u8]) -> Vec<u8> {
837        let mut body = der(0x06, oid_bytes);
838        if critical {
839            body.extend_from_slice(&[0x01, 0x01, 0xFF]);
840        }
841        body.extend_from_slice(octet_string_tlv);
842        der(0x30, &body)
843    }
844
845    /// Mint a cert DER signed by `issuer_key`, with the given validity dates
846    /// (`YYMMDDHHMMSSZ`). `exts` is the concatenation of Extension TLVs (empty
847    /// ⇒ no `[3]` field).
848    pub fn mint(
849        issuer_key: &Sm2PrivateKey,
850        issuer_name: &[u8],
851        subject_name: &[u8],
852        subject_key: &Sm2PublicKey,
853        exts: &[u8],
854        not_before: &str,
855        not_after: &str,
856    ) -> Vec<u8> {
857        let algid = der(0x30, &der(0x06, crate::asn1::oid::SM2_SIGN_WITH_SM3));
858        let mut tbs_body = der(0xA0, &der(0x02, &[0x02])); // [0] EXPLICIT INTEGER 2
859        tbs_body.extend_from_slice(&der(0x02, &[0x01])); // serial
860        tbs_body.extend_from_slice(&algid);
861        tbs_body.extend_from_slice(issuer_name);
862        let validity = der(
863            0x30,
864            &[
865                der(0x17, not_before.as_bytes()),
866                der(0x17, not_after.as_bytes()),
867            ]
868            .concat(),
869        );
870        tbs_body.extend_from_slice(&validity);
871        tbs_body.extend_from_slice(subject_name);
872        tbs_body.extend_from_slice(&crate::spki::encode(subject_key));
873        if !exts.is_empty() {
874            tbs_body.extend_from_slice(&der(0xA3, &der(0x30, exts)));
875        }
876        let tbs = der(0x30, &tbs_body);
877        let sig = sign_with_id(issuer_key, DEFAULT_SIGNER_ID, &tbs, &mut SysRng).expect("sign tbs");
878        let mut sig_bs = alloc::vec![0u8]; // unused bits = 0
879        sig_bs.extend_from_slice(&sig);
880        let mut cert = tbs;
881        cert.extend_from_slice(&algid);
882        cert.extend_from_slice(&der(0x03, &sig_bs));
883        der(0x30, &cert)
884    }
885
886    /// Mint + parse, panicking on a malformed mint (surfaces minting bugs).
887    pub fn cert(
888        issuer_key: &Sm2PrivateKey,
889        issuer_name: &[u8],
890        subject_name: &[u8],
891        subject_key: &Sm2PublicKey,
892        exts: &[u8],
893    ) -> super::Certificate {
894        let der_bytes = mint(
895            issuer_key,
896            issuer_name,
897            subject_name,
898            subject_key,
899            exts,
900            "260101000000Z",
901            "270101000000Z",
902        );
903        super::Certificate::from_der(&der_bytes).expect("minted cert parses")
904    }
905}
906
907#[cfg(test)]
908mod v1_8_tests {
909    use super::test_support::*;
910    use super::*;
911    use alloc::vec::Vec;
912
913    // ---- KeyUsage reader (private parse) ----
914
915    #[test]
916    fn keyusage_bit_order() {
917        // 03 02 05 A0 : unused=5, value 0xA0 = 1010_0000 → bits 0 (MSB) + 2.
918        let k = KeyUsage::parse(&[0x03, 0x02, 0x05, 0xA0]).unwrap();
919        assert!(k.digital_signature() && k.key_encipherment());
920        assert!(!k.content_commitment() && !k.key_agreement() && !k.key_cert_sign());
921        // 03 03 07 80 80 : bit 0 + bit 8 (decipherOnly).
922        let k2 = KeyUsage::parse(&[0x03, 0x03, 0x07, 0x80, 0x80]).unwrap();
923        assert!(k2.digital_signature() && k2.decipher_only());
924        // malformed: trailing garbage / unused>7.
925        assert!(KeyUsage::parse(&[0x03, 0x02, 0x05, 0xA0, 0x00]).is_none());
926        assert!(KeyUsage::parse(&[0x03, 0x02, 0x08, 0xA0]).is_none());
927    }
928
929    #[test]
930    fn basicconstraints_reader() {
931        let ca = BasicConstraints::parse(&[0x30, 0x03, 0x01, 0x01, 0xFF]).unwrap();
932        assert!(ca.is_ca && ca.path_len.is_none());
933        let ca_pl =
934            BasicConstraints::parse(&[0x30, 0x06, 0x01, 0x01, 0xFF, 0x02, 0x01, 0x00]).unwrap();
935        assert!(ca_pl.is_ca && ca_pl.path_len == Some(0));
936        let empty = BasicConstraints::parse(&[0x30, 0x00]).unwrap();
937        assert!(!empty.is_ca && empty.path_len.is_none());
938        // trailing garbage.
939        assert!(BasicConstraints::parse(&[0x30, 0x03, 0x01, 0x01, 0xFF, 0x00]).is_none());
940    }
941
942    #[test]
943    fn extension_helpers() {
944        let known: &[&[u8]] = &[oid::KEY_USAGE, oid::BASIC_CONSTRAINTS];
945        let ku = ku_ext(&[0], true);
946        let unknown_crit = raw_ext(&[0x55, 0x1d, 0x25], true, &[0x05, 0x00]); // 2.5.29.37 EKU, critical
947        let unknown_noncrit = raw_ext(&[0x55, 0x1d, 0x25], false, &[0x05, 0x00]);
948        let with_crit = der(0x30, &[ku.clone(), unknown_crit].concat());
949        let with_noncrit = der(0x30, &[ku, unknown_noncrit].concat());
950        assert!(find_extension(&with_crit, oid::KEY_USAGE).is_some());
951        assert!(find_extension(&with_crit, oid::BASIC_CONSTRAINTS).is_none());
952        assert!(has_unknown_critical(&with_crit, known));
953        assert!(!has_unknown_critical(&with_noncrit, known));
954    }
955
956    // ---- verify_chain (minted certs) ----
957
958    /// Build a (leaf, int, root) trio sharing the standard linkage.
959    fn trio() -> (Certificate, Certificate, Certificate) {
960        let (rk, ik, lk) = (key(1), key(2), key(3));
961        let (rn, in_, ln) = (name(b"root"), name(b"int"), name(b"leaf"));
962        let ca_exts = [ku_ext(&[5], true), bc_ext(true, None, true)].concat();
963        let root = cert(&rk, &rn, &rn, &rk.public_key(), &ca_exts);
964        let int = cert(&rk, &rn, &in_, &ik.public_key(), &ca_exts);
965        let leaf = cert(&ik, &in_, &ln, &lk.public_key(), &ku_ext(&[0], true));
966        (leaf, int, root)
967    }
968
969    #[test]
970    fn valid_chain_to_anchor() {
971        let (leaf, int, root) = trio();
972        assert!(verify_chain(&[leaf, int], &[root], None));
973    }
974
975    #[test]
976    fn wrong_signing_key_rejected() {
977        // Leaf claims issuer=int but is signed by root's key → edge sig fails.
978        let (rk, ik, lk) = (key(1), key(2), key(3));
979        let (rn, in_, ln) = (name(b"root"), name(b"int"), name(b"leaf"));
980        let ca_exts = [ku_ext(&[5], true), bc_ext(true, None, true)].concat();
981        let root = cert(&rk, &rn, &rn, &rk.public_key(), &ca_exts);
982        let int = cert(&rk, &rn, &in_, &ik.public_key(), &ca_exts);
983        let bad_leaf = cert(&rk, &in_, &ln, &lk.public_key(), &ku_ext(&[0], true));
984        assert!(!verify_chain(&[bad_leaf, int], &[root], None));
985    }
986
987    #[test]
988    fn non_ca_intermediate_rejected() {
989        let (rk, ik, lk) = (key(1), key(2), key(3));
990        let (rn, in_, ln) = (name(b"root"), name(b"int"), name(b"leaf"));
991        let ca_exts = [ku_ext(&[5], true), bc_ext(true, None, true)].concat();
992        let root = cert(&rk, &rn, &rn, &rk.public_key(), &ca_exts);
993        // int lacks CA=TRUE.
994        let int = cert(&rk, &rn, &in_, &ik.public_key(), &ku_ext(&[5], true));
995        let leaf = cert(&ik, &in_, &ln, &lk.public_key(), &ku_ext(&[0], true));
996        assert!(!verify_chain(&[leaf, int], &[root], None));
997    }
998
999    #[test]
1000    fn broken_name_link_rejected() {
1001        let (rk, ik, lk) = (key(1), key(2), key(3));
1002        let (rn, in_, ln) = (name(b"root"), name(b"int"), name(b"leaf"));
1003        let ca_exts = [ku_ext(&[5], true), bc_ext(true, None, true)].concat();
1004        let root = cert(&rk, &rn, &rn, &rk.public_key(), &ca_exts);
1005        let int = cert(&rk, &rn, &in_, &ik.public_key(), &ca_exts);
1006        // leaf's issuer says "other", not "int".
1007        let leaf = cert(
1008            &ik,
1009            &name(b"other"),
1010            &ln,
1011            &lk.public_key(),
1012            &ku_ext(&[0], true),
1013        );
1014        assert!(!verify_chain(&[leaf, int], &[root], None));
1015    }
1016
1017    #[test]
1018    fn over_max_depth_rejected() {
1019        // Length check fires first, so the certs need not link — mint fresh
1020        // self-issued certs past the cap. anchors empty (never reached).
1021        let chain: Vec<Certificate> = (0..=MAX_CHAIN_DEPTH)
1022            .map(|i| {
1023                let k = key(u8::try_from(i + 1).unwrap());
1024                let n = name(b"x");
1025                cert(&k, &n, &n, &k.public_key(), &ku_ext(&[0], true))
1026            })
1027            .collect();
1028        assert!(chain.len() > MAX_CHAIN_DEPTH);
1029        assert!(!verify_chain(&chain, &[], None));
1030    }
1031
1032    #[test]
1033    fn time_window_enforced() {
1034        let (leaf, int, root) = trio();
1035        let nb = leaf.not_before();
1036        let after = X509Time {
1037            year: leaf.not_after().year + 1,
1038            ..leaf.not_after()
1039        };
1040        let chain = [leaf, int];
1041        let anchors = [root];
1042        assert!(verify_chain(&chain, &anchors, Some(nb)));
1043        assert!(!verify_chain(&chain, &anchors, Some(after)));
1044    }
1045
1046    #[test]
1047    fn try_all_anchors_second_valid() {
1048        let (rk, ik, lk) = (key(1), key(2), key(3));
1049        let decoy = key(9);
1050        let (rn, in_, ln) = (name(b"root"), name(b"int"), name(b"leaf"));
1051        let ca_exts = [ku_ext(&[5], true), bc_ext(true, None, true)].concat();
1052        let real_root = cert(&rk, &rn, &rn, &rk.public_key(), &ca_exts);
1053        let decoy_root = cert(&decoy, &rn, &rn, &decoy.public_key(), &ca_exts); // same Name, wrong key
1054        let int = cert(&rk, &rn, &in_, &ik.public_key(), &ca_exts);
1055        let leaf = cert(&ik, &in_, &ln, &lk.public_key(), &ku_ext(&[0], true));
1056        let chain = [leaf, int];
1057        // decoy first, real second → any() must still find the right key.
1058        assert!(verify_chain(&chain, &[decoy_root, real_root], None));
1059        let decoy_only = cert(&decoy, &rn, &rn, &decoy.public_key(), &ca_exts);
1060        assert!(!verify_chain(&chain, &[decoy_only], None));
1061    }
1062
1063    #[test]
1064    fn unknown_critical_extension_rejected() {
1065        let (rk, ik, lk) = (key(1), key(2), key(3));
1066        let (rn, in_, ln) = (name(b"root"), name(b"int"), name(b"leaf"));
1067        let ca_exts = [ku_ext(&[5], true), bc_ext(true, None, true)].concat();
1068        let root = cert(&rk, &rn, &rn, &rk.public_key(), &ca_exts);
1069        let int = cert(&rk, &rn, &in_, &ik.public_key(), &ca_exts);
1070        let anchors = [root];
1071        // 2.5.29.37 (EKU) marked CRITICAL → refused.
1072        let crit = [
1073            ku_ext(&[0], true),
1074            raw_ext(&[0x55, 0x1d, 0x25], true, &[0x05, 0x00]),
1075        ]
1076        .concat();
1077        let leaf_crit = cert(&ik, &in_, &ln, &lk.public_key(), &crit);
1078        let int2 = cert(&rk, &rn, &in_, &ik.public_key(), &ca_exts);
1079        assert!(!verify_chain(&[leaf_crit, int2], &anchors, None));
1080        // same OID NON-critical → ignored.
1081        let noncrit = [
1082            ku_ext(&[0], true),
1083            raw_ext(&[0x55, 0x1d, 0x25], false, &[0x05, 0x00]),
1084        ]
1085        .concat();
1086        let leaf_ok = cert(&ik, &in_, &ln, &lk.public_key(), &noncrit);
1087        assert!(verify_chain(&[leaf_ok, int], &anchors, None));
1088    }
1089
1090    #[test]
1091    fn self_signed_leaf_as_own_anchor() {
1092        // S2 confirmation: an anchor coinciding with the leaf is Name+sig only.
1093        let sk = key(7);
1094        let sn = name(b"self");
1095        let leaf = cert(&sk, &sn, &sn, &sk.public_key(), &ku_ext(&[0], true));
1096        let anchor = cert(&sk, &sn, &sn, &sk.public_key(), &ku_ext(&[0], true));
1097        assert!(verify_chain(&[leaf], &[anchor], None));
1098    }
1099}
1100
1101#[cfg(test)]
1102mod tests {
1103    use super::*;
1104    use alloc::vec::Vec;
1105
1106    fn tlv(tag: u8, content: &[u8]) -> Vec<u8> {
1107        assert!(content.len() < 128, "test helper: short-form lengths only");
1108        let mut out = alloc::vec![tag, u8::try_from(content.len()).unwrap()];
1109        out.extend_from_slice(content);
1110        out
1111    }
1112
1113    // ---- read_time ----
1114
1115    fn utc(s: &str) -> Vec<u8> {
1116        tlv(TAG_UTC_TIME, s.as_bytes())
1117    }
1118    fn gtime(s: &str) -> Vec<u8> {
1119        tlv(TAG_GENERALIZED_TIME, s.as_bytes())
1120    }
1121
1122    #[test]
1123    fn time_utctime_parses() {
1124        let der = utc("260611120000Z");
1125        let (t, rest) = read_time(&der).unwrap();
1126        assert!(rest.is_empty());
1127        assert_eq!(
1128            t,
1129            X509Time {
1130                year: 2026,
1131                month: 6,
1132                day: 11,
1133                hour: 12,
1134                minute: 0,
1135                second: 0
1136            }
1137        );
1138    }
1139
1140    #[test]
1141    fn time_utctime_pivot() {
1142        assert_eq!(read_time(&utc("500101000000Z")).unwrap().0.year, 1950);
1143        assert_eq!(read_time(&utc("490101000000Z")).unwrap().0.year, 2049);
1144    }
1145
1146    #[test]
1147    fn time_generalizedtime_parses() {
1148        let (t, _) = read_time(&gtime("20991231235959Z")).unwrap();
1149        assert_eq!(
1150            t,
1151            X509Time {
1152                year: 2099,
1153                month: 12,
1154                day: 31,
1155                hour: 23,
1156                minute: 59,
1157                second: 59
1158            }
1159        );
1160    }
1161
1162    #[test]
1163    fn time_ordering_is_chronological() {
1164        let (a, _) = read_time(&utc("260611120000Z")).unwrap();
1165        let (b, _) = read_time(&utc("260611120001Z")).unwrap();
1166        let (c, _) = read_time(&gtime("20991231235959Z")).unwrap();
1167        assert!(a < b && b < c);
1168    }
1169
1170    #[test]
1171    fn time_rejects_malformed() {
1172        for bad in [
1173            utc("260611120000"),           // wrong length (no Z, 12 chars)
1174            utc("2606111200000"),          // 13 chars but last not 'Z'
1175            utc("26061112000xZ"),          // non-digit
1176            utc("261311120000Z"),          // month 13
1177            utc("260600120000Z"),          // day 0... (day 0 rejected)
1178            utc("260611240000Z"),          // hour 24
1179            utc("260611126000Z"),          // minute 60
1180            utc("260611120060Z"),          // second 60
1181            gtime("20260611120000+0800Z"), // offset / wrong length
1182            gtime("2026061112000.5Z"),     // fraction / wrong shape
1183            tlv(0x16, b"260611120000Z"),   // wrong tag
1184        ] {
1185            assert!(read_time(&bad).is_none(), "accepted {bad:02x?}");
1186        }
1187    }
1188
1189    // ---- read_sm2_sig_algid ----
1190
1191    fn algid(params_null: bool) -> Vec<u8> {
1192        // oid::SM2_SIGN_WITH_SM3 is the sub-identifier CONTENT bytes (the
1193        // oid-module convention); the 06 LEN framing is added here.
1194        let mut body = tlv(0x06, oid::SM2_SIGN_WITH_SM3);
1195        if params_null {
1196            body.extend_from_slice(&[0x05, 0x00]);
1197        }
1198        tlv(0x30, &body)
1199    }
1200
1201    #[test]
1202    fn algid_absent_and_null_params_accepted() {
1203        for null in [false, true] {
1204            let a = algid(null);
1205            let (span, rest) = read_sm2_sig_algid(&a).expect("valid algid rejected");
1206            assert_eq!(span, &a[..]);
1207            assert!(rest.is_empty());
1208        }
1209    }
1210
1211    #[test]
1212    fn algid_mixed_forms_are_unequal_spans() {
1213        // The full-span byte-equality rule rejects a mixed absent/NULL pair
1214        // even though each form is individually acceptable (design §5.5).
1215        let absent = algid(false);
1216        let null = algid(true);
1217        let (s1, _) = read_sm2_sig_algid(&absent).unwrap();
1218        let (s2, _) = read_sm2_sig_algid(&null).unwrap();
1219        assert_ne!(s1, s2);
1220    }
1221
1222    #[test]
1223    fn algid_rejects_wrong_oid_and_bad_params() {
1224        // ecdsa-with-SHA256 OID instead.
1225        let wrong = tlv(
1226            0x30,
1227            &tlv(0x06, &[0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02]),
1228        );
1229        assert!(read_sm2_sig_algid(&wrong).is_none());
1230        // params = empty SEQUENCE instead of NULL.
1231        let mut body = tlv(0x06, oid::SM2_SIGN_WITH_SM3);
1232        body.extend_from_slice(&[0x30, 0x00]);
1233        assert!(read_sm2_sig_algid(&tlv(0x30, &body)).is_none());
1234        // trailing garbage after NULL.
1235        let mut body = tlv(0x06, oid::SM2_SIGN_WITH_SM3);
1236        body.extend_from_slice(&[0x05, 0x00, 0x00]);
1237        assert!(read_sm2_sig_algid(&tlv(0x30, &body)).is_none());
1238    }
1239
1240    // ---- check_extensions_shape ----
1241
1242    fn extension(oid_content: &[u8], critical: Option<u8>, value: &[u8]) -> Vec<u8> {
1243        let mut body = tlv(0x06, oid_content);
1244        if let Some(b) = critical {
1245            body.extend_from_slice(&tlv(TAG_BOOLEAN, &[b]));
1246        }
1247        body.extend_from_slice(&tlv(0x04, value));
1248        tlv(0x30, &body)
1249    }
1250
1251    #[test]
1252    fn extensions_shape_ok() {
1253        let e1 = extension(&[0x55, 0x1d, 0x0f], Some(0xFF), &[0x03, 0x02, 0x01, 0x06]);
1254        let e2 = extension(&[0x55, 0x1d, 0x13], None, &[0x30, 0x00]);
1255        let mut both = e1;
1256        both.extend_from_slice(&e2);
1257        assert!(check_extensions_shape(&tlv(0x30, &both)).is_some());
1258    }
1259
1260    #[test]
1261    fn extensions_shape_rejects() {
1262        // Empty Extensions SEQUENCE (SIZE(1..) violated).
1263        assert!(check_extensions_shape(&tlv(0x30, &[])).is_none());
1264        // Element that is not an Extension SEQUENCE.
1265        assert!(check_extensions_shape(&tlv(0x30, &tlv(0x04, &[0x00]))).is_none());
1266        // Extension with trailing garbage after extnValue.
1267        let mut bad = tlv(0x06, &[0x55, 0x1d, 0x0f]);
1268        bad.extend_from_slice(&tlv(0x04, &[0x00]));
1269        bad.push(0x00);
1270        assert!(check_extensions_shape(&tlv(0x30, &tlv(0x30, &bad))).is_none());
1271        // Trailing bytes after the Extensions SEQUENCE.
1272        let ok = extension(&[0x55, 0x1d, 0x0f], None, &[0x00]);
1273        let mut outer = tlv(0x30, &ok);
1274        outer.push(0x00);
1275        assert!(check_extensions_shape(&outer).is_none());
1276    }
1277}