use crate::timestamp::{CheckedActor, Interval as GenerationInterval, TemporalError};
use structured_asn1::memory::{MemoryError, OwnedVec, Work};
use structured_pkix::Certificate;
use structured_pkix::extension::Recognizer;
use structured_pkix::path::{
self, Anchor, BuildResult, Interval, Rejection, Truncation, Validation, ValidationContext,
ValidationFacts, Verify, Watch, WorkingKey,
};
use structured_pkix::temporal::LeapTable;
mod authority;
mod census;
mod closure;
mod root_visits;
pub use closure::{
AuthorityClosure, AuthorityClosureError, AuthorityNode, AuthorityOrigin, close_authorities,
};
pub use root_visits::AuthorityVisitError;
mod crl;
pub use census::{MemberStatus, PathStatuses, StatusCensusError};
mod origin;
mod participants;
mod purpose;
pub use purpose::{AuthorityUse, AuthorityUses, Uses, UsesError, UsesResult, authority_uses};
mod reading;
pub use participants::{
Authorities, ClosureResult, Counter, CounterReceipts, Credential, CredentialGroup,
CredentialReceipts, Participant, ParticipantAuthority, ParticipantClosure,
ParticipantClosureError, ParticipantError, ParticipantRequirement, ParticipantRequirements,
ParticipantResult, SignerReceipt, participants,
};
mod responder;
pub use reading::{CrlReading, CrlReadingBinding, CrlReadingTarget};
mod status;
pub use crl::{
CrlAuthorities, CrlAuthority, CrlBinding, CrlBindingError, CrlBindingFailure, CrlCoverage,
CrlCoverageBinding, CrlCoverageError, CrlTarget,
};
pub use responder::{ResponderBinding, ResponderError, ResponderFailure, ResponderPath};
pub use status::{OcspBinding, OcspBindingError, OcspBindingFailure, OcspTarget};
#[derive(Debug, Clone, Copy)]
pub struct Member<'a> {
certificate: &'a Certificate<'a>,
key: WorkingKey<'a>,
}
impl<'a> Member<'a> {
#[must_use]
pub fn certificate(self) -> &'a Certificate<'a> {
self.certificate
}
#[must_use]
pub fn key(self) -> WorkingKey<'a> {
self.key
}
}
#[derive(Debug)]
pub struct CheckedPath<'a> {
anchor: Anchor<'a>,
interval: Interval,
members: OwnedVec<Member<'a>>,
facts: ValidationFacts<'a>,
}
impl<'a> CheckedPath<'a> {
#[must_use]
pub fn anchor(&self) -> Anchor<'a> {
self.anchor
}
#[must_use]
pub fn interval(&self) -> Interval {
self.interval
}
#[must_use]
pub fn members(&self) -> &[Member<'a>] {
&self.members
}
#[must_use]
pub fn facts(&self) -> &ValidationFacts<'a> {
&self.facts
}
pub fn bind_timestamp<'view, 'certificate, N: Copy, D: Copy, G: Copy, I, W: Work>(
&'view self,
actor: &'view CheckedActor<'certificate, 'a, N, D, G, I>,
utc: &LeapTable<'_>,
execution: &W,
) -> TimestampBindingResult<'view, 'certificate, 'a, N, D, G, I> {
execution
.charge_work(1)
.map_err(TimestampBindingError::Resource)?;
let target = self.members.last().expect("a checked path has a target");
if !exact(
target.certificate.as_bytes(),
actor.certificate().as_bytes(),
execution,
)
.map_err(TimestampBindingError::Resource)?
{
return Ok(TimestampBinding::Rejected(
TimestampBindingFailure::Certificate,
));
}
let key = match (self.facts.key.parameters(), actor.key().parameters()) {
(None, None) => true,
(Some(left), Some(right)) => {
exact(left, right, execution).map_err(TimestampBindingError::Resource)?
}
_ => false,
};
if !key {
return Ok(TimestampBinding::Rejected(
TimestampBindingFailure::EffectiveKey,
));
}
let generation = actor
.verified()
.interval(utc, execution)
.map_err(TimestampBindingError::Temporal)?;
if self.interval.from_instant() > generation.earliest
|| self.interval.to_instant() < generation.latest
{
return Ok(TimestampBinding::Rejected(
TimestampBindingFailure::Interval,
));
}
Ok(TimestampBinding::Bound(TimestampPath {
path: self,
actor,
generation,
}))
}
}
fn exact(left: &[u8], right: &[u8], execution: &impl Work) -> Result<bool, MemoryError> {
execution.charge_work(1)?;
if core::ptr::eq(left, right) {
return Ok(true);
}
if left.len() != right.len() {
return Ok(false);
}
let work = u64::try_from(left.len())
.ok()
.and_then(|n| n.checked_mul(2))
.ok_or(MemoryError::WorkExceeded)?;
execution.charge_work(work)?;
Ok(left == right)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TimestampBindingFailure {
Certificate,
EffectiveKey,
Interval,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TimestampBindingError {
Resource(MemoryError),
Temporal(TemporalError),
}
pub struct TimestampPath<'view, 'certificate, 'document, N, D, G, I> {
path: &'view CheckedPath<'document>,
actor: &'view CheckedActor<'certificate, 'document, N, D, G, I>,
generation: GenerationInterval,
}
pub type TimestampBindingResult<'view, 'certificate, 'document, N, D, G, I> =
Result<TimestampBinding<'view, 'certificate, 'document, N, D, G, I>, TimestampBindingError>;
impl<'view, 'certificate, 'document, N, D, G, I>
TimestampPath<'view, 'certificate, 'document, N, D, G, I>
{
#[must_use]
pub fn path(&self) -> &'view CheckedPath<'document> {
self.path
}
#[must_use]
pub fn actor(&self) -> &'view CheckedActor<'certificate, 'document, N, D, G, I> {
self.actor
}
#[must_use]
pub fn generation(&self) -> GenerationInterval {
self.generation
}
}
pub enum TimestampBinding<'view, 'certificate, 'document, N, D, G, I> {
Bound(TimestampPath<'view, 'certificate, 'document, N, D, G, I>),
Rejected(TimestampBindingFailure),
}
#[derive(Debug)]
#[expect(
clippy::large_enum_variant,
reason = "return the checked path and its existing accounted storage without a second heap allocation for enum layout"
)]
pub enum PathOutcome<'a> {
Accepted(CheckedPath<'a>),
Rejected(Rejection<'a>),
Incomplete(Truncation),
}
pub fn check_path<'a, V: Verify<E>, T: Watch, R: Recognizer<E>, E: Work>(
chain: &[&'a Certificate<'a>],
anchor: Anchor<'a>,
context: &ValidationContext<'a, '_, V, T, R, E>,
) -> BuildResult<PathOutcome<'a>, V::Error, R::Error> {
let facts = match path::validate(chain, anchor, context)? {
Validation::Accepted(facts) => facts,
Validation::Rejected(reason) => return Ok(PathOutcome::Rejected(reason)),
Validation::Incomplete(reason) => return Ok(PathOutcome::Incomplete(reason)),
};
let execution = context.execution;
let mut members = execution.vector();
let fail = |reason| {
structured_pkix::Error::resource(reason, "ltv.validationPath", chain[0].span().start)
};
members.try_reserve_exact(chain.len()).map_err(fail)?;
let mut key = WorkingKey::standing(anchor.key());
for certificate in chain.iter().rev() {
key = key.subject(certificate, execution)?;
members
.try_push(Member { certificate, key })
.map_err(fail)?;
}
Ok(PathOutcome::Accepted(CheckedPath {
anchor,
interval: context.interval,
members: members.into_owned(),
facts,
}))
}