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}
288
289/// The X.509 `basicConstraints` extension (RFC 5280 §4.2.1.9): the CA flag
290/// plus an optional path-length constraint.
291///
292/// `path_len` is **parsed but NOT enforced** by [`verify_chain`] — v1.8 uses
293/// a fixed [`MAX_CHAIN_DEPTH`] cap instead (scope D-4).
294#[derive(Clone, Copy, Debug, PartialEq, Eq)]
295pub struct BasicConstraints {
296    /// The `cA` boolean (DEFAULT `FALSE`).
297    pub is_ca: bool,
298    /// The `pathLenConstraint`, if present. Parsed, not enforced.
299    pub path_len: Option<u32>,
300}
301
302impl BasicConstraints {
303    /// Parse the DER `SEQUENCE { cA BOOLEAN DEFAULT FALSE,
304    /// pathLenConstraint INTEGER OPTIONAL }` (the `extnValue` content of a
305    /// `basicConstraints` extension). `None` on malformed input.
306    fn parse(seq_tlv: &[u8]) -> Option<Self> {
307        let (content, rest) = reader::read_sequence(seq_tlv)?;
308        if !rest.is_empty() {
309            return None;
310        }
311        let (is_ca, after) = match reader::read_tlv(content, TAG_BOOLEAN) {
312            Some((b, r)) => {
313                if b.len() != 1 {
314                    return None;
315                }
316                (b[0] != 0, r)
317            }
318            None => (false, content),
319        };
320        let path_len = if after.is_empty() {
321            None
322        } else {
323            let (int, r) = reader::read_integer(after)?;
324            if !r.is_empty() || int.len() > 4 {
325                return None;
326            }
327            let mut v = 0u32;
328            for &byte in int {
329                v = (v << 8) | u32::from(byte);
330            }
331            Some(v)
332        };
333        Some(Self { is_ca, path_len })
334    }
335}
336
337/// Find the extension with `extn_id` in the `Extensions` SEQUENCE TLV,
338/// returning its `extnValue` content. `None` if absent. The blob was
339/// already shape-checked by [`Certificate::from_der`]; this re-walk is
340/// defensive (returns `None` on any malformed element). Returns the FIRST
341/// match (RFC 5280 forbids duplicate extensions; `from_der` does not dedup).
342fn find_extension<'a>(ext_tlv: &'a [u8], extn_id: &[u8]) -> Option<&'a [u8]> {
343    let (seq, _) = reader::read_sequence(ext_tlv)?;
344    let mut exts = seq;
345    while !exts.is_empty() {
346        let ext = next_extension(exts)?;
347        if ext.oid == extn_id {
348            return Some(ext.value);
349        }
350        exts = ext.rest;
351    }
352    None
353}
354
355/// `true` iff any extension is `critical` AND its `extnID` is not in `known`
356/// (RFC 5280 §4.2 — refuse a critical constraint we do not process; scope
357/// Q8.7b). Fail-closed on malformed input is not needed (`from_der` already
358/// shape-checked) but the walk is defensive.
359fn has_unknown_critical(ext_tlv: &[u8], known: &[&[u8]]) -> bool {
360    let Some((seq, _)) = reader::read_sequence(ext_tlv) else {
361        return false;
362    };
363    let mut exts = seq;
364    while !exts.is_empty() {
365        let Some(ext) = next_extension(exts) else {
366            return false;
367        };
368        if ext.critical && !known.contains(&ext.oid) {
369            return true;
370        }
371        exts = ext.rest;
372    }
373    false
374}
375
376/// A parsed X.509 v3 certificate (GM/T 0015 profile: SM2-with-SM3
377/// signature, sm2p256v1 SPKI).
378///
379/// Parsing makes **no trust decisions** — see the module docs for the full
380/// list of what this type deliberately does NOT do. Field bytes are owned
381/// copies of the exact wire encoding.
382pub struct Certificate {
383    tbs: Vec<u8>,
384    serial: Vec<u8>,
385    issuer: Vec<u8>,
386    subject: Vec<u8>,
387    extensions: Option<Vec<u8>>,
388    sig: Vec<u8>,
389    not_before: X509Time,
390    not_after: X509Time,
391    subject_key: Sm2PublicKey,
392}
393
394impl Certificate {
395    /// Strict-DER parse of an X.509 **v3** certificate in the accepted
396    /// profile (module docs). Single `None` for EVERY malformed or
397    /// out-of-profile input — the workspace failure-mode invariant.
398    ///
399    /// Parsing performs **no trust decisions**; a `Some` means only "the
400    /// bytes frame as a well-formed certificate in the accepted profile and
401    /// carry a valid SM2 subject public key".
402    #[must_use]
403    pub fn from_der(der: &[u8]) -> Option<Self> {
404        let (cert, rest) = reader::read_sequence(der)?;
405        if !rest.is_empty() {
406            return None;
407        }
408
409        // tbsCertificate — capture the exact wire TLV span (design §5.2);
410        // verification never re-encodes.
411        let (tbs_content, after_tbs) = reader::read_sequence(cert)?;
412        let tbs_span = &cert[..cert.len() - after_tbs.len()];
413
414        // ---- inside tbsCertificate ----
415        // version [0] EXPLICIT INTEGER, value exactly 2 (v3 only, Q3.11);
416        // the wrapper content must contain ONLY the INTEGER.
417        let (ver_content, cur) = reader::read_context_tagged_explicit(tbs_content, 0)?;
418        let (ver_int, ver_rest) = reader::read_integer(ver_content)?;
419        if ver_int != [2] || !ver_rest.is_empty() {
420            return None;
421        }
422
423        // serialNumber — strict INTEGER. Negative serials are REJECTED by
424        // the reader (deliberate deviation from RFC 5280's "gracefully
425        // handle"); the returned value bytes are DER-pad-stripped and the
426        // 1..=20 bound applies to those stripped bytes (design §5.4).
427        let (serial, cur) = reader::read_integer(cur)?;
428        if serial.is_empty() || serial.len() > 20 {
429            return None;
430        }
431
432        // inner signature AlgorithmIdentifier (full span kept for the
433        // outer==inner byte-equality rule).
434        let (algid_inner, cur) = read_sm2_sig_algid(cur)?;
435
436        // issuer Name — raw TLV span, no interpretation (Q3.7).
437        let (_, after_issuer) = reader::read_sequence(cur)?;
438        let issuer = &cur[..cur.len() - after_issuer.len()];
439        let cur = after_issuer;
440
441        // validity — SEQUENCE of exactly two Times, body exactly consumed.
442        let (val_content, cur) = reader::read_sequence(cur)?;
443        let (not_before, val_rest) = read_time(val_content)?;
444        let (not_after, val_rest) = read_time(val_rest)?;
445        if !val_rest.is_empty() {
446            return None;
447        }
448
449        // subject Name — raw TLV span.
450        let (_, after_subject) = reader::read_sequence(cur)?;
451        let subject = &cur[..cur.len() - after_subject.len()];
452        let cur = after_subject;
453
454        // subjectPublicKeyInfo — the full TLV span goes to the existing
455        // spki::decode, which enforces id-ecPublicKey + sm2p256v1 + an
456        // on-curve, non-identity point.
457        let (_, after_spki) = reader::read_sequence(cur)?;
458        let spki_span = &cur[..cur.len() - after_spki.len()];
459        let subject_key = crate::spki::decode(spki_span)?;
460        let cur = after_spki;
461
462        // optional issuerUniqueID [1] / subjectUniqueID [2] — skipped.
463        let cur = match reader::read_context_tagged_implicit(cur, 1) {
464            Some((_, r)) => r,
465            None => cur,
466        };
467        let cur = match reader::read_context_tagged_implicit(cur, 2) {
468            Some((_, r)) => r,
469            None => cur,
470        };
471
472        // optional extensions [3] — shape-checked one level deep, kept raw,
473        // NEVER interpreted (critical flags included; design §5.9).
474        let (extensions, cur) = match reader::read_context_tagged_explicit(cur, 3) {
475            Some((ext_content, r)) => {
476                check_extensions_shape(ext_content)?;
477                (Some(ext_content), r)
478            }
479            None => (None, cur),
480        };
481        // tbsCertificate content fully consumed.
482        if !cur.is_empty() {
483            return None;
484        }
485
486        // ---- after tbsCertificate ----
487        // outer AlgorithmIdentifier: full-TLV-span byte equality with the
488        // inner one (design §5.5 — mixed absent/NULL params rejected).
489        let (algid_outer, after_alg) = read_sm2_sig_algid(after_tbs)?;
490        if algid_outer != algid_inner {
491            return None;
492        }
493
494        // signatureValue: BIT STRING with 0 unused bits, last element. The
495        // content's SEQUENCE{r,s} semantics are checked exclusively by
496        // decode_sig inside verify_with_id (design §5.10) — a cert with
497        // garbage here PARSES but never VERIFIES.
498        let (unused, sig, after_sig) = reader::read_bit_string(after_alg)?;
499        if unused != 0 || !after_sig.is_empty() {
500            return None;
501        }
502
503        Some(Self {
504            tbs: tbs_span.to_vec(),
505            serial: serial.to_vec(),
506            issuer: issuer.to_vec(),
507            subject: subject.to_vec(),
508            extensions: extensions.map(<[u8]>::to_vec),
509            sig: sig.to_vec(),
510            not_before,
511            not_after,
512            subject_key,
513        })
514    }
515
516    /// Verify this certificate's SM2-with-SM3 signature against `issuer`
517    /// over the **exact `tbsCertificate` wire bytes**, using the GM /
518    /// RFC 8998 default ID `"1234567812345678"`.
519    ///
520    /// **This is NOT certificate validation.** `true` means exactly "this
521    /// issuer key signed these tbs bytes" — no chain, no time/validity
522    /// check, no keyUsage/basicConstraints/critical-extension evaluation,
523    /// no revocation. See the module docs.
524    #[must_use]
525    pub fn verify_signature(&self, issuer: &Sm2PublicKey) -> bool {
526        self.verify_signature_with_id(issuer, DEFAULT_SIGNER_ID)
527    }
528
529    /// As [`Certificate::verify_signature`], with a caller-supplied SM2 ID
530    /// (RFC 8998 §3.2.1 mandates the default, but CA practice can vary).
531    #[must_use]
532    pub fn verify_signature_with_id(&self, issuer: &Sm2PublicKey, id: &[u8]) -> bool {
533        verify_with_id(issuer, id, &self.tbs, &self.sig)
534    }
535
536    /// The subject's SM2 public key — infallible by construction (the SPKI
537    /// was validated on-curve during parse).
538    #[must_use]
539    pub const fn subject_public_key(&self) -> Sm2PublicKey {
540        self.subject_key
541    }
542
543    /// The exact `TBSCertificate` TLV bytes as they appeared on the wire
544    /// (the bytes the signature covers).
545    #[must_use]
546    pub fn tbs_raw(&self) -> &[u8] {
547        &self.tbs
548    }
549
550    /// The serial number as unsigned big-endian value bytes (the DER
551    /// disambiguating pad is stripped), 1..=20 bytes. Negative serials are
552    /// rejected at parse.
553    #[must_use]
554    pub fn serial_raw(&self) -> &[u8] {
555        &self.serial
556    }
557
558    /// The issuer `Name` as its full raw DER TLV — no interpretation; use
559    /// byte equality for matching (Q3.7).
560    #[must_use]
561    pub fn issuer_raw(&self) -> &[u8] {
562        &self.issuer
563    }
564
565    /// The subject `Name` as its full raw DER TLV — no interpretation.
566    #[must_use]
567    pub fn subject_raw(&self) -> &[u8] {
568        &self.subject
569    }
570
571    /// The `[3]` extensions content (the `Extensions` SEQUENCE TLV) if
572    /// present — shape-checked at parse but NEVER interpreted, `critical`
573    /// flags included. A caller building trust logic on top inherits
574    /// RFC 5280 §4.2's "MUST reject unrecognized critical extensions"
575    /// obligation.
576    #[must_use]
577    pub fn extensions_raw(&self) -> Option<&[u8]> {
578        self.extensions.as_deref()
579    }
580
581    /// `notBefore` — exposed, never compared to a clock (the caller owns
582    /// any validity-period decision).
583    #[must_use]
584    pub const fn not_before(&self) -> X509Time {
585        self.not_before
586    }
587
588    /// `notAfter` — exposed, never compared to a clock.
589    #[must_use]
590    pub const fn not_after(&self) -> X509Time {
591        self.not_after
592    }
593
594    /// Whether `subject` and `issuer` are byte-identical raw DER Names
595    /// (the RFC 5280 self-issued notion, byte-strict). NOT a statement
596    /// that the certificate is self-SIGNED — use
597    /// [`Certificate::verify_signature`] with the cert's own
598    /// [`Certificate::subject_public_key`] for that.
599    #[must_use]
600    pub fn is_self_issued(&self) -> bool {
601        self.issuer == self.subject
602    }
603
604    /// The parsed `keyUsage` extension if present and well-formed, else
605    /// `None` (absent or malformed are not distinguished — a public
606    /// structural read, not a trust-reason channel). NOT a trust decision.
607    #[must_use]
608    pub fn key_usage(&self) -> Option<KeyUsage> {
609        KeyUsage::parse(find_extension(self.extensions.as_deref()?, oid::KEY_USAGE)?)
610    }
611
612    /// The parsed `basicConstraints` extension if present and well-formed,
613    /// else `None`. `path_len` is exposed but NOT enforced by
614    /// [`verify_chain`] (scope D-4). NOT a trust decision.
615    #[must_use]
616    pub fn basic_constraints(&self) -> Option<BasicConstraints> {
617        BasicConstraints::parse(find_extension(
618            self.extensions.as_deref()?,
619            oid::BASIC_CONSTRAINTS,
620        )?)
621    }
622
623    /// Whether the `subject` Name is empty (a `SEQUENCE` with no content).
624    ///
625    /// `pub(crate)` for `tlcp::chain`'s pair-binding check (gated on its sole
626    /// consumer, the `tlcp` feature), so that layer need not re-parse
627    /// certificate DER. The `subject` TLV was validated as a SEQUENCE at
628    /// parse; a decode failure here counts as empty (fail-closed).
629    #[cfg(feature = "tlcp")]
630    pub(crate) fn subject_is_empty(&self) -> bool {
631        reader::read_sequence(&self.subject).is_none_or(|(content, _)| content.is_empty())
632    }
633}
634
635/// Maximum total certificates (leaf + intermediates) in a chain accepted by
636/// [`verify_chain`].
637///
638/// A denial-of-service / over-restriction guard — **NOT** a
639/// `pathLenConstraint` substitute (scope D-4).
640pub const MAX_CHAIN_DEPTH: usize = 8;
641
642/// The extensions this profile PROCESSES: a *critical* extension whose OID
643/// is not here is refused by [`verify_chain`] (scope Q8.7b).
644const KNOWN_EXTS: &[&[u8]] = &[oid::KEY_USAGE, oid::BASIC_CONSTRAINTS];
645
646fn within_window(cert: &Certificate, at: Option<X509Time>) -> bool {
647    at.is_none_or(|t| cert.not_before <= t && t <= cert.not_after)
648}
649
650/// An issuer (intermediate) must be a CA: `basicConstraints CA=TRUE` AND
651/// `keyUsage` present with `keyCertSign` (stricter than gotlcp, which skips
652/// `keyCertSign`; RFC 5280 §4.2.1.3).
653fn is_ca_issuer(cert: &Certificate) -> bool {
654    cert.basic_constraints().is_some_and(|bc| bc.is_ca)
655        && cert.key_usage().is_some_and(KeyUsage::key_cert_sign)
656}
657
658/// Verify a single linear certificate chain links to a caller-trusted
659/// anchor.
660///
661/// `chain` is leaf-first in issuing order (`chain[0]` = the leaf,
662/// `chain[1..]` = intermediates). `anchors` are certificates the caller
663/// **declares** trusted. `at_time` (`Some`) enforces the validity window on
664/// every certificate, including the matched anchor.
665///
666/// Returns `true` iff: there are 1 to [`MAX_CHAIN_DEPTH`] certificates; no
667/// certificate carries an unknown *critical* extension; every adjacent edge
668/// links by raw issuer↔subject Name byte-equality AND its SM2 signature
669/// verifies; every intermediate is a CA (`CA=TRUE` + `keyCertSign`); the
670/// topmost issuer is a trusted anchor (Name match + signature — **every**
671/// same-Name anchor is tried, so CA key-rollover resolves by which key
672/// actually signed); and (if `Some`) every certificate is within its window.
673///
674/// **⚠ This is NOT certificate validation and NOT endpoint authentication.**
675/// It does not check the *leaf's* role keyUsage (that is
676/// `tlcp::chain::verify_pair`'s job), and the matched anchor is trusted by
677/// fiat — checked ONLY by Name + signature + window, never keyUsage / CA /
678/// leaf-role, even when it coincides with the leaf. A `true` says the chain
679/// links to a trusted CA, never "this is the peer I dialed" (endpoint
680/// identity binding is the caller's, permanently). See the module docs.
681#[must_use]
682pub fn verify_chain(
683    chain: &[Certificate],
684    anchors: &[Certificate],
685    at_time: Option<X509Time>,
686) -> bool {
687    if chain.is_empty() || chain.len() > MAX_CHAIN_DEPTH {
688        return false;
689    }
690    for cert in chain {
691        if cert
692            .extensions
693            .as_deref()
694            .is_some_and(|e| has_unknown_critical(e, KNOWN_EXTS))
695        {
696            return false;
697        }
698        if !within_window(cert, at_time) {
699            return false;
700        }
701    }
702    // Intermediate edges: chain[i] is issued by chain[i+1], which must be a CA.
703    for i in 0..chain.len() - 1 {
704        let (subj, iss) = (&chain[i], &chain[i + 1]);
705        if subj.issuer_raw() != iss.subject_raw() {
706            return false;
707        }
708        if !subj.verify_signature(&iss.subject_public_key()) {
709            return false;
710        }
711        if !is_ca_issuer(iss) {
712            return false;
713        }
714    }
715    // Top edge: chain.last() must be issued by a trusted anchor. Name match is
716    // necessary, NOT sufficient — try every same-Name anchor, the signature
717    // (and window) decides.
718    let top = &chain[chain.len() - 1];
719    anchors.iter().any(|a| {
720        a.subject_raw() == top.issuer_raw()
721            && within_window(a, at_time)
722            && top.verify_signature(&a.subject_public_key())
723    })
724}
725
726/// Test-only certificate minting — shared by `x509`'s `verify_chain` tests
727/// and `tlcp::chain`'s `verify_pair` tests. Builds DER parseable by
728/// [`Certificate::from_der`] and signs the tbs with a real SM2 key.
729#[cfg(test)]
730pub(crate) mod test_support {
731    use crate::sm2::{DEFAULT_SIGNER_ID, Sm2PrivateKey, Sm2PublicKey, sign_with_id};
732    use alloc::vec::Vec;
733    use getrandom::SysRng;
734
735    /// DER TLV with a definite (short- or long-form) length.
736    #[allow(clippy::cast_possible_truncation)]
737    pub fn der(tag: u8, content: &[u8]) -> Vec<u8> {
738        let mut out = alloc::vec![tag];
739        let n = content.len();
740        if n < 128 {
741            out.push(n as u8);
742        } else {
743            let mut len_bytes = Vec::new();
744            let mut v = n;
745            while v > 0 {
746                len_bytes.push((v & 0xff) as u8);
747                v >>= 8;
748            }
749            len_bytes.reverse();
750            out.push(0x80 | len_bytes.len() as u8);
751            out.extend_from_slice(&len_bytes);
752        }
753        out.extend_from_slice(content);
754        out
755    }
756
757    /// A structurally-valid `Name`: a SEQUENCE whose content is `label` (the
758    /// x509 layer treats Names as opaque byte-equal TLVs).
759    pub fn name(label: &[u8]) -> Vec<u8> {
760        der(0x30, label)
761    }
762
763    /// A test SM2 key from a small nonzero scalar seed (always in `[1, n-1]`).
764    pub fn key(seed: u8) -> Sm2PrivateKey {
765        let mut b = [0u8; 32];
766        b[31] = seed.max(1);
767        Option::from(Sm2PrivateKey::from_bytes_be(&b)).expect("valid test scalar")
768    }
769
770    /// A `keyUsage` Extension TLV asserting `bits` (0-based MSB-first).
771    pub fn ku_ext(bits: &[u8], critical: bool) -> Vec<u8> {
772        let mut val = [0u8; 2];
773        for &b in bits {
774            val[(b / 8) as usize] |= 1 << (7 - (b % 8));
775        }
776        let nbytes = usize::from(bits.iter().any(|&b| b >= 8)) + 1;
777        let mut bs = alloc::vec![0u8]; // unused = 0
778        bs.extend_from_slice(&val[..nbytes]);
779        extension(
780            crate::asn1::oid::KEY_USAGE,
781            critical,
782            &der(0x04, &der(0x03, &bs)),
783        )
784    }
785
786    /// A `basicConstraints` Extension TLV.
787    #[allow(clippy::cast_possible_truncation)]
788    pub fn bc_ext(is_ca: bool, path_len: Option<u32>, critical: bool) -> Vec<u8> {
789        let mut seq = Vec::new();
790        if is_ca {
791            seq.extend_from_slice(&[0x01, 0x01, 0xFF]);
792        }
793        if let Some(p) = path_len {
794            seq.extend_from_slice(&der(0x02, &[p as u8]));
795        }
796        extension(
797            crate::asn1::oid::BASIC_CONSTRAINTS,
798            critical,
799            &der(0x04, &der(0x30, &seq)),
800        )
801    }
802
803    /// An arbitrary Extension TLV by raw OID content + inner extnValue bytes.
804    pub fn raw_ext(oid_bytes: &[u8], critical: bool, value_inner: &[u8]) -> Vec<u8> {
805        extension(oid_bytes, critical, &der(0x04, value_inner))
806    }
807
808    fn extension(oid_bytes: &[u8], critical: bool, octet_string_tlv: &[u8]) -> Vec<u8> {
809        let mut body = der(0x06, oid_bytes);
810        if critical {
811            body.extend_from_slice(&[0x01, 0x01, 0xFF]);
812        }
813        body.extend_from_slice(octet_string_tlv);
814        der(0x30, &body)
815    }
816
817    /// Mint a cert DER signed by `issuer_key`, with the given validity dates
818    /// (`YYMMDDHHMMSSZ`). `exts` is the concatenation of Extension TLVs (empty
819    /// ⇒ no `[3]` field).
820    pub fn mint(
821        issuer_key: &Sm2PrivateKey,
822        issuer_name: &[u8],
823        subject_name: &[u8],
824        subject_key: &Sm2PublicKey,
825        exts: &[u8],
826        not_before: &str,
827        not_after: &str,
828    ) -> Vec<u8> {
829        let algid = der(0x30, &der(0x06, crate::asn1::oid::SM2_SIGN_WITH_SM3));
830        let mut tbs_body = der(0xA0, &der(0x02, &[0x02])); // [0] EXPLICIT INTEGER 2
831        tbs_body.extend_from_slice(&der(0x02, &[0x01])); // serial
832        tbs_body.extend_from_slice(&algid);
833        tbs_body.extend_from_slice(issuer_name);
834        let validity = der(
835            0x30,
836            &[
837                der(0x17, not_before.as_bytes()),
838                der(0x17, not_after.as_bytes()),
839            ]
840            .concat(),
841        );
842        tbs_body.extend_from_slice(&validity);
843        tbs_body.extend_from_slice(subject_name);
844        tbs_body.extend_from_slice(&crate::spki::encode(subject_key));
845        if !exts.is_empty() {
846            tbs_body.extend_from_slice(&der(0xA3, &der(0x30, exts)));
847        }
848        let tbs = der(0x30, &tbs_body);
849        let sig = sign_with_id(issuer_key, DEFAULT_SIGNER_ID, &tbs, &mut SysRng).expect("sign tbs");
850        let mut sig_bs = alloc::vec![0u8]; // unused bits = 0
851        sig_bs.extend_from_slice(&sig);
852        let mut cert = tbs;
853        cert.extend_from_slice(&algid);
854        cert.extend_from_slice(&der(0x03, &sig_bs));
855        der(0x30, &cert)
856    }
857
858    /// Mint + parse, panicking on a malformed mint (surfaces minting bugs).
859    pub fn cert(
860        issuer_key: &Sm2PrivateKey,
861        issuer_name: &[u8],
862        subject_name: &[u8],
863        subject_key: &Sm2PublicKey,
864        exts: &[u8],
865    ) -> super::Certificate {
866        let der_bytes = mint(
867            issuer_key,
868            issuer_name,
869            subject_name,
870            subject_key,
871            exts,
872            "260101000000Z",
873            "270101000000Z",
874        );
875        super::Certificate::from_der(&der_bytes).expect("minted cert parses")
876    }
877}
878
879#[cfg(test)]
880mod v1_8_tests {
881    use super::test_support::*;
882    use super::*;
883    use alloc::vec::Vec;
884
885    // ---- KeyUsage reader (private parse) ----
886
887    #[test]
888    fn keyusage_bit_order() {
889        // 03 02 05 A0 : unused=5, value 0xA0 = 1010_0000 → bits 0 (MSB) + 2.
890        let k = KeyUsage::parse(&[0x03, 0x02, 0x05, 0xA0]).unwrap();
891        assert!(k.digital_signature() && k.key_encipherment());
892        assert!(!k.content_commitment() && !k.key_agreement() && !k.key_cert_sign());
893        // 03 03 07 80 80 : bit 0 + bit 8 (decipherOnly).
894        let k2 = KeyUsage::parse(&[0x03, 0x03, 0x07, 0x80, 0x80]).unwrap();
895        assert!(k2.digital_signature() && k2.decipher_only());
896        // malformed: trailing garbage / unused>7.
897        assert!(KeyUsage::parse(&[0x03, 0x02, 0x05, 0xA0, 0x00]).is_none());
898        assert!(KeyUsage::parse(&[0x03, 0x02, 0x08, 0xA0]).is_none());
899    }
900
901    #[test]
902    fn basicconstraints_reader() {
903        let ca = BasicConstraints::parse(&[0x30, 0x03, 0x01, 0x01, 0xFF]).unwrap();
904        assert!(ca.is_ca && ca.path_len.is_none());
905        let ca_pl =
906            BasicConstraints::parse(&[0x30, 0x06, 0x01, 0x01, 0xFF, 0x02, 0x01, 0x00]).unwrap();
907        assert!(ca_pl.is_ca && ca_pl.path_len == Some(0));
908        let empty = BasicConstraints::parse(&[0x30, 0x00]).unwrap();
909        assert!(!empty.is_ca && empty.path_len.is_none());
910        // trailing garbage.
911        assert!(BasicConstraints::parse(&[0x30, 0x03, 0x01, 0x01, 0xFF, 0x00]).is_none());
912    }
913
914    #[test]
915    fn extension_helpers() {
916        let known: &[&[u8]] = &[oid::KEY_USAGE, oid::BASIC_CONSTRAINTS];
917        let ku = ku_ext(&[0], true);
918        let unknown_crit = raw_ext(&[0x55, 0x1d, 0x25], true, &[0x05, 0x00]); // 2.5.29.37 EKU, critical
919        let unknown_noncrit = raw_ext(&[0x55, 0x1d, 0x25], false, &[0x05, 0x00]);
920        let with_crit = der(0x30, &[ku.clone(), unknown_crit].concat());
921        let with_noncrit = der(0x30, &[ku, unknown_noncrit].concat());
922        assert!(find_extension(&with_crit, oid::KEY_USAGE).is_some());
923        assert!(find_extension(&with_crit, oid::BASIC_CONSTRAINTS).is_none());
924        assert!(has_unknown_critical(&with_crit, known));
925        assert!(!has_unknown_critical(&with_noncrit, known));
926    }
927
928    // ---- verify_chain (minted certs) ----
929
930    /// Build a (leaf, int, root) trio sharing the standard linkage.
931    fn trio() -> (Certificate, Certificate, Certificate) {
932        let (rk, ik, lk) = (key(1), key(2), key(3));
933        let (rn, in_, ln) = (name(b"root"), name(b"int"), name(b"leaf"));
934        let ca_exts = [ku_ext(&[5], true), bc_ext(true, None, true)].concat();
935        let root = cert(&rk, &rn, &rn, &rk.public_key(), &ca_exts);
936        let int = cert(&rk, &rn, &in_, &ik.public_key(), &ca_exts);
937        let leaf = cert(&ik, &in_, &ln, &lk.public_key(), &ku_ext(&[0], true));
938        (leaf, int, root)
939    }
940
941    #[test]
942    fn valid_chain_to_anchor() {
943        let (leaf, int, root) = trio();
944        assert!(verify_chain(&[leaf, int], &[root], None));
945    }
946
947    #[test]
948    fn wrong_signing_key_rejected() {
949        // Leaf claims issuer=int but is signed by root's key → edge sig fails.
950        let (rk, ik, lk) = (key(1), key(2), key(3));
951        let (rn, in_, ln) = (name(b"root"), name(b"int"), name(b"leaf"));
952        let ca_exts = [ku_ext(&[5], true), bc_ext(true, None, true)].concat();
953        let root = cert(&rk, &rn, &rn, &rk.public_key(), &ca_exts);
954        let int = cert(&rk, &rn, &in_, &ik.public_key(), &ca_exts);
955        let bad_leaf = cert(&rk, &in_, &ln, &lk.public_key(), &ku_ext(&[0], true));
956        assert!(!verify_chain(&[bad_leaf, int], &[root], None));
957    }
958
959    #[test]
960    fn non_ca_intermediate_rejected() {
961        let (rk, ik, lk) = (key(1), key(2), key(3));
962        let (rn, in_, ln) = (name(b"root"), name(b"int"), name(b"leaf"));
963        let ca_exts = [ku_ext(&[5], true), bc_ext(true, None, true)].concat();
964        let root = cert(&rk, &rn, &rn, &rk.public_key(), &ca_exts);
965        // int lacks CA=TRUE.
966        let int = cert(&rk, &rn, &in_, &ik.public_key(), &ku_ext(&[5], true));
967        let leaf = cert(&ik, &in_, &ln, &lk.public_key(), &ku_ext(&[0], true));
968        assert!(!verify_chain(&[leaf, int], &[root], None));
969    }
970
971    #[test]
972    fn broken_name_link_rejected() {
973        let (rk, ik, lk) = (key(1), key(2), key(3));
974        let (rn, in_, ln) = (name(b"root"), name(b"int"), name(b"leaf"));
975        let ca_exts = [ku_ext(&[5], true), bc_ext(true, None, true)].concat();
976        let root = cert(&rk, &rn, &rn, &rk.public_key(), &ca_exts);
977        let int = cert(&rk, &rn, &in_, &ik.public_key(), &ca_exts);
978        // leaf's issuer says "other", not "int".
979        let leaf = cert(
980            &ik,
981            &name(b"other"),
982            &ln,
983            &lk.public_key(),
984            &ku_ext(&[0], true),
985        );
986        assert!(!verify_chain(&[leaf, int], &[root], None));
987    }
988
989    #[test]
990    fn over_max_depth_rejected() {
991        // Length check fires first, so the certs need not link — mint fresh
992        // self-issued certs past the cap. anchors empty (never reached).
993        let chain: Vec<Certificate> = (0..=MAX_CHAIN_DEPTH)
994            .map(|i| {
995                let k = key(u8::try_from(i + 1).unwrap());
996                let n = name(b"x");
997                cert(&k, &n, &n, &k.public_key(), &ku_ext(&[0], true))
998            })
999            .collect();
1000        assert!(chain.len() > MAX_CHAIN_DEPTH);
1001        assert!(!verify_chain(&chain, &[], None));
1002    }
1003
1004    #[test]
1005    fn time_window_enforced() {
1006        let (leaf, int, root) = trio();
1007        let nb = leaf.not_before();
1008        let after = X509Time {
1009            year: leaf.not_after().year + 1,
1010            ..leaf.not_after()
1011        };
1012        let chain = [leaf, int];
1013        let anchors = [root];
1014        assert!(verify_chain(&chain, &anchors, Some(nb)));
1015        assert!(!verify_chain(&chain, &anchors, Some(after)));
1016    }
1017
1018    #[test]
1019    fn try_all_anchors_second_valid() {
1020        let (rk, ik, lk) = (key(1), key(2), key(3));
1021        let decoy = key(9);
1022        let (rn, in_, ln) = (name(b"root"), name(b"int"), name(b"leaf"));
1023        let ca_exts = [ku_ext(&[5], true), bc_ext(true, None, true)].concat();
1024        let real_root = cert(&rk, &rn, &rn, &rk.public_key(), &ca_exts);
1025        let decoy_root = cert(&decoy, &rn, &rn, &decoy.public_key(), &ca_exts); // same Name, wrong key
1026        let int = cert(&rk, &rn, &in_, &ik.public_key(), &ca_exts);
1027        let leaf = cert(&ik, &in_, &ln, &lk.public_key(), &ku_ext(&[0], true));
1028        let chain = [leaf, int];
1029        // decoy first, real second → any() must still find the right key.
1030        assert!(verify_chain(&chain, &[decoy_root, real_root], None));
1031        let decoy_only = cert(&decoy, &rn, &rn, &decoy.public_key(), &ca_exts);
1032        assert!(!verify_chain(&chain, &[decoy_only], None));
1033    }
1034
1035    #[test]
1036    fn unknown_critical_extension_rejected() {
1037        let (rk, ik, lk) = (key(1), key(2), key(3));
1038        let (rn, in_, ln) = (name(b"root"), name(b"int"), name(b"leaf"));
1039        let ca_exts = [ku_ext(&[5], true), bc_ext(true, None, true)].concat();
1040        let root = cert(&rk, &rn, &rn, &rk.public_key(), &ca_exts);
1041        let int = cert(&rk, &rn, &in_, &ik.public_key(), &ca_exts);
1042        let anchors = [root];
1043        // 2.5.29.37 (EKU) marked CRITICAL → refused.
1044        let crit = [
1045            ku_ext(&[0], true),
1046            raw_ext(&[0x55, 0x1d, 0x25], true, &[0x05, 0x00]),
1047        ]
1048        .concat();
1049        let leaf_crit = cert(&ik, &in_, &ln, &lk.public_key(), &crit);
1050        let int2 = cert(&rk, &rn, &in_, &ik.public_key(), &ca_exts);
1051        assert!(!verify_chain(&[leaf_crit, int2], &anchors, None));
1052        // same OID NON-critical → ignored.
1053        let noncrit = [
1054            ku_ext(&[0], true),
1055            raw_ext(&[0x55, 0x1d, 0x25], false, &[0x05, 0x00]),
1056        ]
1057        .concat();
1058        let leaf_ok = cert(&ik, &in_, &ln, &lk.public_key(), &noncrit);
1059        assert!(verify_chain(&[leaf_ok, int], &anchors, None));
1060    }
1061
1062    #[test]
1063    fn self_signed_leaf_as_own_anchor() {
1064        // S2 confirmation: an anchor coinciding with the leaf is Name+sig only.
1065        let sk = key(7);
1066        let sn = name(b"self");
1067        let leaf = cert(&sk, &sn, &sn, &sk.public_key(), &ku_ext(&[0], true));
1068        let anchor = cert(&sk, &sn, &sn, &sk.public_key(), &ku_ext(&[0], true));
1069        assert!(verify_chain(&[leaf], &[anchor], None));
1070    }
1071}
1072
1073#[cfg(test)]
1074mod tests {
1075    use super::*;
1076    use alloc::vec::Vec;
1077
1078    fn tlv(tag: u8, content: &[u8]) -> Vec<u8> {
1079        assert!(content.len() < 128, "test helper: short-form lengths only");
1080        let mut out = alloc::vec![tag, u8::try_from(content.len()).unwrap()];
1081        out.extend_from_slice(content);
1082        out
1083    }
1084
1085    // ---- read_time ----
1086
1087    fn utc(s: &str) -> Vec<u8> {
1088        tlv(TAG_UTC_TIME, s.as_bytes())
1089    }
1090    fn gtime(s: &str) -> Vec<u8> {
1091        tlv(TAG_GENERALIZED_TIME, s.as_bytes())
1092    }
1093
1094    #[test]
1095    fn time_utctime_parses() {
1096        let der = utc("260611120000Z");
1097        let (t, rest) = read_time(&der).unwrap();
1098        assert!(rest.is_empty());
1099        assert_eq!(
1100            t,
1101            X509Time {
1102                year: 2026,
1103                month: 6,
1104                day: 11,
1105                hour: 12,
1106                minute: 0,
1107                second: 0
1108            }
1109        );
1110    }
1111
1112    #[test]
1113    fn time_utctime_pivot() {
1114        assert_eq!(read_time(&utc("500101000000Z")).unwrap().0.year, 1950);
1115        assert_eq!(read_time(&utc("490101000000Z")).unwrap().0.year, 2049);
1116    }
1117
1118    #[test]
1119    fn time_generalizedtime_parses() {
1120        let (t, _) = read_time(&gtime("20991231235959Z")).unwrap();
1121        assert_eq!(
1122            t,
1123            X509Time {
1124                year: 2099,
1125                month: 12,
1126                day: 31,
1127                hour: 23,
1128                minute: 59,
1129                second: 59
1130            }
1131        );
1132    }
1133
1134    #[test]
1135    fn time_ordering_is_chronological() {
1136        let (a, _) = read_time(&utc("260611120000Z")).unwrap();
1137        let (b, _) = read_time(&utc("260611120001Z")).unwrap();
1138        let (c, _) = read_time(&gtime("20991231235959Z")).unwrap();
1139        assert!(a < b && b < c);
1140    }
1141
1142    #[test]
1143    fn time_rejects_malformed() {
1144        for bad in [
1145            utc("260611120000"),           // wrong length (no Z, 12 chars)
1146            utc("2606111200000"),          // 13 chars but last not 'Z'
1147            utc("26061112000xZ"),          // non-digit
1148            utc("261311120000Z"),          // month 13
1149            utc("260600120000Z"),          // day 0... (day 0 rejected)
1150            utc("260611240000Z"),          // hour 24
1151            utc("260611126000Z"),          // minute 60
1152            utc("260611120060Z"),          // second 60
1153            gtime("20260611120000+0800Z"), // offset / wrong length
1154            gtime("2026061112000.5Z"),     // fraction / wrong shape
1155            tlv(0x16, b"260611120000Z"),   // wrong tag
1156        ] {
1157            assert!(read_time(&bad).is_none(), "accepted {bad:02x?}");
1158        }
1159    }
1160
1161    // ---- read_sm2_sig_algid ----
1162
1163    fn algid(params_null: bool) -> Vec<u8> {
1164        // oid::SM2_SIGN_WITH_SM3 is the sub-identifier CONTENT bytes (the
1165        // oid-module convention); the 06 LEN framing is added here.
1166        let mut body = tlv(0x06, oid::SM2_SIGN_WITH_SM3);
1167        if params_null {
1168            body.extend_from_slice(&[0x05, 0x00]);
1169        }
1170        tlv(0x30, &body)
1171    }
1172
1173    #[test]
1174    fn algid_absent_and_null_params_accepted() {
1175        for null in [false, true] {
1176            let a = algid(null);
1177            let (span, rest) = read_sm2_sig_algid(&a).expect("valid algid rejected");
1178            assert_eq!(span, &a[..]);
1179            assert!(rest.is_empty());
1180        }
1181    }
1182
1183    #[test]
1184    fn algid_mixed_forms_are_unequal_spans() {
1185        // The full-span byte-equality rule rejects a mixed absent/NULL pair
1186        // even though each form is individually acceptable (design §5.5).
1187        let absent = algid(false);
1188        let null = algid(true);
1189        let (s1, _) = read_sm2_sig_algid(&absent).unwrap();
1190        let (s2, _) = read_sm2_sig_algid(&null).unwrap();
1191        assert_ne!(s1, s2);
1192    }
1193
1194    #[test]
1195    fn algid_rejects_wrong_oid_and_bad_params() {
1196        // ecdsa-with-SHA256 OID instead.
1197        let wrong = tlv(
1198            0x30,
1199            &tlv(0x06, &[0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02]),
1200        );
1201        assert!(read_sm2_sig_algid(&wrong).is_none());
1202        // params = empty SEQUENCE instead of NULL.
1203        let mut body = tlv(0x06, oid::SM2_SIGN_WITH_SM3);
1204        body.extend_from_slice(&[0x30, 0x00]);
1205        assert!(read_sm2_sig_algid(&tlv(0x30, &body)).is_none());
1206        // trailing garbage after NULL.
1207        let mut body = tlv(0x06, oid::SM2_SIGN_WITH_SM3);
1208        body.extend_from_slice(&[0x05, 0x00, 0x00]);
1209        assert!(read_sm2_sig_algid(&tlv(0x30, &body)).is_none());
1210    }
1211
1212    // ---- check_extensions_shape ----
1213
1214    fn extension(oid_content: &[u8], critical: Option<u8>, value: &[u8]) -> Vec<u8> {
1215        let mut body = tlv(0x06, oid_content);
1216        if let Some(b) = critical {
1217            body.extend_from_slice(&tlv(TAG_BOOLEAN, &[b]));
1218        }
1219        body.extend_from_slice(&tlv(0x04, value));
1220        tlv(0x30, &body)
1221    }
1222
1223    #[test]
1224    fn extensions_shape_ok() {
1225        let e1 = extension(&[0x55, 0x1d, 0x0f], Some(0xFF), &[0x03, 0x02, 0x01, 0x06]);
1226        let e2 = extension(&[0x55, 0x1d, 0x13], None, &[0x30, 0x00]);
1227        let mut both = e1;
1228        both.extend_from_slice(&e2);
1229        assert!(check_extensions_shape(&tlv(0x30, &both)).is_some());
1230    }
1231
1232    #[test]
1233    fn extensions_shape_rejects() {
1234        // Empty Extensions SEQUENCE (SIZE(1..) violated).
1235        assert!(check_extensions_shape(&tlv(0x30, &[])).is_none());
1236        // Element that is not an Extension SEQUENCE.
1237        assert!(check_extensions_shape(&tlv(0x30, &tlv(0x04, &[0x00]))).is_none());
1238        // Extension with trailing garbage after extnValue.
1239        let mut bad = tlv(0x06, &[0x55, 0x1d, 0x0f]);
1240        bad.extend_from_slice(&tlv(0x04, &[0x00]));
1241        bad.push(0x00);
1242        assert!(check_extensions_shape(&tlv(0x30, &tlv(0x30, &bad))).is_none());
1243        // Trailing bytes after the Extensions SEQUENCE.
1244        let ok = extension(&[0x55, 0x1d, 0x0f], None, &[0x00]);
1245        let mut outer = tlv(0x30, &ok);
1246        outer.push(0x00);
1247        assert!(check_extensions_shape(&outer).is_none());
1248    }
1249}