extern crate alloc;
use crate::asn1::oid;
use crate::asn1::reader;
use crate::sm2::{DEFAULT_SIGNER_ID, Sm2PublicKey, verify_with_id};
use alloc::vec::Vec;
const TAG_UTC_TIME: u8 = 0x17;
const TAG_GENERALIZED_TIME: u8 = 0x18;
const TAG_BOOLEAN: u8 = 0x01;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct X509Time {
pub year: u16,
pub month: u8,
pub day: u8,
pub hour: u8,
pub minute: u8,
pub second: u8,
}
fn two_digits(b: &[u8]) -> Option<u8> {
match b {
[a @ b'0'..=b'9', c @ b'0'..=b'9'] => Some((a - b'0') * 10 + (c - b'0')),
_ => None,
}
}
fn read_time(input: &[u8]) -> Option<(X509Time, &[u8])> {
let (year, body, rest) = if let Some((v, rest)) = reader::read_tlv(input, TAG_UTC_TIME) {
if v.len() != 13 {
return None;
}
let yy = u16::from(two_digits(&v[0..2])?);
(if yy >= 50 { 1900 + yy } else { 2000 + yy }, &v[2..], rest)
} else {
let (v, rest) = reader::read_tlv(input, TAG_GENERALIZED_TIME)?;
if v.len() != 15 {
return None;
}
(
u16::from(two_digits(&v[0..2])?) * 100 + u16::from(two_digits(&v[2..4])?),
&v[4..],
rest,
)
};
if body.len() != 11 || body[10] != b'Z' {
return None;
}
let (month, day) = (two_digits(&body[0..2])?, two_digits(&body[2..4])?);
let (hour, minute, second) = (
two_digits(&body[4..6])?,
two_digits(&body[6..8])?,
two_digits(&body[8..10])?,
);
if !(1..=12).contains(&month)
|| !(1..=31).contains(&day)
|| hour > 23
|| minute > 59
|| second > 59
{
return None;
}
Some((
X509Time {
year,
month,
day,
hour,
minute,
second,
},
rest,
))
}
fn read_sm2_sig_algid(input: &[u8]) -> Option<(&[u8], &[u8])> {
let (body, rest) = reader::read_sequence(input)?;
let span = &input[..input.len() - rest.len()];
let (oid_bytes, after_oid) = reader::read_oid(body)?;
if oid_bytes != oid::SM2_SIGN_WITH_SM3 {
return None;
}
if after_oid.is_empty() {
Some((span, rest))
} else {
let after_null = reader::read_null(after_oid)?;
if after_null.is_empty() {
Some((span, rest))
} else {
None
}
}
}
fn check_extensions_shape(content: &[u8]) -> Option<()> {
let (seq, rest) = reader::read_sequence(content)?;
if !rest.is_empty() || seq.is_empty() {
return None;
}
let mut exts = seq;
while !exts.is_empty() {
let (ext, r) = reader::read_sequence(exts)?;
let (_extn_id, after_oid) = reader::read_oid(ext)?;
let after_bool = match reader::read_tlv(after_oid, TAG_BOOLEAN) {
Some((b, rb)) => {
if b.len() != 1 {
return None;
}
rb
}
None => after_oid,
};
let (_extn_value, after_value) = reader::read_octet_string(after_bool)?;
if !after_value.is_empty() {
return None;
}
exts = r;
}
Some(())
}
pub struct Certificate {
tbs: Vec<u8>,
serial: Vec<u8>,
issuer: Vec<u8>,
subject: Vec<u8>,
extensions: Option<Vec<u8>>,
sig: Vec<u8>,
not_before: X509Time,
not_after: X509Time,
subject_key: Sm2PublicKey,
}
impl Certificate {
#[must_use]
pub fn from_der(der: &[u8]) -> Option<Self> {
let (cert, rest) = reader::read_sequence(der)?;
if !rest.is_empty() {
return None;
}
let (tbs_content, after_tbs) = reader::read_sequence(cert)?;
let tbs_span = &cert[..cert.len() - after_tbs.len()];
let (ver_content, cur) = reader::read_context_tagged_explicit(tbs_content, 0)?;
let (ver_int, ver_rest) = reader::read_integer(ver_content)?;
if ver_int != [2] || !ver_rest.is_empty() {
return None;
}
let (serial, cur) = reader::read_integer(cur)?;
if serial.is_empty() || serial.len() > 20 {
return None;
}
let (algid_inner, cur) = read_sm2_sig_algid(cur)?;
let (_, after_issuer) = reader::read_sequence(cur)?;
let issuer = &cur[..cur.len() - after_issuer.len()];
let cur = after_issuer;
let (val_content, cur) = reader::read_sequence(cur)?;
let (not_before, val_rest) = read_time(val_content)?;
let (not_after, val_rest) = read_time(val_rest)?;
if !val_rest.is_empty() {
return None;
}
let (_, after_subject) = reader::read_sequence(cur)?;
let subject = &cur[..cur.len() - after_subject.len()];
let cur = after_subject;
let (_, after_spki) = reader::read_sequence(cur)?;
let spki_span = &cur[..cur.len() - after_spki.len()];
let subject_key = crate::spki::decode(spki_span)?;
let cur = after_spki;
let cur = match reader::read_context_tagged_implicit(cur, 1) {
Some((_, r)) => r,
None => cur,
};
let cur = match reader::read_context_tagged_implicit(cur, 2) {
Some((_, r)) => r,
None => cur,
};
let (extensions, cur) = match reader::read_context_tagged_explicit(cur, 3) {
Some((ext_content, r)) => {
check_extensions_shape(ext_content)?;
(Some(ext_content), r)
}
None => (None, cur),
};
if !cur.is_empty() {
return None;
}
let (algid_outer, after_alg) = read_sm2_sig_algid(after_tbs)?;
if algid_outer != algid_inner {
return None;
}
let (unused, sig, after_sig) = reader::read_bit_string(after_alg)?;
if unused != 0 || !after_sig.is_empty() {
return None;
}
Some(Self {
tbs: tbs_span.to_vec(),
serial: serial.to_vec(),
issuer: issuer.to_vec(),
subject: subject.to_vec(),
extensions: extensions.map(<[u8]>::to_vec),
sig: sig.to_vec(),
not_before,
not_after,
subject_key,
})
}
#[must_use]
pub fn verify_signature(&self, issuer: &Sm2PublicKey) -> bool {
self.verify_signature_with_id(issuer, DEFAULT_SIGNER_ID)
}
#[must_use]
pub fn verify_signature_with_id(&self, issuer: &Sm2PublicKey, id: &[u8]) -> bool {
verify_with_id(issuer, id, &self.tbs, &self.sig)
}
#[must_use]
pub const fn subject_public_key(&self) -> Sm2PublicKey {
self.subject_key
}
#[must_use]
pub fn tbs_raw(&self) -> &[u8] {
&self.tbs
}
#[must_use]
pub fn serial_raw(&self) -> &[u8] {
&self.serial
}
#[must_use]
pub fn issuer_raw(&self) -> &[u8] {
&self.issuer
}
#[must_use]
pub fn subject_raw(&self) -> &[u8] {
&self.subject
}
#[must_use]
pub fn extensions_raw(&self) -> Option<&[u8]> {
self.extensions.as_deref()
}
#[must_use]
pub const fn not_before(&self) -> X509Time {
self.not_before
}
#[must_use]
pub const fn not_after(&self) -> X509Time {
self.not_after
}
#[must_use]
pub fn is_self_issued(&self) -> bool {
self.issuer == self.subject
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec::Vec;
fn tlv(tag: u8, content: &[u8]) -> Vec<u8> {
assert!(content.len() < 128, "test helper: short-form lengths only");
let mut out = alloc::vec![tag, u8::try_from(content.len()).unwrap()];
out.extend_from_slice(content);
out
}
fn utc(s: &str) -> Vec<u8> {
tlv(TAG_UTC_TIME, s.as_bytes())
}
fn gtime(s: &str) -> Vec<u8> {
tlv(TAG_GENERALIZED_TIME, s.as_bytes())
}
#[test]
fn time_utctime_parses() {
let der = utc("260611120000Z");
let (t, rest) = read_time(&der).unwrap();
assert!(rest.is_empty());
assert_eq!(
t,
X509Time {
year: 2026,
month: 6,
day: 11,
hour: 12,
minute: 0,
second: 0
}
);
}
#[test]
fn time_utctime_pivot() {
assert_eq!(read_time(&utc("500101000000Z")).unwrap().0.year, 1950);
assert_eq!(read_time(&utc("490101000000Z")).unwrap().0.year, 2049);
}
#[test]
fn time_generalizedtime_parses() {
let (t, _) = read_time(>ime("20991231235959Z")).unwrap();
assert_eq!(
t,
X509Time {
year: 2099,
month: 12,
day: 31,
hour: 23,
minute: 59,
second: 59
}
);
}
#[test]
fn time_ordering_is_chronological() {
let (a, _) = read_time(&utc("260611120000Z")).unwrap();
let (b, _) = read_time(&utc("260611120001Z")).unwrap();
let (c, _) = read_time(>ime("20991231235959Z")).unwrap();
assert!(a < b && b < c);
}
#[test]
fn time_rejects_malformed() {
for bad in [
utc("260611120000"), utc("2606111200000"), utc("26061112000xZ"), utc("261311120000Z"), utc("260600120000Z"), utc("260611240000Z"), utc("260611126000Z"), utc("260611120060Z"), gtime("20260611120000+0800Z"), gtime("2026061112000.5Z"), tlv(0x16, b"260611120000Z"), ] {
assert!(read_time(&bad).is_none(), "accepted {bad:02x?}");
}
}
fn algid(params_null: bool) -> Vec<u8> {
let mut body = tlv(0x06, oid::SM2_SIGN_WITH_SM3);
if params_null {
body.extend_from_slice(&[0x05, 0x00]);
}
tlv(0x30, &body)
}
#[test]
fn algid_absent_and_null_params_accepted() {
for null in [false, true] {
let a = algid(null);
let (span, rest) = read_sm2_sig_algid(&a).expect("valid algid rejected");
assert_eq!(span, &a[..]);
assert!(rest.is_empty());
}
}
#[test]
fn algid_mixed_forms_are_unequal_spans() {
let absent = algid(false);
let null = algid(true);
let (s1, _) = read_sm2_sig_algid(&absent).unwrap();
let (s2, _) = read_sm2_sig_algid(&null).unwrap();
assert_ne!(s1, s2);
}
#[test]
fn algid_rejects_wrong_oid_and_bad_params() {
let wrong = tlv(
0x30,
&tlv(0x06, &[0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02]),
);
assert!(read_sm2_sig_algid(&wrong).is_none());
let mut body = tlv(0x06, oid::SM2_SIGN_WITH_SM3);
body.extend_from_slice(&[0x30, 0x00]);
assert!(read_sm2_sig_algid(&tlv(0x30, &body)).is_none());
let mut body = tlv(0x06, oid::SM2_SIGN_WITH_SM3);
body.extend_from_slice(&[0x05, 0x00, 0x00]);
assert!(read_sm2_sig_algid(&tlv(0x30, &body)).is_none());
}
fn extension(oid_content: &[u8], critical: Option<u8>, value: &[u8]) -> Vec<u8> {
let mut body = tlv(0x06, oid_content);
if let Some(b) = critical {
body.extend_from_slice(&tlv(TAG_BOOLEAN, &[b]));
}
body.extend_from_slice(&tlv(0x04, value));
tlv(0x30, &body)
}
#[test]
fn extensions_shape_ok() {
let e1 = extension(&[0x55, 0x1d, 0x0f], Some(0xFF), &[0x03, 0x02, 0x01, 0x06]);
let e2 = extension(&[0x55, 0x1d, 0x13], None, &[0x30, 0x00]);
let mut both = e1;
both.extend_from_slice(&e2);
assert!(check_extensions_shape(&tlv(0x30, &both)).is_some());
}
#[test]
fn extensions_shape_rejects() {
assert!(check_extensions_shape(&tlv(0x30, &[])).is_none());
assert!(check_extensions_shape(&tlv(0x30, &tlv(0x04, &[0x00]))).is_none());
let mut bad = tlv(0x06, &[0x55, 0x1d, 0x0f]);
bad.extend_from_slice(&tlv(0x04, &[0x00]));
bad.push(0x00);
assert!(check_extensions_shape(&tlv(0x30, &tlv(0x30, &bad))).is_none());
let ok = extension(&[0x55, 0x1d, 0x0f], None, &[0x00]);
let mut outer = tlv(0x30, &ok);
outer.push(0x00);
assert!(check_extensions_shape(&outer).is_none());
}
}