use super::der::{Der, DerError, tag};
use super::oid;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum Curve {
P256,
P521,
}
impl Curve {
#[must_use]
pub const fn field_len(self) -> usize {
match self {
Self::P256 => 32,
Self::P521 => 66,
}
}
#[must_use]
pub const fn suite(self) -> crate::pnc::Suite {
match self {
Self::P256 => crate::pnc::Suite::EcdsaSha256,
Self::P521 => crate::pnc::Suite::EcdsaSha512,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct KeyUsage(u16);
impl KeyUsage {
pub const DIGITAL_SIGNATURE: Self = Self(1 << 0);
pub const NON_REPUDIATION: Self = Self(1 << 1);
pub const KEY_ENCIPHERMENT: Self = Self(1 << 2);
pub const DATA_ENCIPHERMENT: Self = Self(1 << 3);
pub const KEY_AGREEMENT: Self = Self(1 << 4);
pub const KEY_CERT_SIGN: Self = Self(1 << 5);
pub const CRL_SIGN: Self = Self(1 << 6);
#[must_use]
pub const fn and(self, other: Self) -> Self {
Self(self.0 | other.0)
}
#[must_use]
pub const fn contains(self, wanted: Self) -> bool {
self.0 & wanted.0 == wanted.0
}
#[must_use]
pub const fn bits(self) -> u16 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct BasicConstraints {
pub ca: bool,
pub path_len: Option<u32>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Name<'a> {
pub encoded: &'a [u8],
pub common_name: Option<&'a str>,
pub organization: Option<&'a str>,
pub domain_component: Option<&'a str>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Certificate<'a> {
pub der: &'a [u8],
pub tbs: &'a [u8],
pub serial: &'a [u8],
pub issuer: Name<'a>,
pub subject: Name<'a>,
pub not_before: i64,
pub not_after: i64,
pub curve: Curve,
pub public_key: &'a [u8],
pub key_usage: Option<KeyUsage>,
pub basic_constraints: Option<BasicConstraints>,
pub signature: heapless_sig::Sig,
}
pub mod heapless_sig {
pub const MAX: usize = 132;
#[derive(Debug, Clone, Copy)]
pub struct Sig {
pub(crate) bytes: [u8; MAX],
pub(crate) len: usize,
}
impl PartialEq for Sig {
fn eq(&self, other: &Self) -> bool {
self.as_slice() == other.as_slice()
}
}
impl Eq for Sig {}
impl Sig {
#[must_use]
pub fn as_slice(&self) -> &[u8] {
&self.bytes[..self.len]
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum CertError {
Der(DerError),
NotV3,
UnsupportedCurve,
BadPublicKey,
UnsupportedSignatureAlgorithm,
AlgorithmMismatch,
BadSignature,
BadValidity,
UnknownCriticalExtension,
DuplicateExtension,
}
impl From<DerError> for CertError {
fn from(e: DerError) -> Self {
Self::Der(e)
}
}
impl<'a> Certificate<'a> {
pub fn parse(der: &'a [u8]) -> Result<Self, CertError> {
let mut outer = Der::new(der);
let mut cert = outer.nested(tag::SEQUENCE)?;
outer.finish()?;
let (_, tbs, tbs_contents) = cert.any_raw()?;
let mut tbs_reader = Der::new(tbs_contents);
let version = tbs_reader.optional(tag::context(0))?.ok_or(CertError::NotV3)?;
if Der::new(version).small_uint()? != 2 {
return Err(CertError::NotV3);
}
let serial = tbs_reader.expect(tag::INTEGER)?;
let inner_alg = read_signature_algorithm(&mut tbs_reader)?;
let issuer = read_name(&mut tbs_reader)?;
let (not_before, not_after) = read_validity(&mut tbs_reader)?;
let subject = read_name(&mut tbs_reader)?;
let (curve, public_key) = read_spki(&mut tbs_reader)?;
let _ = tbs_reader.optional(0x81)?;
let _ = tbs_reader.optional(0x82)?;
let mut key_usage = None;
let mut basic_constraints = None;
if let Some(extensions) = tbs_reader.optional(tag::context(3))? {
let mut list = Der::new(extensions);
let mut list = list.nested(tag::SEQUENCE)?;
let mut seen = ExtensionsSeen::default();
while !list.is_empty() {
let mut ext = list.nested(tag::SEQUENCE)?;
let id = ext.expect(tag::OID)?;
let critical =
if ext.peek() == Some(tag::BOOLEAN) { ext.boolean()? } else { false };
let value = ext.expect(tag::OCTET_STRING)?;
ext.finish()?;
match id {
oid::KEY_USAGE => {
seen.mark(Extension::KeyUsage)?;
key_usage = Some(read_key_usage(value)?);
}
oid::BASIC_CONSTRAINTS => {
seen.mark(Extension::BasicConstraints)?;
basic_constraints = Some(read_basic_constraints(value)?);
}
oid::SUBJECT_KEY_IDENTIFIER
| oid::AUTHORITY_KEY_IDENTIFIER
| oid::CERTIFICATE_POLICIES
| oid::CRL_DISTRIBUTION_POINTS
| oid::AUTHORITY_INFO_ACCESS
| oid::EXT_KEY_USAGE
if !critical => {}
_ if critical => return Err(CertError::UnknownCriticalExtension),
_ => {}
}
}
}
let outer_alg = read_signature_algorithm(&mut cert)?;
if outer_alg != inner_alg {
return Err(CertError::AlgorithmMismatch);
}
let (unused, sig_der) = cert.bit_string()?;
if unused != 0 {
return Err(CertError::BadSignature);
}
cert.finish()?;
let signature = raw_signature(sig_der, curve)?;
Ok(Self {
der,
tbs,
serial,
issuer,
subject,
not_before,
not_after,
curve,
public_key,
key_usage,
basic_constraints,
signature,
})
}
#[must_use]
pub fn is_self_issued(&self) -> bool {
self.issuer.encoded == self.subject.encoded
}
#[must_use]
pub const fn is_valid_at(&self, at: i64) -> bool {
self.not_before <= at && at <= self.not_after
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Extension {
KeyUsage,
BasicConstraints,
}
#[derive(Debug, Default)]
struct ExtensionsSeen {
key_usage: bool,
basic_constraints: bool,
}
impl ExtensionsSeen {
fn mark(&mut self, which: Extension) -> Result<(), CertError> {
let slot = match which {
Extension::KeyUsage => &mut self.key_usage,
Extension::BasicConstraints => &mut self.basic_constraints,
};
if core::mem::replace(slot, true) { Err(CertError::DuplicateExtension) } else { Ok(()) }
}
}
fn read_signature_algorithm<'a>(reader: &mut Der<'a>) -> Result<&'a [u8], CertError> {
let mut alg = reader.nested(tag::SEQUENCE)?;
let id = alg.expect(tag::OID)?;
if !alg.is_empty() {
return Err(CertError::UnsupportedSignatureAlgorithm);
}
match id {
oid::ECDSA_WITH_SHA256 | oid::ECDSA_WITH_SHA512 => Ok(id),
_ => Err(CertError::UnsupportedSignatureAlgorithm),
}
}
fn read_name<'a>(reader: &mut Der<'a>) -> Result<Name<'a>, CertError> {
let (_, encoded, contents) = reader.any_raw()?;
let mut rdns = Der::new(contents);
let mut name = Name { encoded, common_name: None, organization: None, domain_component: None };
while !rdns.is_empty() {
let mut rdn = rdns.nested(tag::SET)?;
while !rdn.is_empty() {
let mut attr = rdn.nested(tag::SEQUENCE)?;
let id = attr.expect(tag::OID)?;
let (_, value) = attr.any()?;
attr.finish()?;
let Ok(text) = core::str::from_utf8(value) else { continue };
match id {
oid::COMMON_NAME => name.common_name = Some(text),
oid::ORGANIZATION => name.organization = Some(text),
oid::DOMAIN_COMPONENT if name.domain_component.is_none() => {
name.domain_component = Some(text);
}
_ => {}
}
}
}
Ok(name)
}
fn read_validity(reader: &mut Der<'_>) -> Result<(i64, i64), CertError> {
let mut validity = reader.nested(tag::SEQUENCE)?;
let not_before = read_time(&mut validity)?;
let not_after = read_time(&mut validity)?;
validity.finish()?;
Ok((not_before, not_after))
}
fn read_time(reader: &mut Der<'_>) -> Result<i64, CertError> {
let (tag, bytes) = reader.any()?;
let text = core::str::from_utf8(bytes).map_err(|_| CertError::BadValidity)?;
let (year, rest) = match tag {
tag::UTC_TIME => {
let (yy, rest) = split_digits(text, 2)?;
(if yy < 50 { 2000 + yy } else { 1900 + yy }, rest)
}
tag::GENERALIZED_TIME => {
let (yyyy, rest) = split_digits(text, 4)?;
(yyyy, rest)
}
_ => return Err(CertError::BadValidity),
};
let (month, rest) = split_digits(rest, 2)?;
let (day, rest) = split_digits(rest, 2)?;
let (hour, rest) = split_digits(rest, 2)?;
let (minute, rest) = split_digits(rest, 2)?;
let (second, rest) = split_digits(rest, 2)?;
if rest != "Z" {
return Err(CertError::BadValidity);
}
if !(1..=12).contains(&month) || day == 0 || day > 31 || hour > 23 || minute > 59 || second > 60
{
return Err(CertError::BadValidity);
}
Ok(unix_seconds(i64::from(year), month, day, hour, minute, second))
}
fn split_digits(text: &str, n: usize) -> Result<(u32, &str), CertError> {
let (head, rest) = text.split_at_checked(n).ok_or(CertError::BadValidity)?;
let value = head.bytes().try_fold(0u32, |acc, b| {
b.is_ascii_digit().then(|| acc * 10 + u32::from(b - b'0')).ok_or(CertError::BadValidity)
})?;
Ok((value, rest))
}
fn unix_seconds(year: i64, month: u32, day: u32, hour: u32, minute: u32, second: u32) -> i64 {
let m = i64::from(month);
let d = i64::from(day);
let y = if m <= 2 { year - 1 } else { year };
let era = if y >= 0 { y } else { y - 399 } / 400;
let yoe = y - era * 400;
let mp = (m + 9) % 12;
let doy = (153 * mp + 2) / 5 + d - 1;
let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
let days = era * 146_097 + doe - 719_468;
days * 86_400 + i64::from(hour) * 3600 + i64::from(minute) * 60 + i64::from(second)
}
fn read_spki<'a>(reader: &mut Der<'a>) -> Result<(Curve, &'a [u8]), CertError> {
let mut spki = reader.nested(tag::SEQUENCE)?;
let mut alg = spki.nested(tag::SEQUENCE)?;
if alg.expect(tag::OID)? != oid::EC_PUBLIC_KEY {
return Err(CertError::UnsupportedCurve);
}
let curve = match alg.expect(tag::OID)? {
oid::PRIME256V1 => Curve::P256,
oid::SECP521R1 => Curve::P521,
_ => return Err(CertError::UnsupportedCurve),
};
alg.finish()?;
let (unused, point) = spki.bit_string()?;
spki.finish()?;
if unused != 0 {
return Err(CertError::BadPublicKey);
}
if point.first() != Some(&0x04) || point.len() != 1 + 2 * curve.field_len() {
return Err(CertError::BadPublicKey);
}
Ok((curve, point))
}
fn read_key_usage(value: &[u8]) -> Result<KeyUsage, CertError> {
let mut reader = Der::new(value);
let (unused, bytes) = reader.bit_string()?;
reader.finish()?;
let mut bits = 0u16;
for (index, &byte) in bytes.iter().take(2).enumerate() {
for bit in 0..8u32 {
let position = u32::try_from(index).unwrap_or(u32::MAX) * 8 + bit;
if index == bytes.len() - 1 && bit >= 8 - u32::from(unused) {
break;
}
if byte & (0x80 >> bit) != 0 {
bits |= 1u16 << position;
}
}
}
Ok(KeyUsage(bits))
}
fn read_basic_constraints(value: &[u8]) -> Result<BasicConstraints, CertError> {
let mut reader = Der::new(value);
let mut seq = reader.nested(tag::SEQUENCE)?;
reader.finish()?;
let ca = if seq.peek() == Some(tag::BOOLEAN) { seq.boolean()? } else { false };
let path_len = if seq.peek() == Some(tag::INTEGER) { Some(seq.small_uint()?) } else { None };
seq.finish()?;
Ok(BasicConstraints { ca, path_len })
}
fn raw_signature(der: &[u8], curve: Curve) -> Result<heapless_sig::Sig, CertError> {
let width = curve.field_len();
let mut reader = Der::new(der);
let mut seq = reader.nested(tag::SEQUENCE).map_err(|_| CertError::BadSignature)?;
reader.finish().map_err(|_| CertError::BadSignature)?;
let mut bytes = [0u8; heapless_sig::MAX];
for half in 0..2 {
let value = seq.expect(tag::INTEGER).map_err(|_| CertError::BadSignature)?;
let value = match value {
[] => return Err(CertError::BadSignature),
[0x00, rest @ ..] if rest.first().is_some_and(|&b| b & 0x80 != 0) => rest,
[first, ..] if *first & 0x80 != 0 => return Err(CertError::BadSignature),
other => other,
};
if value.is_empty() || value.len() > width {
return Err(CertError::BadSignature);
}
let start = half * width + (width - value.len());
bytes[start..half * width + width].copy_from_slice(value);
}
seq.finish().map_err(|_| CertError::BadSignature)?;
Ok(heapless_sig::Sig { bytes, len: 2 * width })
}
impl core::fmt::Display for CertError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Der(e) => write!(f, "{e}"),
Self::NotV3 => f.write_str("the certificate is not X.509v3"),
Self::UnsupportedCurve => {
f.write_str("the public key is not on secp256r1 or secp521r1")
}
Self::BadPublicKey => f.write_str("the public key is not an uncompressed SEC1 point"),
Self::UnsupportedSignatureAlgorithm => {
f.write_str("the signature algorithm is not ECDSA with SHA-256 or SHA-512")
}
Self::AlgorithmMismatch => f.write_str(
"signatureAlgorithm and tbsCertificate.signature name different algorithms",
),
Self::BadSignature => f.write_str("the signature is not a well-formed ECDSA-Sig-Value"),
Self::BadValidity => f.write_str("a validity time could not be read"),
Self::UnknownCriticalExtension => f.write_str(
"the certificate carries a critical extension this build cannot process",
),
Self::DuplicateExtension => f.write_str("an extension appears more than once"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for CertError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Der(e) => Some(e),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_epoch_and_a_leap_day_convert_exactly() {
assert_eq!(unix_seconds(1970, 1, 1, 0, 0, 0), 0);
assert_eq!(unix_seconds(2000, 3, 1, 0, 0, 0), 951_868_800);
assert_eq!(unix_seconds(2024, 2, 29, 12, 0, 0), 1_709_208_000);
assert_eq!(unix_seconds(2038, 1, 19, 3, 14, 7), 2_147_483_647);
}
#[test]
fn utc_time_pivots_at_fifty() {
let read = |text: &str| {
let mut der = alloc::vec![tag::UTC_TIME, u8::try_from(text.len()).unwrap()];
der.extend_from_slice(text.as_bytes());
read_time(&mut Der::new(&der)).unwrap()
};
assert_eq!(read("490101000000Z"), unix_seconds(2049, 1, 1, 0, 0, 0));
assert_eq!(read("500101000000Z"), unix_seconds(1950, 1, 1, 0, 0, 0));
}
#[test]
fn a_time_that_is_not_utc_is_refused() {
let text = b"9912312359+0100";
let mut der = alloc::vec![tag::UTC_TIME, u8::try_from(text.len()).unwrap()];
der.extend_from_slice(text);
assert_eq!(read_time(&mut Der::new(&der)), Err(CertError::BadValidity));
}
#[test]
fn key_usage_bits_read_in_the_order_x509_numbers_them() {
let ku = read_key_usage(&[0x03, 0x02, 0x07, 0x80]).unwrap();
assert!(ku.contains(KeyUsage::DIGITAL_SIGNATURE));
assert!(!ku.contains(KeyUsage::KEY_CERT_SIGN));
let ku = read_key_usage(&[0x03, 0x02, 0x01, 0x06]).unwrap();
assert!(ku.contains(KeyUsage::KEY_CERT_SIGN));
assert!(ku.contains(KeyUsage::CRL_SIGN));
assert!(!ku.contains(KeyUsage::DIGITAL_SIGNATURE));
}
#[test]
fn basic_constraints_defaults_to_not_a_ca() {
assert_eq!(
read_basic_constraints(&[0x30, 0x00]).unwrap(),
BasicConstraints { ca: false, path_len: None }
);
assert_eq!(
read_basic_constraints(&[0x30, 0x06, 0x01, 0x01, 0xFF, 0x02, 0x01, 0x01]).unwrap(),
BasicConstraints { ca: true, path_len: Some(1) }
);
}
#[test]
fn a_der_signature_becomes_a_fixed_width_pair() {
let der = [0x30, 0x06, 0x02, 0x01, 0x01, 0x02, 0x01, 0x02];
let sig = raw_signature(&der, Curve::P256).unwrap();
assert_eq!(sig.as_slice().len(), 64);
assert_eq!(sig.as_slice()[31], 1);
assert_eq!(sig.as_slice()[63], 2);
assert!(sig.as_slice()[..31].iter().all(|&b| b == 0));
}
#[test]
fn a_negative_or_over_wide_coordinate_is_refused() {
let der = [0x30, 0x06, 0x02, 0x01, 0x80, 0x02, 0x01, 0x02];
assert_eq!(raw_signature(&der, Curve::P256), Err(CertError::BadSignature));
}
}