1use super::{ReferenceDigestError, ReferenceSearch, ref_catalogue::Memo};
4use crate::{
5 Xades,
6 binary::{self, BinaryXml, Location},
7 certificate::{Agreement, DigestProvider, FormatDigest},
8 digest,
9 prepare::{Preparation, Prepared},
10 unsigned,
11 xml::{Access, Xml},
12};
13use core::ops::Range;
14use structured_asn1::digest::Digest;
15use structured_asn1::memory::{MemoryError, OwnedMap, OwnedVec, Work};
16use structured_ltv::material::PathCertificate;
17use structured_ltv::pki::{Participant, ParticipantAuthority, ParticipantRequirements};
18use structured_ltv::timestamp::TimestampScope;
19use structured_pkix::Certificate;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum CredentialReferencePlace {
25 Protected(usize),
27 Ordinary {
29 property: usize,
31 entry: usize,
33 },
34 Attribute {
36 property: usize,
38 entry: usize,
40 },
41}
42
43pub struct CredentialCertificate<'view, 'document> {
45 participant: usize,
46 role: PathCertificate,
47 certificate: &'view Certificate<'document>,
48 ordinary: bool,
49 protected: bool,
50 attribute: bool,
51}
52impl<'v, 'd> CredentialCertificate<'v, 'd> {
53 #[must_use]
55 pub fn participant(&self) -> usize {
56 self.participant
57 }
58 #[must_use]
60 pub fn role(&self) -> PathCertificate {
61 self.role
62 }
63 #[must_use]
65 pub fn certificate(&self) -> &'v Certificate<'d> {
66 self.certificate
67 }
68 #[must_use]
70 pub fn presence(&self) -> (bool, bool, bool) {
71 (self.protected, self.ordinary, self.attribute)
72 }
73 #[must_use]
75 pub fn redundant_attribute_reference(&self) -> bool {
76 self.attribute && (self.ordinary || self.protected)
77 }
78}
79
80pub struct CredentialCertificateRow<N> {
82 place: CredentialReferencePlace,
83 node: N,
84 search: ReferenceSearch,
85 range: Range<usize>,
86}
87impl<N: Copy> CredentialCertificateRow<N> {
88 #[must_use]
90 pub fn place(&self) -> CredentialReferencePlace {
91 self.place
92 }
93 #[must_use]
95 pub fn node(&self) -> N {
96 self.node
97 }
98 #[must_use]
100 pub fn search(&self) -> ReferenceSearch {
101 self.search
102 }
103}
104
105pub struct CredentialCertificateReferences<'p, 'v, 'd, N, D, G> {
108 prepared: &'p Preparation<N, D, G>,
109 candidates: OwnedVec<CredentialCertificate<'v, 'd>>,
110 rows: OwnedVec<CredentialCertificateRow<N>>,
111 comparisons: OwnedVec<Agreement>,
112 present: bool,
113 covered: bool,
114}
115impl<'p, 'v, 'd, N, D, G> CredentialCertificateReferences<'p, 'v, 'd, N, D, G> {
116 #[must_use]
118 pub fn prepared(&self) -> &'p Preparation<N, D, G> {
119 self.prepared
120 }
121 #[must_use]
123 pub fn property_present(&self) -> bool {
124 self.present
125 }
126 #[must_use]
129 pub fn candidates(&self) -> &[CredentialCertificate<'v, 'd>] {
130 &self.candidates
131 }
132 #[must_use]
134 pub fn rows(&self) -> &[CredentialCertificateRow<N>] {
135 &self.rows
136 }
137 #[must_use]
139 pub fn comparisons(&self, row: usize) -> Option<&[Agreement]> {
140 self.comparisons.get(self.rows.get(row)?.range.clone())
141 }
142 #[must_use]
145 pub fn all_required_referenced(&self) -> bool {
146 self.covered
147 }
148}
149
150pub type CredentialCertificateResult<'p, 'v, 'd, X, E> = binary::BinaryResult<
152 CredentialCertificateReferences<
153 'p,
154 'v,
155 'd,
156 <X as Xml>::Id,
157 <X as Xml>::Document,
158 <X as Xml>::Generation,
159 >,
160 X,
161 ReferenceDigestError<E>,
162>;
163
164fn candidates<'v, 'd, N: Copy, D: Copy, G: Copy, W: Work>(
165 required: &ParticipantRequirements<'v, 'd, N, D, G, Xades>,
166 work: &W,
167) -> Result<OwnedVec<CredentialCertificate<'v, 'd>, W>, MemoryError> {
168 let mut values = work.vector();
169 let mut push = |participant, role, certificate| {
170 values.try_push(CredentialCertificate {
171 participant,
172 role,
173 certificate,
174 ordinary: false,
175 protected: false,
176 attribute: false,
177 })
178 };
179 for (ordinal, entry) in required.entries().iter().enumerate() {
180 work.charge_work(1)?;
181 if !matches!(entry.participant(), Participant::Credential { .. }) {
182 continue;
183 }
184 match entry.authority() {
185 ParticipantAuthority::Path(path) => {
186 if let Some(certificate) = path.anchor().certificate() {
187 push(ordinal, PathCertificate::Anchor, certificate)?;
188 }
189 for (member, value) in path.members().iter().enumerate() {
190 work.charge_work(1)?;
191 push(
192 ordinal,
193 PathCertificate::Member(member),
194 value.certificate(),
195 )?;
196 }
197 }
198 ParticipantAuthority::Anchor(anchor) => {
199 if let Some(certificate) = anchor.anchor().certificate() {
200 push(ordinal, PathCertificate::Anchor, certificate)?;
201 }
202 }
203 ParticipantAuthority::CertificateFree => (),
204 }
205 }
206 Ok(values)
207}
208
209struct CredentialScan<'provider, 'view, 'document, P: Digest<'provider>, W: Work> {
210 memo: Memo<'provider, 'document, P, W>,
211 candidates: Option<OwnedVec<CredentialCertificate<'view, 'document>, W>>,
212 ids: OwnedVec<usize, W>,
213 comparisons: OwnedVec<Agreement, W>,
214}
215impl<'q, 'v, 'd, P: Digest<'q>, W: Work> CredentialScan<'q, 'v, 'd, P, W> {
216 fn compare<N: Copy, D: Copy, G: Copy>(
217 &mut self,
218 identity: FormatDigest<P::Identity>,
219 expected: &[u8],
220 place: CredentialReferencePlace,
221 required: &ParticipantRequirements<'v, 'd, N, D, G, Xades>,
222 work: &W,
223 ) -> Result<(), digest::Error<structured_cms::stream::RangesError, P::Error>> {
224 let method = self.memo.method(identity, work)?;
225 if self.candidates.is_none() {
226 let values = candidates(required, work).map_err(digest::Error::Resource)?;
227 let mut index: OwnedMap<&[u8], usize, W> = work.map();
228 for (ordinal, candidate) in values.iter().enumerate() {
229 work.charge_work(1).map_err(digest::Error::Resource)?;
230 let bytes = candidate.certificate.as_bytes();
231 let id =
232 if let Some(&id) = index.get(&bytes, work).map_err(digest::Error::Resource)? {
233 id
234 } else {
235 index
236 .try_insert(bytes, ordinal)
237 .map_err(digest::Error::Resource)?;
238 ordinal
239 };
240 self.ids.try_push(id).map_err(digest::Error::Resource)?;
241 }
242 self.candidates = Some(values);
243 }
244 let values = self
245 .candidates
246 .as_mut()
247 .expect("initialized credential paths");
248 for (ordinal, candidate) in values.iter_mut().enumerate() {
249 work.charge_work(1).map_err(digest::Error::Resource)?;
250 self.comparisons
251 .try_reserve(1)
252 .map_err(digest::Error::Resource)?;
253 let agreement = self.memo.compare(
254 self.ids[ordinal],
255 candidate.certificate.as_bytes(),
256 method,
257 expected,
258 work,
259 )?;
260 if agreement == Agreement::Matches {
261 match place {
262 CredentialReferencePlace::Protected(_) => candidate.protected = true,
263 CredentialReferencePlace::Ordinary { .. } => candidate.ordinary = true,
264 CredentialReferencePlace::Attribute { .. } => candidate.attribute = true,
265 }
266 }
267 self.comparisons
268 .try_push(agreement)
269 .map_err(digest::Error::Resource)?;
270 }
271 Ok(())
272 }
273}
274
275pub fn check_credential_certificate_references<
288 'p,
289 'v,
290 'd,
291 'q,
292 X: BinaryXml,
293 P: DigestProvider<'q, X>,
294 W: Work,
295>(
296 xml: &X,
297 prepared: &'p Prepared<X>,
298 required: &ParticipantRequirements<'v, 'd, X::Id, X::Document, X::Generation, Xades>,
299 provider: &'q P,
300 work: &W,
301) -> CredentialCertificateResult<'p, 'v, 'd, X, P::Error> {
302 let source = prepared.bound().containers();
303 let location = Location {
304 snapshot: source.snapshot(),
305 node: source.signature(),
306 };
307 let access = Access::new(xml, location.snapshot, work);
308 access
309 .query(location.node, |_, _, _| Ok(()))
310 .map_err(|cause| binary::Error::Access { location, cause })?;
311 if required.scope().location().snapshot != location.snapshot
312 || !matches!(required.scope(), TimestampScope::All(_))
313 || !access
314 .equal_id(required.signature(), location.node)
315 .map_err(|cause| binary::Error::Callback {
316 location,
317 cause: ReferenceDigestError::Digest(digest::Error::Resource(cause)),
318 })?
319 {
320 return Err(binary::Error::Callback {
321 location,
322 cause: ReferenceDigestError::Foreign,
323 });
324 }
325 process(xml, prepared, required, provider, &access, location)
326}
327
328fn process<'p, 'v, 'd, 'q, X: BinaryXml, P: DigestProvider<'q, X>, W: Work>(
329 xml: &X,
330 prepared: &'p Prepared<X>,
331 required: &ParticipantRequirements<'v, 'd, X::Id, X::Document, X::Generation, Xades>,
332 provider: &'q P,
333 access: &Access<'_, X, W>,
334 location: Location<X::Id, X::Document, X::Generation>,
335) -> CredentialCertificateResult<'p, 'v, 'd, X, P::Error> {
336 let work = access.execution();
337 let resource = |cause| binary::Error::Callback {
338 location,
339 cause: ReferenceDigestError::Digest(digest::Error::Resource(cause)),
340 };
341 let mut present = false;
342 for property in prepared.validation_references() {
343 work.charge_work(1).map_err(resource)?;
344 present |= property.role() == unsigned::Role::AttributeCertificateReferences;
345 }
346 let mut state = CredentialScan {
347 memo: Memo::new(provider, work),
348 candidates: None,
349 ids: work.vector(),
350 comparisons: work.vector(),
351 };
352 let mut rows = work.vector();
353 if present {
354 let mut row = |place, node, method, value| {
355 work.charge_work(1).map_err(resource)?;
356 rows.try_reserve(1).map_err(resource)?;
357 let start = state.comparisons.len();
358 let search = match digest::check_with(
359 xml,
360 digest::Reference {
361 snapshot: location.snapshot,
362 method,
363 value,
364 },
365 provider,
366 work,
367 |identity, expected, work| state.compare(identity, expected, place, required, work),
368 )
369 .map_err(|cause| cause.map_callback(ReferenceDigestError::Digest))?
370 {
371 digest::Checked::Value(()) => ReferenceSearch::Compared,
372 digest::Checked::Answer(answer) => ReferenceSearch::Unavailable(answer),
373 };
374 rows.try_push(CredentialCertificateRow {
375 place,
376 node,
377 search,
378 range: start..state.comparisons.len(),
379 })
380 .map_err(resource)
381 };
382 for (entry, reference) in prepared.certificates().entries().iter().enumerate() {
383 row(
384 CredentialReferencePlace::Protected(entry),
385 reference.node(),
386 reference.method(),
387 reference.digest(),
388 )?;
389 }
390 for (property, references) in prepared.validation_references().iter().enumerate() {
391 work.charge_work(1).map_err(resource)?;
392 for (entry, reference) in references.entries().iter().enumerate() {
393 work.charge_work(1).map_err(resource)?;
394 let place = match references.role() {
395 unsigned::Role::CertificateReferences => {
396 CredentialReferencePlace::Ordinary { property, entry }
397 }
398 unsigned::Role::AttributeCertificateReferences => {
399 CredentialReferencePlace::Attribute { property, entry }
400 }
401 _ => continue,
402 };
403 let (method, value) = reference.digest().expect("certificate digest is mandatory");
404 row(place, reference.node(), method, value)?;
405 }
406 }
407 }
408 let mut covered = !present || state.candidates.is_some();
409 let candidates = state.candidates.unwrap_or_else(|| work.vector());
410 for candidate in &candidates {
411 work.charge_work(1).map_err(resource)?;
412 covered &= candidate.ordinary || candidate.protected || candidate.attribute;
413 }
414 access
415 .query(location.node, |_, _, _| Ok(()))
416 .map_err(|cause| binary::Error::Access { location, cause })?;
417 Ok(CredentialCertificateReferences {
418 prepared,
419 candidates: candidates.into_owned(),
420 rows: rows.into_owned(),
421 comparisons: state.comparisons.into_owned(),
422 present,
423 covered,
424 })
425}