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
}
}
}
struct ParsedExt<'a> {
oid: &'a [u8],
critical: bool,
value: &'a [u8],
rest: &'a [u8],
}
fn next_extension(exts: &[u8]) -> Option<ParsedExt<'_>> {
let (ext, rest) = reader::read_sequence(exts)?;
let (oid, after_oid) = reader::read_oid(ext)?;
let (critical, after_bool) = match reader::read_tlv(after_oid, TAG_BOOLEAN) {
Some((b, rb)) => {
if b.len() != 1 {
return None;
}
(b[0] != 0, rb)
}
None => (false, after_oid),
};
let (value, after_value) = reader::read_octet_string(after_bool)?;
if !after_value.is_empty() {
return None;
}
Some(ParsedExt {
oid,
critical,
value,
rest,
})
}
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() {
exts = next_extension(exts)?.rest;
}
Some(())
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct KeyUsage {
bits: u16,
}
impl KeyUsage {
fn parse(bit_string_tlv: &[u8]) -> Option<Self> {
let (unused, value, rest) = reader::read_bit_string(bit_string_tlv)?;
if !rest.is_empty() || unused > 7 {
return None;
}
let mut bits = 0u16;
for i in 0u16..9 {
let (byte, off) = ((i / 8) as usize, 7 - (i % 8));
if byte < value.len() && (value[byte] >> off) & 1 == 1 {
bits |= 1 << i;
}
}
Some(Self { bits })
}
const fn has(self, i: u16) -> bool {
self.bits & (1 << i) != 0
}
#[must_use]
pub const fn digital_signature(self) -> bool {
self.has(0)
}
#[must_use]
pub const fn content_commitment(self) -> bool {
self.has(1)
}
#[must_use]
pub const fn key_encipherment(self) -> bool {
self.has(2)
}
#[must_use]
pub const fn data_encipherment(self) -> bool {
self.has(3)
}
#[must_use]
pub const fn key_agreement(self) -> bool {
self.has(4)
}
#[must_use]
pub const fn key_cert_sign(self) -> bool {
self.has(5)
}
#[must_use]
pub const fn crl_sign(self) -> bool {
self.has(6)
}
#[must_use]
pub const fn encipher_only(self) -> bool {
self.has(7)
}
#[must_use]
pub const fn decipher_only(self) -> bool {
self.has(8)
}
#[doc(hidden)]
#[must_use]
pub const fn bits(&self) -> u16 {
self.bits
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct BasicConstraints {
pub is_ca: bool,
pub path_len: Option<u32>,
}
impl BasicConstraints {
fn parse(seq_tlv: &[u8]) -> Option<Self> {
let (content, rest) = reader::read_sequence(seq_tlv)?;
if !rest.is_empty() {
return None;
}
let (is_ca, after) = match reader::read_tlv(content, TAG_BOOLEAN) {
Some((b, r)) => {
if b.len() != 1 {
return None;
}
(b[0] != 0, r)
}
None => (false, content),
};
let path_len = if after.is_empty() {
None
} else {
let (int, r) = reader::read_integer(after)?;
if !r.is_empty() || int.len() > 4 {
return None;
}
let mut v = 0u32;
for &byte in int {
v = (v << 8) | u32::from(byte);
}
Some(v)
};
Some(Self { is_ca, path_len })
}
}
fn find_extension<'a>(ext_tlv: &'a [u8], extn_id: &[u8]) -> Option<&'a [u8]> {
let (seq, _) = reader::read_sequence(ext_tlv)?;
let mut exts = seq;
while !exts.is_empty() {
let ext = next_extension(exts)?;
if ext.oid == extn_id {
return Some(ext.value);
}
exts = ext.rest;
}
None
}
fn has_unknown_critical(ext_tlv: &[u8], known: &[&[u8]]) -> bool {
let Some((seq, _)) = reader::read_sequence(ext_tlv) else {
return false;
};
let mut exts = seq;
while !exts.is_empty() {
let Some(ext) = next_extension(exts) else {
return false;
};
if ext.critical && !known.contains(&ext.oid) {
return true;
}
exts = ext.rest;
}
false
}
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
}
#[must_use]
pub fn key_usage(&self) -> Option<KeyUsage> {
KeyUsage::parse(find_extension(self.extensions.as_deref()?, oid::KEY_USAGE)?)
}
#[must_use]
pub fn basic_constraints(&self) -> Option<BasicConstraints> {
BasicConstraints::parse(find_extension(
self.extensions.as_deref()?,
oid::BASIC_CONSTRAINTS,
)?)
}
#[cfg(feature = "tlcp")]
pub(crate) fn subject_is_empty(&self) -> bool {
reader::read_sequence(&self.subject).is_none_or(|(content, _)| content.is_empty())
}
}
pub const MAX_CHAIN_DEPTH: usize = 8;
const KNOWN_EXTS: &[&[u8]] = &[oid::KEY_USAGE, oid::BASIC_CONSTRAINTS];
fn within_window(cert: &Certificate, at: Option<X509Time>) -> bool {
at.is_none_or(|t| cert.not_before <= t && t <= cert.not_after)
}
fn is_ca_issuer(cert: &Certificate) -> bool {
cert.basic_constraints().is_some_and(|bc| bc.is_ca)
&& cert.key_usage().is_some_and(KeyUsage::key_cert_sign)
}
#[must_use]
pub fn verify_chain(
chain: &[Certificate],
anchors: &[Certificate],
at_time: Option<X509Time>,
) -> bool {
let chain: alloc::vec::Vec<&Certificate> = chain.iter().collect();
let anchors: alloc::vec::Vec<&Certificate> = anchors.iter().collect();
verify_chain_refs(&chain, &anchors, at_time)
}
#[doc(hidden)]
#[must_use]
pub fn verify_chain_refs(
chain: &[&Certificate],
anchors: &[&Certificate],
at_time: Option<X509Time>,
) -> bool {
if chain.is_empty() || chain.len() > MAX_CHAIN_DEPTH {
return false;
}
for &cert in chain {
if cert
.extensions
.as_deref()
.is_some_and(|e| has_unknown_critical(e, KNOWN_EXTS))
{
return false;
}
if !within_window(cert, at_time) {
return false;
}
}
for i in 0..chain.len() - 1 {
let (subj, iss) = (chain[i], chain[i + 1]);
if subj.issuer_raw() != iss.subject_raw() {
return false;
}
if !subj.verify_signature(&iss.subject_public_key()) {
return false;
}
if !is_ca_issuer(iss) {
return false;
}
}
let top = chain[chain.len() - 1];
anchors.iter().any(|a| {
a.subject_raw() == top.issuer_raw()
&& within_window(a, at_time)
&& top.verify_signature(&a.subject_public_key())
})
}
#[cfg(test)]
pub(crate) mod test_support {
use crate::sm2::{DEFAULT_SIGNER_ID, Sm2PrivateKey, Sm2PublicKey, sign_with_id};
use alloc::vec::Vec;
use getrandom::SysRng;
#[allow(clippy::cast_possible_truncation)]
pub fn der(tag: u8, content: &[u8]) -> Vec<u8> {
let mut out = alloc::vec![tag];
let n = content.len();
if n < 128 {
out.push(n as u8);
} else {
let mut len_bytes = Vec::new();
let mut v = n;
while v > 0 {
len_bytes.push((v & 0xff) as u8);
v >>= 8;
}
len_bytes.reverse();
out.push(0x80 | len_bytes.len() as u8);
out.extend_from_slice(&len_bytes);
}
out.extend_from_slice(content);
out
}
pub fn name(label: &[u8]) -> Vec<u8> {
der(0x30, label)
}
pub fn key(seed: u8) -> Sm2PrivateKey {
let mut b = [0u8; 32];
b[31] = seed.max(1);
Option::from(Sm2PrivateKey::from_bytes_be(&b)).expect("valid test scalar")
}
pub fn ku_ext(bits: &[u8], critical: bool) -> Vec<u8> {
let mut val = [0u8; 2];
for &b in bits {
val[(b / 8) as usize] |= 1 << (7 - (b % 8));
}
let nbytes = usize::from(bits.iter().any(|&b| b >= 8)) + 1;
let mut bs = alloc::vec![0u8]; bs.extend_from_slice(&val[..nbytes]);
extension(
crate::asn1::oid::KEY_USAGE,
critical,
&der(0x04, &der(0x03, &bs)),
)
}
#[allow(clippy::cast_possible_truncation)]
pub fn bc_ext(is_ca: bool, path_len: Option<u32>, critical: bool) -> Vec<u8> {
let mut seq = Vec::new();
if is_ca {
seq.extend_from_slice(&[0x01, 0x01, 0xFF]);
}
if let Some(p) = path_len {
seq.extend_from_slice(&der(0x02, &[p as u8]));
}
extension(
crate::asn1::oid::BASIC_CONSTRAINTS,
critical,
&der(0x04, &der(0x30, &seq)),
)
}
pub fn raw_ext(oid_bytes: &[u8], critical: bool, value_inner: &[u8]) -> Vec<u8> {
extension(oid_bytes, critical, &der(0x04, value_inner))
}
fn extension(oid_bytes: &[u8], critical: bool, octet_string_tlv: &[u8]) -> Vec<u8> {
let mut body = der(0x06, oid_bytes);
if critical {
body.extend_from_slice(&[0x01, 0x01, 0xFF]);
}
body.extend_from_slice(octet_string_tlv);
der(0x30, &body)
}
pub fn mint(
issuer_key: &Sm2PrivateKey,
issuer_name: &[u8],
subject_name: &[u8],
subject_key: &Sm2PublicKey,
exts: &[u8],
not_before: &str,
not_after: &str,
) -> Vec<u8> {
let algid = der(0x30, &der(0x06, crate::asn1::oid::SM2_SIGN_WITH_SM3));
let mut tbs_body = der(0xA0, &der(0x02, &[0x02])); tbs_body.extend_from_slice(&der(0x02, &[0x01])); tbs_body.extend_from_slice(&algid);
tbs_body.extend_from_slice(issuer_name);
let validity = der(
0x30,
&[
der(0x17, not_before.as_bytes()),
der(0x17, not_after.as_bytes()),
]
.concat(),
);
tbs_body.extend_from_slice(&validity);
tbs_body.extend_from_slice(subject_name);
tbs_body.extend_from_slice(&crate::spki::encode(subject_key));
if !exts.is_empty() {
tbs_body.extend_from_slice(&der(0xA3, &der(0x30, exts)));
}
let tbs = der(0x30, &tbs_body);
let sig = sign_with_id(issuer_key, DEFAULT_SIGNER_ID, &tbs, &mut SysRng).expect("sign tbs");
let mut sig_bs = alloc::vec![0u8]; sig_bs.extend_from_slice(&sig);
let mut cert = tbs;
cert.extend_from_slice(&algid);
cert.extend_from_slice(&der(0x03, &sig_bs));
der(0x30, &cert)
}
pub fn cert(
issuer_key: &Sm2PrivateKey,
issuer_name: &[u8],
subject_name: &[u8],
subject_key: &Sm2PublicKey,
exts: &[u8],
) -> super::Certificate {
let der_bytes = mint(
issuer_key,
issuer_name,
subject_name,
subject_key,
exts,
"260101000000Z",
"270101000000Z",
);
super::Certificate::from_der(&der_bytes).expect("minted cert parses")
}
}
#[cfg(test)]
mod v1_8_tests {
use super::test_support::*;
use super::*;
use alloc::vec::Vec;
#[test]
fn keyusage_bit_order() {
let k = KeyUsage::parse(&[0x03, 0x02, 0x05, 0xA0]).unwrap();
assert!(k.digital_signature() && k.key_encipherment());
assert!(!k.content_commitment() && !k.key_agreement() && !k.key_cert_sign());
let k2 = KeyUsage::parse(&[0x03, 0x03, 0x07, 0x80, 0x80]).unwrap();
assert!(k2.digital_signature() && k2.decipher_only());
assert!(KeyUsage::parse(&[0x03, 0x02, 0x05, 0xA0, 0x00]).is_none());
assert!(KeyUsage::parse(&[0x03, 0x02, 0x08, 0xA0]).is_none());
}
#[test]
fn basicconstraints_reader() {
let ca = BasicConstraints::parse(&[0x30, 0x03, 0x01, 0x01, 0xFF]).unwrap();
assert!(ca.is_ca && ca.path_len.is_none());
let ca_pl =
BasicConstraints::parse(&[0x30, 0x06, 0x01, 0x01, 0xFF, 0x02, 0x01, 0x00]).unwrap();
assert!(ca_pl.is_ca && ca_pl.path_len == Some(0));
let empty = BasicConstraints::parse(&[0x30, 0x00]).unwrap();
assert!(!empty.is_ca && empty.path_len.is_none());
assert!(BasicConstraints::parse(&[0x30, 0x03, 0x01, 0x01, 0xFF, 0x00]).is_none());
}
#[test]
fn extension_helpers() {
let known: &[&[u8]] = &[oid::KEY_USAGE, oid::BASIC_CONSTRAINTS];
let ku = ku_ext(&[0], true);
let unknown_crit = raw_ext(&[0x55, 0x1d, 0x25], true, &[0x05, 0x00]); let unknown_noncrit = raw_ext(&[0x55, 0x1d, 0x25], false, &[0x05, 0x00]);
let with_crit = der(0x30, &[ku.clone(), unknown_crit].concat());
let with_noncrit = der(0x30, &[ku, unknown_noncrit].concat());
assert!(find_extension(&with_crit, oid::KEY_USAGE).is_some());
assert!(find_extension(&with_crit, oid::BASIC_CONSTRAINTS).is_none());
assert!(has_unknown_critical(&with_crit, known));
assert!(!has_unknown_critical(&with_noncrit, known));
}
fn trio() -> (Certificate, Certificate, Certificate) {
let (rk, ik, lk) = (key(1), key(2), key(3));
let (rn, in_, ln) = (name(b"root"), name(b"int"), name(b"leaf"));
let ca_exts = [ku_ext(&[5], true), bc_ext(true, None, true)].concat();
let root = cert(&rk, &rn, &rn, &rk.public_key(), &ca_exts);
let int = cert(&rk, &rn, &in_, &ik.public_key(), &ca_exts);
let leaf = cert(&ik, &in_, &ln, &lk.public_key(), &ku_ext(&[0], true));
(leaf, int, root)
}
#[test]
fn valid_chain_to_anchor() {
let (leaf, int, root) = trio();
assert!(verify_chain(&[leaf, int], &[root], None));
}
#[test]
fn wrong_signing_key_rejected() {
let (rk, ik, lk) = (key(1), key(2), key(3));
let (rn, in_, ln) = (name(b"root"), name(b"int"), name(b"leaf"));
let ca_exts = [ku_ext(&[5], true), bc_ext(true, None, true)].concat();
let root = cert(&rk, &rn, &rn, &rk.public_key(), &ca_exts);
let int = cert(&rk, &rn, &in_, &ik.public_key(), &ca_exts);
let bad_leaf = cert(&rk, &in_, &ln, &lk.public_key(), &ku_ext(&[0], true));
assert!(!verify_chain(&[bad_leaf, int], &[root], None));
}
#[test]
fn non_ca_intermediate_rejected() {
let (rk, ik, lk) = (key(1), key(2), key(3));
let (rn, in_, ln) = (name(b"root"), name(b"int"), name(b"leaf"));
let ca_exts = [ku_ext(&[5], true), bc_ext(true, None, true)].concat();
let root = cert(&rk, &rn, &rn, &rk.public_key(), &ca_exts);
let int = cert(&rk, &rn, &in_, &ik.public_key(), &ku_ext(&[5], true));
let leaf = cert(&ik, &in_, &ln, &lk.public_key(), &ku_ext(&[0], true));
assert!(!verify_chain(&[leaf, int], &[root], None));
}
#[test]
fn broken_name_link_rejected() {
let (rk, ik, lk) = (key(1), key(2), key(3));
let (rn, in_, ln) = (name(b"root"), name(b"int"), name(b"leaf"));
let ca_exts = [ku_ext(&[5], true), bc_ext(true, None, true)].concat();
let root = cert(&rk, &rn, &rn, &rk.public_key(), &ca_exts);
let int = cert(&rk, &rn, &in_, &ik.public_key(), &ca_exts);
let leaf = cert(
&ik,
&name(b"other"),
&ln,
&lk.public_key(),
&ku_ext(&[0], true),
);
assert!(!verify_chain(&[leaf, int], &[root], None));
}
#[test]
fn over_max_depth_rejected() {
let chain: Vec<Certificate> = (0..=MAX_CHAIN_DEPTH)
.map(|i| {
let k = key(u8::try_from(i + 1).unwrap());
let n = name(b"x");
cert(&k, &n, &n, &k.public_key(), &ku_ext(&[0], true))
})
.collect();
assert!(chain.len() > MAX_CHAIN_DEPTH);
assert!(!verify_chain(&chain, &[], None));
}
#[test]
fn time_window_enforced() {
let (leaf, int, root) = trio();
let nb = leaf.not_before();
let after = X509Time {
year: leaf.not_after().year + 1,
..leaf.not_after()
};
let chain = [leaf, int];
let anchors = [root];
assert!(verify_chain(&chain, &anchors, Some(nb)));
assert!(!verify_chain(&chain, &anchors, Some(after)));
}
#[test]
fn try_all_anchors_second_valid() {
let (rk, ik, lk) = (key(1), key(2), key(3));
let decoy = key(9);
let (rn, in_, ln) = (name(b"root"), name(b"int"), name(b"leaf"));
let ca_exts = [ku_ext(&[5], true), bc_ext(true, None, true)].concat();
let real_root = cert(&rk, &rn, &rn, &rk.public_key(), &ca_exts);
let decoy_root = cert(&decoy, &rn, &rn, &decoy.public_key(), &ca_exts); let int = cert(&rk, &rn, &in_, &ik.public_key(), &ca_exts);
let leaf = cert(&ik, &in_, &ln, &lk.public_key(), &ku_ext(&[0], true));
let chain = [leaf, int];
assert!(verify_chain(&chain, &[decoy_root, real_root], None));
let decoy_only = cert(&decoy, &rn, &rn, &decoy.public_key(), &ca_exts);
assert!(!verify_chain(&chain, &[decoy_only], None));
}
#[test]
fn unknown_critical_extension_rejected() {
let (rk, ik, lk) = (key(1), key(2), key(3));
let (rn, in_, ln) = (name(b"root"), name(b"int"), name(b"leaf"));
let ca_exts = [ku_ext(&[5], true), bc_ext(true, None, true)].concat();
let root = cert(&rk, &rn, &rn, &rk.public_key(), &ca_exts);
let int = cert(&rk, &rn, &in_, &ik.public_key(), &ca_exts);
let anchors = [root];
let crit = [
ku_ext(&[0], true),
raw_ext(&[0x55, 0x1d, 0x25], true, &[0x05, 0x00]),
]
.concat();
let leaf_crit = cert(&ik, &in_, &ln, &lk.public_key(), &crit);
let int2 = cert(&rk, &rn, &in_, &ik.public_key(), &ca_exts);
assert!(!verify_chain(&[leaf_crit, int2], &anchors, None));
let noncrit = [
ku_ext(&[0], true),
raw_ext(&[0x55, 0x1d, 0x25], false, &[0x05, 0x00]),
]
.concat();
let leaf_ok = cert(&ik, &in_, &ln, &lk.public_key(), &noncrit);
assert!(verify_chain(&[leaf_ok, int], &anchors, None));
}
#[test]
fn self_signed_leaf_as_own_anchor() {
let sk = key(7);
let sn = name(b"self");
let leaf = cert(&sk, &sn, &sn, &sk.public_key(), &ku_ext(&[0], true));
let anchor = cert(&sk, &sn, &sn, &sk.public_key(), &ku_ext(&[0], true));
assert!(verify_chain(&[leaf], &[anchor], None));
}
}
#[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());
}
}