1use super::{Index, Placement, Pool};
4use crate::{
5 Cades, Error,
6 prepare::Preparation,
7 source::{DocumentId, Node},
8};
9use structured_asn1::memory::{OwnedVec, Work};
10use structured_ltv::{
11 baseline::X509Material,
12 material::{self, ArtifactRef, Incorporation, IncorporationError, IndexError},
13 pki::{self, ParticipantClosure, ParticipantRequirements, UsesError},
14 source::Query,
15 timestamp::TimestampScope,
16};
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum Finding {
21 Missing {
23 artifact: usize,
25 },
26 Placement {
28 artifact: usize,
30 },
31 Malformed {
33 node: Node,
35 },
36}
37
38pub struct Checks<'closure, 'requirements, 'view, 'certificate, 'document, 'model> {
42 inputs: &'closure ParticipantClosure<
43 'requirements,
44 'view,
45 'certificate,
46 'document,
47 Node,
48 DocumentId<'model, 'document>,
49 (),
50 Cades,
51 >,
52 presence:
53 Incorporation<'certificate, 'document, Node, DocumentId<'model, 'document>, (), Placement>,
54 findings: OwnedVec<Finding>,
55}
56impl<'d, 'm> Checks<'_, '_, '_, '_, 'd, 'm> {
57 #[must_use]
59 pub fn presence(&self) -> &Incorporation<'_, 'd, Node, DocumentId<'m, 'd>, (), Placement> {
60 &self.presence
61 }
62 #[must_use]
64 pub fn findings(&self) -> &[Finding] {
65 &self.findings
66 }
67 #[must_use]
70 pub fn all_x509_requirements_met(&self) -> bool {
71 self.findings.is_empty() && self.presence.all_present()
72 }
73}
74impl<'v, 'd, 'm> X509Material<'v, 'd> for Checks<'_, '_, 'v, '_, 'd, 'm> {
75 type Id = Node;
76 type Document = DocumentId<'m, 'd>;
77 type Generation = ();
78 type Issuer = Cades;
79 fn requirements(
80 &self,
81 ) -> &ParticipantRequirements<'v, 'd, Node, DocumentId<'m, 'd>, (), Cades> {
82 self.inputs.requirements()
83 }
84 fn all_x509_requirements_met(&self) -> bool {
85 Self::all_x509_requirements_met(self)
86 }
87}
88
89#[derive(Debug)]
91pub enum CheckError {
92 Processing(Error),
94 Uses(UsesError<Node>),
96 Presence(IncorporationError<Node>),
98 Index(IndexError<Node>),
100}
101
102pub fn check_participants<'cl, 'r, 'v, 'c, 'd, 'm, Q, W>(
111 prepared: &Preparation<'m, 'd>,
112 pool: &Pool<'_, 'm, 'd>,
113 inputs: &'cl ParticipantClosure<'r, 'v, 'c, 'd, Node, DocumentId<'m, 'd>, (), Cades>,
114 index: &Index<'_, 'm, 'd, W>,
115 query: &Q,
116) -> Result<Checks<'cl, 'r, 'v, 'c, 'd, 'm>, CheckError>
117where
118 Q: Query<Id = Node, Document = DocumentId<'m, 'd>, Generation = ()>,
119 W: Work,
120{
121 check_in(prepared, pool, inputs, index, None, query)
122}
123
124pub fn check_participants_preserved<'cl, 'r, 'v, 'c, 'd, 'm, Q, W>(
129 prepared: &Preparation<'m, 'd>,
130 pool: &Pool<'_, 'm, 'd>,
131 inputs: &'cl ParticipantClosure<'r, 'v, 'c, 'd, Node, DocumentId<'m, 'd>, (), Cades>,
132 index: &Index<'_, 'm, 'd, W>,
133 preservation: &crate::evidence::Closure<'_, '_, 'm, 'd, '_>,
134 query: &Q,
135) -> Result<Checks<'cl, 'r, 'v, 'c, 'd, 'm>, CheckError>
136where
137 Q: Query<Id = Node, Document = DocumentId<'m, 'd>, Generation = ()>,
138 W: Work,
139{
140 check_in(prepared, pool, inputs, index, Some(preservation), query)
141}
142fn check_in<'cl, 'r, 'v, 'c, 'd, 'm, Q, W>(
143 prepared: &Preparation<'m, 'd>,
144 pool: &Pool<'_, 'm, 'd>,
145 inputs: &'cl ParticipantClosure<'r, 'v, 'c, 'd, Node, DocumentId<'m, 'd>, (), Cades>,
146 index: &Index<'_, 'm, 'd, W>,
147 preservation: Option<&crate::evidence::Closure<'_, '_, 'm, 'd, '_>>,
148 query: &Q,
149) -> Result<Checks<'cl, 'r, 'v, 'c, 'd, 'm>, CheckError>
150where
151 Q: Query<Id = Node, Document = DocumentId<'m, 'd>, Generation = ()>,
152 W: Work,
153{
154 if let Some(proofs) = preservation
155 && !core::ptr::eq(proofs.prepared(), prepared)
156 {
157 return Err(CheckError::Processing(Error::Foreign {
158 at: prepared.signer().span().start,
159 }));
160 }
161 let selected = prepared.document().locate(prepared.signer().span());
162 query
163 .admit(selected.node, 1)
164 .map_err(|cause| CheckError::Index(IndexError::Scope(cause)))?;
165 if !core::ptr::eq(pool.prepared(), prepared)
166 || inputs.requirements().signature() != selected.node
167 || query.snapshot() != selected.snapshot
168 || !matches!(inputs.requirements().scope(), TimestampScope::All(_))
169 {
170 return Err(CheckError::Processing(Error::Foreign {
171 at: selected.node.span().start,
172 }));
173 }
174 index
175 .require_signature(selected.node, query)
176 .map_err(CheckError::Index)?;
177 let uses = pki::authority_uses(inputs, query).map_err(CheckError::Uses)?;
178 let artifacts = material::artifact_uses(&uses, query).map_err(CheckError::Uses)?;
179 let presence =
180 material::incorporation(&artifacts, index, query).map_err(CheckError::Presence)?;
181 let mut findings = query.execution().vector();
182 let archive_augmentation = prepared.requested() == crate::Level::Archival;
183 let resource = |cause| {
184 CheckError::Processing(Error::resource(
185 cause,
186 "cades.material",
187 selected.node.span().start,
188 ))
189 };
190 for (ordinal, entry) in artifacts.entries().iter().enumerate() {
191 query.execution().charge_work(1).map_err(resource)?;
192 let artifact = entry.artifact();
193 let mut copies = index.artifact(artifact, query).map_err(CheckError::Index)?;
194 let mut present = false;
195 let mut allowed = false;
196 while let Some(copy) = copies.next().transpose().map_err(CheckError::Index)? {
197 present = true;
198 let poe = match copy.placement() {
199 Placement::PoeCertificate(location) | Placement::PoeRevocation(location)
200 if archive_augmentation =>
201 {
202 preserved_extra(prepared, preservation, location, query)?
203 }
204 _ => false,
205 };
206 allowed |= match artifact {
207 ArtifactRef::Certificate(_) => {
208 matches!(copy.placement(), Placement::RootCertificate(_))
212 || poe
213 || (archive_augmentation
214 && (matches!(copy.placement(), Placement::TokenCertificate { .. })
215 || matches!(copy.placement(), Placement::CertificateValues(location)
216 if archived_values(prepared, location))
217 || (copy.embedded_certificate().is_some()
218 && embedded_placement(prepared, copy.placement()))))
219 }
220 ArtifactRef::Crl(_) | ArtifactRef::Ocsp(_) => {
221 matches!(
222 copy.placement(),
223 Placement::RootRevocation(_) | Placement::TokenRevocation { .. }
224 ) || poe
225 || (archive_augmentation
226 && matches!(copy.placement(), Placement::CrlValues(location)
227 | Placement::OcspValues(location) if archived_values(prepared, location)))
228 }
229 };
230 }
231 if !present {
232 findings
233 .try_push(Finding::Missing { artifact: ordinal })
234 .map_err(resource)?;
235 } else if !allowed {
236 findings
237 .try_push(Finding::Placement { artifact: ordinal })
238 .map_err(resource)?;
239 }
240 }
241 retain_malformed(pool, query, selected.node, &mut findings)?;
242 Ok(Checks {
243 inputs,
244 presence,
245 findings: findings.into_owned(),
246 })
247}
248
249fn retain_malformed<'m, 'd, Q, W: Work>(
250 pool: &Pool<'_, 'm, 'd>,
251 query: &Q,
252 selected: Node,
253 findings: &mut OwnedVec<Finding, W>,
254) -> Result<(), CheckError>
255where
256 Q: Query<Id = Node, Document = DocumentId<'m, 'd>, Generation = ()>,
257{
258 let resource = |cause| {
259 CheckError::Processing(Error::resource(
260 cause,
261 "cades.material",
262 selected.span().start,
263 ))
264 };
265 for entry in pool.open() {
266 query.execution().charge_work(1).map_err(resource)?;
267 if entry.cause.is_some() {
268 findings
269 .try_push(Finding::Malformed { node: entry.node })
270 .map_err(resource)?;
271 }
272 }
273 super::legacy::malformed(
274 pool,
275 |node| {
276 query
277 .admit(node, 1)
278 .map_err(|cause| CheckError::Index(IndexError::Scope(cause)))
279 },
280 |node, fault| {
281 query.execution().charge_work(24).map_err(resource)?;
282 if fault.attribute == crate::properties::legacy::LEGACY[2]
283 || fault.attribute == crate::properties::legacy::LEGACY[3]
284 {
285 findings
286 .try_push(Finding::Malformed { node })
287 .map_err(resource)?;
288 }
289 Ok(())
290 },
291 )?;
292 Ok(())
293}
294
295fn preserved_extra<'m, 'd, Q>(
296 prepared: &Preparation<'m, 'd>,
297 preservation: Option<&crate::evidence::Closure<'_, '_, 'm, 'd, '_>>,
298 location: super::PoeLocation,
299 query: &Q,
300) -> Result<bool, CheckError>
301where
302 Q: Query<Id = Node, Document = DocumentId<'m, 'd>, Generation = ()>,
303{
304 let resource = |cause| {
305 CheckError::Processing(Error::resource(
306 cause,
307 "cades.material",
308 location.property.span().start,
309 ))
310 };
311 query.execution().charge_work(1).map_err(resource)?;
312 let Some(proofs) = preservation else {
313 return Ok(false);
314 };
315 let entry = prepared
316 .parsed
317 .proofs
318 .entries
319 .get(location.proof)
320 .ok_or_else(|| {
321 CheckError::Processing(Error::Foreign {
322 at: location.property.span().start,
323 })
324 })?;
325 if entry.required.signature != location.signature
326 || entry.required.property != location.property
327 {
328 return Err(CheckError::Processing(Error::Foreign {
329 at: location.property.span().start,
330 }));
331 }
332 proofs
333 .contains(&entry.required, query.execution())
334 .map_err(resource)
335}
336
337fn archived_values(prepared: &Preparation<'_, '_>, location: super::LegacyLocation) -> bool {
338 if !location.unsigned {
342 return false;
343 }
344 let Some(token) = location.token else {
345 return false;
346 };
347 prepared
348 .timestamps()
349 .get(token)
350 .is_some_and(|entry| entry.purpose() == structured_ltv::timestamp::Purpose::Archive)
351}
352
353fn embedded_placement(prepared: &Preparation<'_, '_>, placement: Placement) -> bool {
354 match placement {
355 Placement::CertificateValues(location)
356 | Placement::CrlValues(location)
357 | Placement::OcspValues(location) => archived_values(prepared, location),
358 _ => true,
359 }
360}