1use crate::binding::Bound;
4use crate::container::XADES;
5use crate::properties::{Error, PropertyResult};
6use crate::timestamp::XADES_141;
7use crate::xml::{Access, AccessError, Name, Snapshot, Xml};
8use structured_asn1::memory::{OwnedVec, Work};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum Requirements {
13 Basic,
15 Timestamped,
17 LongTerm,
19 Archival,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum Role {
26 Counter,
28 SignatureTimestamp,
30 Certificates,
32 Revocation,
34 AttributeCertificates,
36 AttributeRevocation,
38 AnyValidation,
40 TimestampValidation,
42 Archive,
44 RenewedDigests,
46 PolicyStore,
48 CertificateReferences,
50 RevocationReferences,
52 AttributeCertificateReferences,
54 AttributeRevocationReferences,
56 SignatureAndReferencesTimestamp,
58 ReferencesTimestamp,
60 Extension,
62}
63
64#[derive(Debug, Clone, Copy)]
66pub struct Entry<N> {
67 pub node: N,
69 pub role: Role,
71}
72
73#[derive(Debug)]
75pub struct Catalogue<N, D, G> {
76 snapshot: Snapshot<D, G>,
77 signature: N,
78 parent: Option<N>,
79 data: Option<N>,
80 entries: OwnedVec<Entry<N>>,
81}
82
83impl<N: Copy, D: Copy, G: Copy> Catalogue<N, D, G> {
84 #[must_use]
86 pub fn snapshot(&self) -> Snapshot<D, G> {
87 self.snapshot
88 }
89 #[must_use]
91 pub fn signature(&self) -> N {
92 self.signature
93 }
94 #[must_use]
96 pub fn parent(&self) -> Option<N> {
97 self.parent
98 }
99 #[must_use]
102 pub fn data_objects(&self) -> Option<N> {
103 self.data
104 }
105 #[must_use]
107 pub fn entries(&self) -> &[Entry<N>] {
108 &self.entries
109 }
110}
111
112pub type Reading<X> = Catalogue<<X as Xml>::Id, <X as Xml>::Document, <X as Xml>::Generation>;
114
115pub fn check_credential_material<X: Xml, W: Work>(
120 access: &Access<'_, X, W>,
121 inventory: &Reading<X>,
122 roles: Option<&crate::role::Catalogue<X>>,
123) -> PropertyResult<(), X> {
124 if access.snapshot() != inventory.snapshot {
125 return Err(Error::Access(AccessError::Changed {
126 node: inventory.signature,
127 }));
128 }
129 let mut credential = false;
130 if let Some(roles) = roles {
131 if roles.snapshot() != inventory.snapshot
132 || !access
133 .equal_id(roles.signature(), inventory.signature)
134 .map_err(|cause| {
135 Error::Access(AccessError::Resource {
136 node: roles.node(),
137 cause,
138 })
139 })?
140 {
141 return Err(Error::Access(AccessError::Changed { node: roles.node() }));
142 }
143 for index in 0..roles.entries().len() {
144 access
145 .query(roles.node(), |_, _, work| Ok(work.charge_work(1)))
146 .map_err(Error::Access)?
147 .map_err(|cause| Error::Resource {
148 node: roles.node(),
149 cause,
150 })?;
151 if roles.entries()[index].kind != crate::role::Kind::Claimed {
152 credential = true;
153 break;
154 }
155 }
156 }
157 for index in 0..inventory.entries.len() {
158 access
159 .query(inventory.signature, |_, _, work| Ok(work.charge_work(1)))
160 .map_err(Error::Access)?
161 .map_err(|cause| Error::Resource {
162 node: inventory.signature,
163 cause,
164 })?;
165 let entry = inventory.entries[index];
166 if !credential
167 && matches!(
168 entry.role,
169 Role::AttributeCertificates
170 | Role::AttributeRevocation
171 | Role::AttributeCertificateReferences
172 | Role::AttributeRevocationReferences
173 )
174 {
175 return Err(Error::Forbidden { node: entry.node });
176 }
177 }
178 Ok(())
179}
180
181pub fn read<X: Xml, W: Work>(
187 access: &Access<'_, X, W>,
188 bound: &Bound<X::Id, X::Document, X::Generation>,
189 requirements: Requirements,
190) -> PropertyResult<Reading<X>, X> {
191 let layout = bound.containers();
192 let signature = layout.signature();
193 if access.snapshot() != layout.snapshot() {
194 return Err(Error::Access(AccessError::Changed { node: signature }));
195 }
196 let [parent, data] = containers(access, layout.unsigned())?;
197 let mut entries = access
198 .query(signature, |_, _, work| Ok(work.vector()))
199 .map_err(Error::Access)?;
200 let mut singletons = [false; 9];
201 let mut signature_timestamp = false;
202 let mut archive_timestamp = false;
203 if let Some(parent) = parent {
204 for child in access.children(parent) {
205 let node = child.map_err(Error::Child)?;
206 let role = classify(access, node)?;
207 let singleton = match role {
210 Role::Certificates => Some(0),
211 Role::Revocation => Some(1),
212 Role::AttributeCertificates => Some(2),
213 Role::AttributeRevocation => Some(3),
214 Role::PolicyStore => Some(4),
215 Role::CertificateReferences => Some(5),
216 Role::RevocationReferences => Some(6),
217 Role::AttributeCertificateReferences => Some(7),
218 Role::AttributeRevocationReferences => Some(8),
219 _ => None,
220 };
221 if let Some(index) = singleton {
222 if singletons[index] {
223 return Err(Error::Duplicate { node });
224 }
225 singletons[index] = true;
226 }
227 if matches!(
228 requirements,
229 Requirements::LongTerm | Requirements::Archival
230 ) && matches!(
231 role,
232 Role::CertificateReferences
233 | Role::RevocationReferences
234 | Role::AttributeCertificateReferences
235 | Role::AttributeRevocationReferences
236 | Role::SignatureAndReferencesTimestamp
237 | Role::ReferencesTimestamp
238 ) {
239 return Err(Error::Forbidden { node });
240 }
241 signature_timestamp |= role == Role::SignatureTimestamp;
242 archive_timestamp |= role == Role::Archive;
243 entries
244 .try_push(Entry { node, role })
245 .map_err(|cause| Error::Resource { node, cause })?;
246 }
247 if entries.is_empty() {
248 return Err(Error::Missing {
249 parent,
250 local: "unsigned property",
251 });
252 }
253 }
254 if requirements != Requirements::Basic && !signature_timestamp {
255 return Err(Error::Missing {
256 parent: signature,
257 local: "SignatureTimeStamp",
258 });
259 }
260 if requirements == Requirements::Archival && !archive_timestamp {
261 return Err(Error::Missing {
262 parent: signature,
263 local: "ArchiveTimeStamp",
264 });
265 }
266 Ok(Catalogue {
267 snapshot: layout.snapshot(),
268 signature,
269 parent,
270 data,
271 entries: entries.into_owned(),
272 })
273}
274
275pub(crate) fn containers<X: Xml, W: Work>(
276 access: &Access<'_, X, W>,
277 unsigned: Option<X::Id>,
278) -> PropertyResult<[Option<X::Id>; 2], X> {
279 let mut fields = [None; 2];
280 let Some(unsigned) = unsigned else {
281 return Ok(fields);
282 };
283 for child in access.children(unsigned) {
284 let node = child.map_err(Error::Child)?;
285 if access
286 .has_name(
287 node,
288 Name {
289 namespace: XADES,
290 local: "UnsignedSignatureProperties",
291 },
292 )
293 .map_err(Error::Access)?
294 {
295 if fields[0].is_some() {
296 return Err(Error::Duplicate { node });
297 }
298 if fields[1].is_some() {
299 return Err(Error::Order { node });
300 }
301 fields[0] = Some(node);
302 } else if access
303 .has_name(
304 node,
305 Name {
306 namespace: XADES,
307 local: "UnsignedDataObjectProperties",
308 },
309 )
310 .map_err(Error::Access)?
311 {
312 if fields[1].is_some() {
313 return Err(Error::Duplicate { node });
314 }
315 fields[1] = Some(node);
316 let mut populated = false;
317 for child in access.children(node) {
318 let child = child.map_err(Error::Child)?;
319 if !access
320 .has_name(
321 child,
322 Name {
323 namespace: XADES,
324 local: "UnsignedDataObjectProperty",
325 },
326 )
327 .map_err(Error::Access)?
328 {
329 return Err(Error::Unexpected { node: child });
330 }
331 populated = true;
332 }
333 if !populated {
334 return Err(Error::Missing {
335 parent: node,
336 local: "UnsignedDataObjectProperty",
337 });
338 }
339 } else {
340 return Err(Error::Unexpected { node });
341 }
342 }
343 Ok(fields)
344}
345
346fn classify<X: Xml, W: Work>(access: &Access<'_, X, W>, node: X::Id) -> PropertyResult<Role, X> {
347 for local in [
348 "CompleteCertificateRefs",
349 "AttributeCertificateRefs",
350 "SigAndRefsTimeStamp",
351 "RefsOnlyTimeStamp",
352 "ArchiveTimeStamp",
353 ] {
354 if access
355 .has_name(
356 node,
357 Name {
358 namespace: XADES,
359 local,
360 },
361 )
362 .map_err(Error::Access)?
363 {
364 return Err(Error::Forbidden { node });
365 }
366 }
367 for (namespace, local, role) in [
368 (XADES, "CounterSignature", Role::Counter),
369 (XADES, "SignatureTimeStamp", Role::SignatureTimestamp),
370 (XADES, "CertificateValues", Role::Certificates),
371 (XADES, "RevocationValues", Role::Revocation),
372 (
373 XADES,
374 "AttrAuthoritiesCertValues",
375 Role::AttributeCertificates,
376 ),
377 (
378 XADES,
379 "AttributeRevocationValues",
380 Role::AttributeRevocation,
381 ),
382 (XADES, "CompleteRevocationRefs", Role::RevocationReferences),
383 (
384 XADES,
385 "AttributeRevocationRefs",
386 Role::AttributeRevocationReferences,
387 ),
388 (
389 XADES_141,
390 "CompleteCertificateRefsV2",
391 Role::CertificateReferences,
392 ),
393 (
394 XADES_141,
395 "AttributeCertificateRefsV2",
396 Role::AttributeCertificateReferences,
397 ),
398 (
399 XADES_141,
400 "SigAndRefsTimeStampV2",
401 Role::SignatureAndReferencesTimestamp,
402 ),
403 (XADES_141, "RefsOnlyTimeStampV2", Role::ReferencesTimestamp),
404 (XADES_141, "AnyValidationData", Role::AnyValidation),
405 (
406 XADES_141,
407 "TimeStampValidationData",
408 Role::TimestampValidation,
409 ),
410 (XADES_141, "ArchiveTimeStamp", Role::Archive),
411 (XADES_141, "RenewedDigestsV2", Role::RenewedDigests),
412 (XADES_141, "SignaturePolicyStore", Role::PolicyStore),
413 ] {
414 if access
415 .has_name(node, Name { namespace, local })
416 .map_err(Error::Access)?
417 {
418 return Ok(role);
419 }
420 }
421 let foreign = access
422 .inspect(node, |value| {
423 let crate::xml::Node::Element(name) = value else {
424 return Ok(false);
425 };
426 if name.namespace.is_empty() {
427 return Ok(false);
428 }
429 Ok(!access.equal(name.namespace, XADES)?)
430 })
431 .map_err(Error::Access)?
432 .map_err(|cause| Error::Resource { node, cause })?;
433 if !foreign {
434 return Err(Error::Unexpected { node });
435 }
436 Ok(Role::Extension)
437}