1use super::{Index, Occurrence, Placement};
4use crate::Xades;
5use crate::xml::{Access, Snapshot, Xml};
6use core::fmt;
7use structured_asn1::memory::{MemoryError, OwnedMap, OwnedVec, Work};
8use structured_ltv::material::{ArtifactRef, ArtifactUses, IndexError};
9use structured_ltv::pki::Uses;
10use structured_ltv::timestamp::TimestampScope;
11use structured_pkix::{Certificate, Version};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum ExclusiveFindingKind {
16 ForbiddenCa,
18 UnstatedCa,
21}
22
23pub struct ExclusiveFinding<'certificate, 'document, N, D, G> {
26 artifact: ArtifactRef<'certificate, 'document>,
27 certificate: &'certificate Certificate<'document>,
28 occurrence: Occurrence<N, D, G>,
29 kind: ExclusiveFindingKind,
30}
31
32impl<'certificate, 'document, N: Copy, D: Copy, G: Copy>
33 ExclusiveFinding<'certificate, 'document, N, D, G>
34{
35 #[must_use]
37 pub fn artifact(&self) -> ArtifactRef<'certificate, 'document> {
38 self.artifact
39 }
40 #[must_use]
42 pub fn certificate(&self) -> &'certificate Certificate<'document> {
43 self.certificate
44 }
45 #[must_use]
47 pub fn occurrence(&self) -> Occurrence<N, D, G> {
48 self.occurrence
49 }
50 #[must_use]
52 pub fn kind(&self) -> ExclusiveFindingKind {
53 self.kind
54 }
55}
56
57pub struct ExclusivePlacement<'certificate, 'document, N, D, G> {
61 snapshot: Snapshot<D, G>,
62 signature: N,
63 findings: OwnedVec<ExclusiveFinding<'certificate, 'document, N, D, G>>,
64}
65
66impl<'certificate, 'document, N: Copy, D: Copy, G: Copy>
67 ExclusivePlacement<'certificate, 'document, N, D, G>
68{
69 #[must_use]
71 pub fn snapshot(&self) -> Snapshot<D, G> {
72 self.snapshot
73 }
74 #[must_use]
76 pub fn signature(&self) -> N {
77 self.signature
78 }
79 #[must_use]
82 pub fn findings(&self) -> &[ExclusiveFinding<'certificate, 'document, N, D, G>] {
83 &self.findings
84 }
85}
86
87pub enum ExclusivityError<'certificate, 'document, N> {
89 Scope {
92 node: N,
94 },
95 Index(IndexError<N>),
97 Resource {
99 node: N,
101 cause: MemoryError,
103 },
104 Certificate {
108 certificate: &'certificate Certificate<'document>,
110 cause: structured_pkix::Error,
112 },
113}
114
115impl<N: fmt::Debug> fmt::Debug for ExclusivityError<'_, '_, N> {
116 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
117 match self {
118 Self::Scope { node } => formatter.debug_struct("Scope").field("node", node).finish(),
119 Self::Index(cause) => formatter.debug_tuple("Index").field(cause).finish(),
120 Self::Resource { node, cause } => formatter
121 .debug_struct("Resource")
122 .field("node", node)
123 .field("cause", cause)
124 .finish(),
125 Self::Certificate { cause, .. } => formatter
128 .debug_struct("Certificate")
129 .field("cause", cause)
130 .finish_non_exhaustive(),
131 }
132 }
133}
134
135pub type ExclusivityResult<'certificate, 'document, X> = Result<
137 ExclusivePlacement<
138 'certificate,
139 'document,
140 <X as Xml>::Id,
141 <X as Xml>::Document,
142 <X as Xml>::Generation,
143 >,
144 ExclusivityError<'certificate, 'document, <X as Xml>::Id>,
145>;
146
147#[derive(Debug, Clone, Copy, PartialEq, Eq)]
148pub(crate) enum CaFact {
149 Asserted,
150 NotAsserted,
151 Unstated,
152}
153
154pub(crate) enum CaError<'certificate, 'document> {
155 Resource(MemoryError),
156 Certificate(&'certificate Certificate<'document>, structured_pkix::Error),
157}
158
159impl fmt::Debug for CaError<'_, '_> {
160 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
161 match self {
162 Self::Resource(cause) => formatter.debug_tuple("Resource").field(cause).finish(),
163 Self::Certificate(_, cause) => formatter
164 .debug_struct("Certificate")
165 .field("cause", cause)
166 .finish_non_exhaustive(),
167 }
168 }
169}
170
171fn exclusive(uses: Uses) -> bool {
172 if uses.signature() {
173 return false;
174 }
175 uses.credential() || uses.timestamp()
176}
177
178fn exclusive_credential(uses: Uses) -> bool {
183 uses.credential() && !uses.signature() && !uses.timestamp()
184}
185
186fn certificate_ca<W: Work>(
187 certificate: &Certificate<'_>,
188 work: &W,
189) -> Result<CaFact, structured_pkix::Error> {
190 let basic = certificate.tbs().extensions().basic_constraints(work)?;
191 Ok(match basic {
197 Some(basic) if basic.is_authority() => CaFact::Asserted,
198 Some(_) => CaFact::NotAsserted,
199 None if certificate.tbs().version() == Version::V3 => CaFact::NotAsserted,
200 None => CaFact::Unstated,
201 })
202}
203
204pub(crate) struct ReferenceCa<'certificate, 'document> {
205 pub(crate) certificate: &'certificate Certificate<'document>,
206 pub(crate) fact: CaFact,
207}
208
209pub(crate) struct ReferenceStatus<'certificate, 'document> {
210 pub(crate) artifact: ArtifactRef<'certificate, 'document>,
211 pub(crate) certificate: &'certificate Certificate<'document>,
212 pub(crate) fact: CaFact,
213}
214
215pub(crate) fn reference_status_candidates<'certificate, 'document, N, D, G, I, W: Work>(
216 artifacts: &ArtifactUses<'_, '_, '_, 'certificate, 'document, N, D, G, I>,
217 work: &W,
218) -> Result<OwnedVec<ReferenceStatus<'certificate, 'document>, W>, CaError<'certificate, 'document>>
219{
220 let mut candidates = work.vector();
221 let mut scan = CaScan {
222 artifacts,
223 cache: work.map(),
224 work,
225 };
226 for (ordinal, entry) in artifacts.entries().iter().enumerate() {
227 work.charge_work(1)?;
228 if !exclusive_credential(entry.uses())
229 || matches!(entry.artifact(), ArtifactRef::Certificate(_))
230 {
231 continue;
232 }
233 if let Some((fact, target)) = scan.witness(ordinal, exclusive_credential)? {
237 candidates.try_push(ReferenceStatus {
238 artifact: entry.artifact(),
239 certificate: scan.certificate(target),
240 fact,
241 })?;
242 }
243 }
244 Ok(candidates)
245}
246
247pub(crate) fn reference_certificate_candidates<'certificate, 'document, N, D, G, I, W: Work>(
248 artifacts: &ArtifactUses<'_, '_, '_, 'certificate, 'document, N, D, G, I>,
249 work: &W,
250) -> Result<OwnedVec<ReferenceCa<'certificate, 'document>, W>, CaError<'certificate, 'document>> {
251 let mut candidates = work.vector();
252 for entry in artifacts.entries() {
253 work.charge_work(1)?;
254 if !exclusive_credential(entry.uses()) {
255 continue;
256 }
257 let ArtifactRef::Certificate(certificate) = entry.artifact() else {
258 continue;
259 };
260 let fact = certificate_ca(certificate, work)
261 .map_err(|cause| CaError::Certificate(certificate, cause))?;
262 if fact != CaFact::NotAsserted {
263 candidates.try_push(ReferenceCa { certificate, fact })?;
264 }
265 }
266 Ok(candidates)
267}
268
269impl From<MemoryError> for CaError<'_, '_> {
270 fn from(cause: MemoryError) -> Self {
271 Self::Resource(cause)
272 }
273}
274
275trait Census<'certificate, 'document> {
277 fn artifact(&self, ordinal: usize) -> ArtifactRef<'certificate, 'document>;
278 fn uses(&self, ordinal: usize) -> Uses;
279 fn targets(&self, ordinal: usize) -> impl Iterator<Item = usize>;
280}
281
282impl<'certificate, 'document, N, D, G, I> Census<'certificate, 'document>
283 for ArtifactUses<'_, '_, '_, 'certificate, 'document, N, D, G, I>
284{
285 fn artifact(&self, ordinal: usize) -> ArtifactRef<'certificate, 'document> {
286 self.entries()[ordinal].artifact()
287 }
288 fn uses(&self, ordinal: usize) -> Uses {
289 self.entries()[ordinal].uses()
290 }
291 fn targets(&self, ordinal: usize) -> impl Iterator<Item = usize> {
292 ArtifactUses::targets(self, ordinal)
293 }
294}
295
296struct CaScan<'query, C, W: Work> {
297 artifacts: &'query C,
298 cache: OwnedMap<usize, CaFact, W>,
299 work: &'query W,
300}
301
302impl<'certificate, 'document, C: Census<'certificate, 'document>, W: Work> CaScan<'_, C, W> {
303 fn certificate(&self, index: usize) -> &'certificate Certificate<'document> {
304 let ArtifactRef::Certificate(certificate) = self.artifacts.artifact(index) else {
305 unreachable!("a status target is an admitted certificate ordinal")
306 };
307 certificate
308 }
309
310 fn class(&mut self, index: usize) -> Result<CaFact, CaError<'certificate, 'document>> {
311 self.work.charge_work(1)?;
312 if let Some(&fact) = self.cache.get(&index, self.work)? {
313 return Ok(fact);
314 }
315 let certificate = self.certificate(index);
316 let fact = certificate_ca(certificate, self.work)
317 .map_err(|cause| CaError::Certificate(certificate, cause))?;
318 self.cache.try_insert(index, fact)?;
319 Ok(fact)
320 }
321
322 fn witness(
323 &mut self,
324 index: usize,
325 accepts: impl Fn(Uses) -> bool,
326 ) -> Result<Option<(CaFact, usize)>, CaError<'certificate, 'document>> {
327 if matches!(self.artifacts.artifact(index), ArtifactRef::Certificate(_)) {
328 return match self.class(index)? {
329 CaFact::NotAsserted => Ok(None),
330 fact => Ok(Some((fact, index))),
331 };
332 }
333 let mut unstated = None;
334 let artifacts = self.artifacts;
335 for certificate in artifacts.targets(index) {
336 self.work.charge_work(1)?;
337 let uses = artifacts.uses(certificate);
338 if !accepts(uses) {
339 continue;
340 }
341 match self.class(certificate)? {
342 CaFact::Asserted => return Ok(Some((CaFact::Asserted, certificate))),
343 CaFact::Unstated => unstated = Some((CaFact::Unstated, certificate)),
344 CaFact::NotAsserted => {}
345 }
346 }
347 Ok(unstated)
348 }
349}
350
351pub fn exclusivity<'certificate, 'document, X: Xml, W: Work, Q: Work>(
367 artifacts: &ArtifactUses<
368 '_,
369 '_,
370 '_,
371 'certificate,
372 'document,
373 X::Id,
374 X::Document,
375 X::Generation,
376 Xades,
377 >,
378 index: &Index<'_, X::Id, X::Document, X::Generation, W>,
379 access: &Access<'_, X, Q>,
380) -> ExclusivityResult<'certificate, 'document, X> {
381 let required = artifacts.inputs().requirements();
382 let scope = required.scope();
383 if !matches!(scope, TimestampScope::All(_)) || scope.location().snapshot != access.snapshot() {
384 return Err(ExclusivityError::Scope {
385 node: scope.location().node,
386 });
387 }
388 let signature = required.signature();
389 index
390 .require_signature(signature, access)
391 .map_err(ExclusivityError::Index)?;
392 let work = access.execution();
393 let resource = |cause| ExclusivityError::Resource {
394 node: signature,
395 cause,
396 };
397 let mut findings = work.vector();
398 let mut scan = CaScan {
399 artifacts,
400 cache: work.map(),
401 work,
402 };
403 for (ordinal, entry) in artifacts.entries().iter().enumerate() {
404 work.charge_work(1).map_err(resource)?;
405 let uses = entry.uses();
406 if !exclusive(uses) {
407 continue;
408 }
409 let mut classified = false;
410 let mut witness = None;
411 for copy in index
412 .artifact(entry.artifact(), access)
413 .map_err(ExclusivityError::Index)?
414 {
415 let copy = copy.map_err(ExclusivityError::Index)?;
416 if !matches!(copy.placement(), Placement::Signature(_))
417 || copy.embedded_certificate().is_some()
418 {
419 continue;
420 }
421 if !classified {
422 witness = scan
423 .witness(ordinal, exclusive)
424 .map_err(|cause| match cause {
425 CaError::Resource(cause) => resource(cause),
426 CaError::Certificate(certificate, cause) => {
427 ExclusivityError::Certificate { certificate, cause }
428 }
429 })?;
430 classified = true;
431 }
432 if let Some((fact, certificate)) = witness {
433 let kind = match fact {
434 CaFact::Asserted => ExclusiveFindingKind::ForbiddenCa,
435 CaFact::Unstated => ExclusiveFindingKind::UnstatedCa,
436 CaFact::NotAsserted => unreachable!("non-CA produces no witness"),
437 };
438 findings
439 .try_push(ExclusiveFinding {
440 artifact: entry.artifact(),
441 certificate: scan.certificate(certificate),
442 occurrence: copy,
443 kind,
444 })
445 .map_err(resource)?;
446 }
447 }
448 }
449 Ok(ExclusivePlacement {
450 snapshot: access.snapshot(),
451 signature,
452 findings: findings.into_owned(),
453 })
454}
455
456#[cfg(test)]
457mod tests;