use crate::certificate::MatterCertificate;
use crate::error::{Error, Result};
use crate::extensions::KeyIdentifier;
use crate::name::DistinguishedName;
use crate::public_key::PublicKey;
use crate::time::MatterTime;
#[derive(Debug, Clone)]
pub struct TrustAnchor {
subject: DistinguishedName,
public_key: PublicKey,
subject_key_identifier: Option<KeyIdentifier>,
}
impl TrustAnchor {
#[must_use]
pub fn from_root_cert(root: &MatterCertificate) -> Self {
Self {
subject: root.subject().clone(),
public_key: root.public_key().clone(),
subject_key_identifier: root.extensions().subject_key_identifier,
}
}
#[must_use]
pub fn from_raw(
subject: DistinguishedName,
public_key: PublicKey,
subject_key_identifier: Option<KeyIdentifier>,
) -> Self {
Self {
subject,
public_key,
subject_key_identifier,
}
}
#[must_use]
pub fn subject(&self) -> &DistinguishedName {
&self.subject
}
#[must_use]
pub fn public_key(&self) -> &PublicKey {
&self.public_key
}
#[must_use]
pub fn subject_key_identifier(&self) -> Option<&KeyIdentifier> {
self.subject_key_identifier.as_ref()
}
}
#[derive(Debug, Clone, Default)]
pub struct TrustedRoots {
anchors: Vec<TrustAnchor>,
}
impl TrustedRoots {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, anchor: TrustAnchor) {
self.anchors.push(anchor);
}
pub fn iter(&self) -> impl Iterator<Item = &TrustAnchor> {
self.anchors.iter()
}
#[must_use]
pub fn len(&self) -> usize {
self.anchors.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.anchors.is_empty()
}
}
#[derive(Debug, Clone, Copy)]
pub struct CertificateChain<'a> {
certs: &'a [MatterCertificate],
}
impl<'a> CertificateChain<'a> {
#[must_use]
pub fn new(certs: &'a [MatterCertificate]) -> Self {
Self { certs }
}
#[must_use]
pub fn len(&self) -> usize {
self.certs.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.certs.is_empty()
}
pub fn validate(&self, roots: &TrustedRoots, at: MatterTime) -> Result<()> {
if self.certs.is_empty() {
return Err(Error::UntrustedRoot);
}
let len = self.certs.len();
for i in 0..len {
let cert = &self.certs[i];
let i_u8 = u8::try_from(i).unwrap_or(u8::MAX);
let nb = cert.not_before();
let na = cert.not_after();
if nb > at {
return Err(Error::NotYetValid {
cert_index: i_u8,
not_before: nb,
at,
});
}
if na != MatterTime::NO_EXPIRY && na < at {
return Err(Error::Expired {
cert_index: i_u8,
not_after: na,
at,
});
}
if i > 0 {
let is_ca = cert
.extensions()
.basic_constraints
.as_ref()
.is_some_and(|bc| bc.is_ca);
if !is_ca {
return Err(Error::NotACa { cert_index: i_u8 });
}
let has_key_cert_sign = cert
.extensions()
.key_usage
.is_some_and(|ku| ku.contains(crate::extensions::KeyUsage::KEY_CERT_SIGN));
if !has_key_cert_sign {
return Err(Error::MissingKeyCertSign { cert_index: i_u8 });
}
} else {
let leaf_is_ca = cert
.extensions()
.basic_constraints
.as_ref()
.is_some_and(|bc| bc.is_ca);
if leaf_is_ca {
return Err(Error::LeafIsCa);
}
}
if i > 0 {
if let Some(plc) = cert
.extensions()
.basic_constraints
.as_ref()
.and_then(|bc| bc.path_len_constraint)
{
let intermediates_below = u8::try_from(i.saturating_sub(1)).unwrap_or(u8::MAX);
if intermediates_below > plc {
return Err(Error::PathLengthExceeded { cert_index: i_u8 });
}
}
}
if i + 1 < len {
let next = &self.certs[i + 1];
if cert.issuer() != next.subject() {
return Err(Error::IssuerSubjectMismatch { cert_index: i_u8 });
}
cert.verify_signed_by(next.public_key())?;
}
}
let top = &self.certs[len - 1];
let top_tbs = top.to_x509_tbs_der()?;
for anchor in roots.iter() {
if top.issuer() != anchor.subject() {
continue;
}
if let Some(anchor_ski) = anchor.subject_key_identifier() {
let top_aki = top.extensions().authority_key_identifier;
if top_aki != Some(*anchor_ski) {
continue;
}
}
if anchor
.public_key()
.verify(&top_tbs, top.signature())
.is_ok()
{
return Ok(());
}
}
Err(Error::UntrustedRoot)
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)] mod tests {
use super::*;
#[test]
fn trusted_roots_default_is_empty() {
let roots = TrustedRoots::default();
assert!(roots.is_empty());
assert_eq!(roots.len(), 0);
assert_eq!(roots.iter().count(), 0);
}
#[test]
fn certificate_chain_empty_reports_zero_length() {
let chain = CertificateChain::new(&[]);
assert!(chain.is_empty());
assert_eq!(chain.len(), 0);
}
#[test]
fn validate_returns_untrusted_root_for_empty_chain() {
let roots = TrustedRoots::new();
let chain = CertificateChain::new(&[]);
let err = chain
.validate(&roots, MatterTime::from_unix_secs(1_700_000_000))
.unwrap_err();
assert!(matches!(err, Error::UntrustedRoot));
}
}