use alloc::vec::Vec;
use super::cert::{Certificate, KeyUsage};
use super::{Profile, VerifyWith};
use crate::pnc::PncError;
pub const MAX_PATH_LEN: usize = 3;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Validated<'a> {
pub leaf: Certificate<'a>,
pub intermediates: Vec<Certificate<'a>>,
pub anchor: Certificate<'a>,
}
impl<'a> Validated<'a> {
#[must_use]
pub const fn public_key(&self) -> &'a [u8] {
self.leaf.public_key
}
#[must_use]
pub const fn subject_common_name(&self) -> Option<&'a str> {
self.leaf.subject.common_name
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum ChainError {
Certificate {
index: usize,
error: super::cert::CertError,
},
Empty,
TooLong {
len: usize,
},
NoTrustAnchor,
Expired {
index: usize,
},
NotAnIssuer {
index: usize,
},
BadSignature {
index: usize,
},
Profile {
wanted: &'static str,
},
MixedCurves,
}
pub fn validate<'a>(
chain: &[&'a [u8]],
anchors: &[&'a [u8]],
profile: Profile,
now: i64,
verifier: &impl VerifyWith,
) -> Result<Validated<'a>, ChainError> {
if chain.len() > MAX_PATH_LEN {
return Err(ChainError::TooLong { len: chain.len() });
}
let parsed = parse_all(chain)?;
let (leaf, intermediates) = parsed.split_first().ok_or(ChainError::Empty)?;
profile.check_leaf(leaf)?;
if parsed.iter().any(|c| c.curve != leaf.curve) {
return Err(ChainError::MixedCurves);
}
for (index, cert) in parsed.iter().enumerate() {
if !cert.is_valid_at(now) {
return Err(ChainError::Expired { index });
}
}
for (index, cert) in parsed.iter().enumerate() {
let below = index;
if let Ok(anchor) = find_anchor(anchors, cert, below, now, verifier, index) {
return Ok(Validated { leaf: *leaf, intermediates: intermediates.to_vec(), anchor });
}
let issuer = parsed.get(index + 1).copied().ok_or(ChainError::NoTrustAnchor)?;
if issuer.subject.encoded != cert.issuer.encoded {
return Err(ChainError::NoTrustAnchor);
}
check_issuer(&issuer, below, index + 1)?;
verify_signature(cert, &issuer, verifier, index)?;
}
Err(ChainError::NoTrustAnchor)
}
fn parse_all<'a>(chain: &[&'a [u8]]) -> Result<Vec<Certificate<'a>>, ChainError> {
chain
.iter()
.enumerate()
.map(|(index, der)| {
Certificate::parse(der).map_err(|error| ChainError::Certificate { index, error })
})
.collect()
}
fn find_anchor<'a>(
anchors: &[&'a [u8]],
cert: &Certificate<'a>,
below: usize,
now: i64,
verifier: &impl VerifyWith,
index: usize,
) -> Result<Certificate<'a>, ChainError> {
for der in anchors {
let Ok(anchor) = Certificate::parse(der) else { continue };
if anchor.subject.encoded != cert.issuer.encoded {
continue;
}
if !anchor.is_valid_at(now) {
continue;
}
if check_issuer(&anchor, below, index).is_err() {
continue;
}
if verify_signature(cert, &anchor, verifier, index).is_ok() {
return Ok(anchor);
}
}
Err(ChainError::NoTrustAnchor)
}
fn check_issuer(issuer: &Certificate<'_>, below: usize, index: usize) -> Result<(), ChainError> {
let constraints = issuer.basic_constraints.ok_or(ChainError::NotAnIssuer { index })?;
if !constraints.ca {
return Err(ChainError::NotAnIssuer { index });
}
if let Some(limit) = constraints.path_len
&& below > limit as usize
{
return Err(ChainError::NotAnIssuer { index });
}
if issuer.key_usage.is_some_and(|ku| !ku.contains(KeyUsage::KEY_CERT_SIGN)) {
return Err(ChainError::NotAnIssuer { index });
}
Ok(())
}
fn verify_signature(
cert: &Certificate<'_>,
issuer: &Certificate<'_>,
verifier: &impl VerifyWith,
index: usize,
) -> Result<(), ChainError> {
verifier
.verify_with(issuer.curve.suite(), issuer.public_key, cert.tbs, cert.signature.as_slice())
.map_err(|_: PncError| ChainError::BadSignature { index })
}
impl core::fmt::Display for ChainError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Certificate { index, error } => write!(f, "certificate {index}: {error}"),
Self::Empty => f.write_str("the chain is empty"),
Self::TooLong { len } => write!(
f,
"the path holds {len} certificates; ISO 15118 allows at most {MAX_PATH_LEN}"
),
Self::NoTrustAnchor => {
f.write_str("the chain does not reach a configured trust anchor")
}
Self::Expired { index } => {
write!(f, "certificate {index} is outside its validity period")
}
Self::NotAnIssuer { index } => {
write!(f, "certificate {index} is not permitted to issue what it signed")
}
Self::BadSignature { index } => {
write!(f, "certificate {index} is not signed by the certificate above it")
}
Self::Profile { wanted } => write!(f, "the leaf certificate is missing {wanted}"),
Self::MixedCurves => f.write_str("the chain changes elliptic curve partway"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for ChainError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Certificate { error, .. } => Some(error),
_ => None,
}
}
}