structured_xades/validation/
ref_presence.rs1use super::{ReferenceDigests, ReferenceSearch};
4use crate::{
5 Xades,
6 certificate::Agreement,
7 pki::CounterPaths,
8 unsigned,
9 xml::{Access, AccessError, Xml},
10};
11use structured_asn1::memory::{MemoryError, OwnedVec, Work};
12use structured_ltv::timestamp::{TimestampRequirements, TimestampScope};
13
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
16pub enum ReferencePresenceFailure {
17 Missing,
19 IdentifierOnly,
22 OtherFormat,
24 Unavailable(Agreement),
26}
27
28pub struct ReferencePresenceRow {
30 required: bool,
31 failure: Option<ReferencePresenceFailure>,
32}
33impl ReferencePresenceRow {
34 #[must_use]
36 pub fn required(&self) -> bool {
37 self.required
38 }
39 #[must_use]
42 pub fn failure(&self) -> Option<ReferencePresenceFailure> {
43 self.failure
44 }
45}
46
47pub struct ReferencePresence<'report, 'references, 'source, N, D, G> {
49 digests: &'report ReferenceDigests<'references, 'source, N, D, G>,
50 rows: OwnedVec<ReferencePresenceRow>,
51 all_present: bool,
52}
53impl<'report, 'references, 'source, N, D, G>
54 ReferencePresence<'report, 'references, 'source, N, D, G>
55{
56 #[must_use]
58 pub fn digests(&self) -> &'report ReferenceDigests<'references, 'source, N, D, G> {
59 self.digests
60 }
61 #[must_use]
63 pub fn rows(&self) -> &[ReferencePresenceRow] {
64 &self.rows
65 }
66 #[must_use]
68 pub fn all_required_present(&self) -> bool {
69 self.all_present
70 }
71}
72
73#[derive(Debug)]
75pub enum ReferencePresenceError<E, N> {
76 Access(AccessError<E, N>),
79 Scope,
82 Resource(MemoryError),
84}
85
86pub type ReferencePresenceResult<'report, 'references, 'source, X> = Result<
88 ReferencePresence<
89 'report,
90 'references,
91 'source,
92 <X as Xml>::Id,
93 <X as Xml>::Document,
94 <X as Xml>::Generation,
95 >,
96 ReferencePresenceError<<X as Xml>::Error, <X as Xml>::Id>,
97>;
98
99pub fn reference_presence<'report, 'references, 'source, X: Xml, W: Work>(
117 digests: &'report ReferenceDigests<'references, 'source, X::Id, X::Document, X::Generation>,
118 required: &TimestampRequirements<X::Id, X::Document, X::Generation, Xades>,
119 counters: &CounterPaths<'_, '_, X::Id, X::Document, X::Generation>,
120 access: &Access<'_, X, W>,
121) -> ReferencePresenceResult<'report, 'references, 'source, X> {
122 require_sources(digests, required, counters, access)?;
123 let prepared = digests.prepared();
124 let mut certificates = false;
125 let mut revocations = false;
126 for entry in prepared.unsigned().entries() {
127 access
128 .execution()
129 .charge_work(1)
130 .map_err(ReferencePresenceError::Resource)?;
131 match entry.role {
132 unsigned::Role::Certificates | unsigned::Role::AttributeCertificates => {
133 certificates = true;
134 }
135 unsigned::Role::Revocation | unsigned::Role::AttributeRevocation => revocations = true,
136 unsigned::Role::Archive => {
137 certificates = true;
138 revocations = true;
139 }
140 _ => (),
141 }
142 }
143 for data in prepared.validation() {
144 access
145 .execution()
146 .charge_work(1)
147 .map_err(ReferencePresenceError::Resource)?;
148 if data.role() == super::Role::Any {
149 certificates |= data
150 .certificates()
151 .is_some_and(|values| !values.entries().is_empty());
152 revocations |= data
153 .revocations()
154 .is_some_and(|values| !values.entries().is_empty());
155 }
156 }
157 let mut rows = access.execution().vector();
158 rows.try_reserve_exact(digests.rows().len())
159 .map_err(ReferencePresenceError::Resource)?;
160 let mut all_present = true;
161 for (ordinal, row) in digests.rows().iter().enumerate() {
162 access
163 .execution()
164 .charge_work(1)
165 .map_err(ReferencePresenceError::Resource)?;
166 let property = &prepared.validation_references()[row.property()];
167 let required = match property.role() {
168 unsigned::Role::CertificateReferences
169 | unsigned::Role::AttributeCertificateReferences => certificates,
170 unsigned::Role::RevocationReferences
171 | unsigned::Role::AttributeRevocationReferences => revocations,
172 _ => unreachable!("privately constructed reference property role"),
173 };
174 let failure = if required {
175 presence_failure(
176 row.search(),
177 digests
178 .comparisons(ordinal)
179 .expect("original comparison row"),
180 access.execution(),
181 )
182 .map_err(ReferencePresenceError::Resource)?
183 } else {
184 None
185 };
186 if failure.is_some() {
187 all_present = false;
188 }
189 rows.try_push(ReferencePresenceRow { required, failure })
190 .map_err(ReferencePresenceError::Resource)?;
191 }
192 access
193 .query(required.signature(), |_, _, _| Ok(()))
194 .map_err(ReferencePresenceError::Access)?;
195 Ok(ReferencePresence {
196 digests,
197 rows: rows.into_owned(),
198 all_present,
199 })
200}
201
202fn presence_failure<W: Work>(
203 search: ReferenceSearch,
204 values: &[super::MaterialDigest],
205 work: &W,
206) -> Result<Option<ReferencePresenceFailure>, MemoryError> {
207 let mut unavailable = None;
208 match search {
209 ReferenceSearch::IdentifierOnly => {
210 return Ok(Some(ReferencePresenceFailure::IdentifierOnly));
211 }
212 ReferenceSearch::OtherFormat => return Ok(Some(ReferencePresenceFailure::OtherFormat)),
213 ReferenceSearch::Unavailable(cause) => {
214 return Ok(Some(ReferencePresenceFailure::Unavailable(cause)));
215 }
216 ReferenceSearch::Compared => (),
217 }
218 for value in values {
219 work.charge_work(1)?;
220 match value.agreement() {
221 Agreement::Matches => return Ok(None),
222 Agreement::Differs => (),
223 cause => {
224 if unavailable.is_none() {
225 unavailable = Some(cause);
226 }
227 }
228 }
229 }
230 Ok(Some(unavailable.map_or(
231 ReferencePresenceFailure::Missing,
232 ReferencePresenceFailure::Unavailable,
233 )))
234}
235
236fn require_sources<X: Xml, W: Work>(
237 digests: &ReferenceDigests<'_, '_, X::Id, X::Document, X::Generation>,
238 required: &TimestampRequirements<X::Id, X::Document, X::Generation, Xades>,
239 counters: &CounterPaths<'_, '_, X::Id, X::Document, X::Generation>,
240 access: &Access<'_, X, W>,
241) -> Result<(), ReferencePresenceError<X::Error, X::Id>> {
242 let original = digests.prepared().bound().containers();
243 let TimestampScope::All(scope) = required.scope() else {
244 return Err(ReferencePresenceError::Scope);
245 };
246 if original.snapshot() != access.snapshot()
247 || scope.snapshot != original.snapshot()
248 || counters.snapshot() != original.snapshot()
249 {
250 return Err(ReferencePresenceError::Scope);
251 }
252 access
253 .query(original.signature(), |_, _, _| Ok(()))
254 .map_err(ReferencePresenceError::Access)?;
255 if !access
256 .equal_id(scope.node, original.signature())
257 .map_err(ReferencePresenceError::Resource)?
258 || !access
259 .equal_id(required.signature(), original.signature())
260 .map_err(ReferencePresenceError::Resource)?
261 || !access
262 .equal_id(counters.signature(), original.signature())
263 .map_err(ReferencePresenceError::Resource)?
264 || !required
265 .includes_counters(counters, access)
266 .map_err(|cause| ReferencePresenceError::Access(cause.into()))?
267 {
268 return Err(ReferencePresenceError::Scope);
269 }
270 let Some(sources) = digests.tokens() else {
271 if required.entries().is_empty() {
272 return Ok(());
273 }
274 return Err(ReferencePresenceError::Scope);
275 };
276 if sources.len() != required.entries().len() {
277 return Err(ReferencePresenceError::Scope);
278 }
279 for (source, entry) in sources.iter().zip(required.entries()) {
280 access
281 .execution()
282 .charge_work(1)
283 .map_err(ReferencePresenceError::Resource)?;
284 if source.location().snapshot != original.snapshot()
285 || source.role().purpose() != entry.purpose
286 || !access
287 .equal_id(source.signature(), entry.signature)
288 .map_err(ReferencePresenceError::Resource)?
289 || !access
290 .equal_id(source.property(), entry.property)
291 .map_err(ReferencePresenceError::Resource)?
292 || !access
293 .equal_id(source.location().node, entry.token)
294 .map_err(ReferencePresenceError::Resource)?
295 {
296 return Err(ReferencePresenceError::Scope);
297 }
298 }
299 Ok(())
300}