structured_cades/material/
checks.rs1use 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 let selected = prepared.document().locate(prepared.signer().span());
122 query
123 .admit(selected.node, 1)
124 .map_err(|cause| CheckError::Index(IndexError::Scope(cause)))?;
125 if !core::ptr::eq(pool.prepared(), prepared)
126 || inputs.requirements().signature() != selected.node
127 || query.snapshot() != selected.snapshot
128 || !matches!(inputs.requirements().scope(), TimestampScope::All(_))
129 {
130 return Err(CheckError::Processing(Error::Foreign {
131 at: selected.node.span().start,
132 }));
133 }
134 index
135 .require_signature(selected.node, query)
136 .map_err(CheckError::Index)?;
137 let uses = pki::authority_uses(inputs, query).map_err(CheckError::Uses)?;
138 let artifacts = material::artifact_uses(&uses, query).map_err(CheckError::Uses)?;
139 let presence =
140 material::incorporation(&artifacts, index, query).map_err(CheckError::Presence)?;
141 let mut findings = query.execution().vector();
142 let archive_augmentation = prepared.requested() == crate::Level::Archival;
143 let resource = |cause| {
144 CheckError::Processing(Error::resource(
145 cause,
146 "cades.material",
147 selected.node.span().start,
148 ))
149 };
150 for (ordinal, entry) in artifacts.entries().iter().enumerate() {
151 query.execution().charge_work(1).map_err(resource)?;
152 let artifact = entry.artifact();
153 let mut copies = index.artifact(artifact, query).map_err(CheckError::Index)?;
154 let mut present = false;
155 let mut allowed = false;
156 while let Some(copy) = copies.next().transpose().map_err(CheckError::Index)? {
157 present = true;
158 allowed |= match artifact {
159 ArtifactRef::Certificate(_) => {
160 matches!(copy.placement(), Placement::RootCertificate(_))
164 || (archive_augmentation
165 && (matches!(copy.placement(), Placement::TokenCertificate { .. })
166 || matches!(copy.placement(), Placement::CertificateValues(location)
167 if archived_values(prepared, location))
168 || (copy.embedded_certificate().is_some()
169 && embedded_placement(prepared, copy.placement()))))
170 }
171 ArtifactRef::Crl(_) | ArtifactRef::Ocsp(_) => {
172 matches!(
173 copy.placement(),
174 Placement::RootRevocation(_) | Placement::TokenRevocation { .. }
175 ) || (archive_augmentation
176 && matches!(copy.placement(), Placement::CrlValues(location)
177 | Placement::OcspValues(location) if archived_values(prepared, location)))
178 }
179 };
180 }
181 if !present {
182 findings
183 .try_push(Finding::Missing { artifact: ordinal })
184 .map_err(resource)?;
185 } else if !allowed {
186 findings
187 .try_push(Finding::Placement { artifact: ordinal })
188 .map_err(resource)?;
189 }
190 }
191 for entry in pool.open() {
192 query.execution().charge_work(1).map_err(resource)?;
193 if entry.cause.is_some() {
194 findings
195 .try_push(Finding::Malformed { node: entry.node })
196 .map_err(resource)?;
197 }
198 }
199 super::legacy::malformed(
200 pool,
201 |node| {
202 query
203 .admit(node, 1)
204 .map_err(|cause| CheckError::Index(IndexError::Scope(cause)))
205 },
206 |node, fault| {
207 query.execution().charge_work(24).map_err(resource)?;
208 if fault.attribute == crate::properties::legacy::LEGACY[2]
209 || fault.attribute == crate::properties::legacy::LEGACY[3]
210 {
211 findings
212 .try_push(Finding::Malformed { node })
213 .map_err(resource)?;
214 }
215 Ok(())
216 },
217 )?;
218 Ok(Checks {
219 inputs,
220 presence,
221 findings: findings.into_owned(),
222 })
223}
224
225fn archived_values(prepared: &Preparation<'_, '_>, location: super::LegacyLocation) -> bool {
226 if !location.unsigned {
230 return false;
231 }
232 let Some(token) = location.token else {
233 return false;
234 };
235 prepared
236 .timestamps()
237 .get(token)
238 .is_some_and(|entry| entry.purpose() == structured_ltv::timestamp::Purpose::Archive)
239}
240
241fn embedded_placement(prepared: &Preparation<'_, '_>, placement: Placement) -> bool {
242 match placement {
243 Placement::CertificateValues(location)
244 | Placement::CrlValues(location)
245 | Placement::OcspValues(location) => archived_values(prepared, location),
246 _ => true,
247 }
248}