1use crate::timestamp::{CheckedActor, Interval as GenerationInterval, TemporalError};
8use structured_asn1::memory::{MemoryError, OwnedVec, Work};
9use structured_pkix::Certificate;
10use structured_pkix::extension::Recognizer;
11use structured_pkix::path::{
12 self, Anchor, BuildResult, Interval, Rejection, Truncation, Validation, ValidationContext,
13 ValidationFacts, Verify, Watch, WorkingKey,
14};
15use structured_pkix::temporal::LeapTable;
16
17mod authority;
18mod census;
19mod closure;
20mod root_visits;
21pub use closure::{
22 AuthorityClosure, AuthorityClosureError, AuthorityNode, AuthorityOrigin, close_authorities,
23};
24pub use root_visits::AuthorityVisitError;
25mod crl;
26pub use census::{MemberStatus, PathStatuses, StatusCensusError};
27mod origin;
28mod participants;
29mod purpose;
30pub use purpose::{AuthorityUse, AuthorityUses, Uses, UsesError, UsesResult, authority_uses};
31mod reading;
32pub use participants::{
33 Authorities, ClosureResult, Counter, CounterReceipts, Credential, CredentialGroup,
34 CredentialReceipts, Participant, ParticipantAuthority, ParticipantClosure,
35 ParticipantClosureError, ParticipantError, ParticipantRequirement, ParticipantRequirements,
36 ParticipantResult, SignerReceipt, participants,
37};
38mod responder;
39pub use reading::{CrlReading, CrlReadingBinding, CrlReadingTarget};
40mod status;
41pub use crl::{
42 CrlAuthorities, CrlAuthority, CrlBinding, CrlBindingError, CrlBindingFailure, CrlCoverage,
43 CrlCoverageBinding, CrlCoverageError, CrlTarget,
44};
45pub use responder::{ResponderBinding, ResponderError, ResponderFailure, ResponderPath};
46pub use status::{OcspBinding, OcspBindingError, OcspBindingFailure, OcspTarget};
47
48#[derive(Debug, Clone, Copy)]
50pub struct Member<'a> {
51 certificate: &'a Certificate<'a>,
52 key: WorkingKey<'a>,
53}
54
55impl<'a> Member<'a> {
56 #[must_use]
58 pub fn certificate(self) -> &'a Certificate<'a> {
59 self.certificate
60 }
61
62 #[must_use]
64 pub fn key(self) -> WorkingKey<'a> {
65 self.key
66 }
67}
68
69#[derive(Debug)]
76pub struct CheckedPath<'a> {
77 anchor: Anchor<'a>,
78 interval: Interval,
79 members: OwnedVec<Member<'a>>,
80 facts: ValidationFacts<'a>,
81}
82
83impl<'a> CheckedPath<'a> {
84 #[must_use]
88 pub fn anchor(&self) -> Anchor<'a> {
89 self.anchor
90 }
91
92 #[must_use]
94 pub fn interval(&self) -> Interval {
95 self.interval
96 }
97
98 #[must_use]
102 pub fn members(&self) -> &[Member<'a>] {
103 &self.members
104 }
105
106 #[must_use]
108 pub fn facts(&self) -> &ValidationFacts<'a> {
109 &self.facts
110 }
111
112 pub fn bind_timestamp<'view, 'certificate, N: Copy, D: Copy, G: Copy, I, W: Work>(
121 &'view self,
122 actor: &'view CheckedActor<'certificate, 'a, N, D, G, I>,
123 utc: &LeapTable<'_>,
124 execution: &W,
125 ) -> TimestampBindingResult<'view, 'certificate, 'a, N, D, G, I> {
126 execution
127 .charge_work(1)
128 .map_err(TimestampBindingError::Resource)?;
129 let target = self.members.last().expect("a checked path has a target");
131 if !exact(
132 target.certificate.as_bytes(),
133 actor.certificate().as_bytes(),
134 execution,
135 )
136 .map_err(TimestampBindingError::Resource)?
137 {
138 return Ok(TimestampBinding::Rejected(
139 TimestampBindingFailure::Certificate,
140 ));
141 }
142 let key = match (self.facts.key.parameters(), actor.key().parameters()) {
146 (None, None) => true,
147 (Some(left), Some(right)) => {
148 exact(left, right, execution).map_err(TimestampBindingError::Resource)?
149 }
150 _ => false,
151 };
152 if !key {
153 return Ok(TimestampBinding::Rejected(
154 TimestampBindingFailure::EffectiveKey,
155 ));
156 }
157 let generation = actor
158 .verified()
159 .interval(utc, execution)
160 .map_err(TimestampBindingError::Temporal)?;
161 if self.interval.from_instant() > generation.earliest
162 || self.interval.to_instant() < generation.latest
163 {
164 return Ok(TimestampBinding::Rejected(
165 TimestampBindingFailure::Interval,
166 ));
167 }
168 Ok(TimestampBinding::Bound(TimestampPath {
169 path: self,
170 actor,
171 generation,
172 }))
173 }
174}
175
176fn exact(left: &[u8], right: &[u8], execution: &impl Work) -> Result<bool, MemoryError> {
177 execution.charge_work(1)?;
178 if core::ptr::eq(left, right) {
179 return Ok(true);
180 }
181 if left.len() != right.len() {
182 return Ok(false);
183 }
184 let work = u64::try_from(left.len())
185 .ok()
186 .and_then(|n| n.checked_mul(2))
187 .ok_or(MemoryError::WorkExceeded)?;
188 execution.charge_work(work)?;
189 Ok(left == right)
190}
191
192#[derive(Debug, Clone, Copy, PartialEq, Eq)]
194pub enum TimestampBindingFailure {
195 Certificate,
197 EffectiveKey,
199 Interval,
201}
202
203#[derive(Debug, Clone, Copy, PartialEq, Eq)]
205pub enum TimestampBindingError {
206 Resource(MemoryError),
208 Temporal(TemporalError),
210}
211
212pub struct TimestampPath<'view, 'certificate, 'document, N, D, G, I> {
215 path: &'view CheckedPath<'document>,
216 actor: &'view CheckedActor<'certificate, 'document, N, D, G, I>,
217 generation: GenerationInterval,
218}
219
220pub type TimestampBindingResult<'view, 'certificate, 'document, N, D, G, I> =
222 Result<TimestampBinding<'view, 'certificate, 'document, N, D, G, I>, TimestampBindingError>;
223
224impl<'view, 'certificate, 'document, N, D, G, I>
225 TimestampPath<'view, 'certificate, 'document, N, D, G, I>
226{
227 #[must_use]
229 pub fn path(&self) -> &'view CheckedPath<'document> {
230 self.path
231 }
232 #[must_use]
234 pub fn actor(&self) -> &'view CheckedActor<'certificate, 'document, N, D, G, I> {
235 self.actor
236 }
237 #[must_use]
239 pub fn generation(&self) -> GenerationInterval {
240 self.generation
241 }
242}
243
244pub enum TimestampBinding<'view, 'certificate, 'document, N, D, G, I> {
246 Bound(TimestampPath<'view, 'certificate, 'document, N, D, G, I>),
248 Rejected(TimestampBindingFailure),
250}
251
252#[derive(Debug)]
254#[expect(
255 clippy::large_enum_variant,
256 reason = "return the checked path and its existing accounted storage without a second heap allocation for enum layout"
257)]
258pub enum PathOutcome<'a> {
259 Accepted(CheckedPath<'a>),
261 Rejected(Rejection<'a>),
263 Incomplete(Truncation),
265}
266
267pub fn check_path<'a, V: Verify<E>, T: Watch, R: Recognizer<E>, E: Work>(
280 chain: &[&'a Certificate<'a>],
281 anchor: Anchor<'a>,
282 context: &ValidationContext<'a, '_, V, T, R, E>,
283) -> BuildResult<PathOutcome<'a>, V::Error, R::Error> {
284 let facts = match path::validate(chain, anchor, context)? {
285 Validation::Accepted(facts) => facts,
286 Validation::Rejected(reason) => return Ok(PathOutcome::Rejected(reason)),
287 Validation::Incomplete(reason) => return Ok(PathOutcome::Incomplete(reason)),
288 };
289 let execution = context.execution;
290 let mut members = execution.vector();
291 let fail = |reason| {
292 structured_pkix::Error::resource(reason, "ltv.validationPath", chain[0].span().start)
293 };
294 members.try_reserve_exact(chain.len()).map_err(fail)?;
295 let mut key = WorkingKey::standing(anchor.key());
296 for certificate in chain.iter().rev() {
297 key = key.subject(certificate, execution)?;
298 members
299 .try_push(Member { certificate, key })
300 .map_err(fail)?;
301 }
302 Ok(PathOutcome::Accepted(CheckedPath {
303 anchor,
304 interval: context.interval,
305 members: members.into_owned(),
306 facts,
307 }))
308}