Skip to main content

structured_xades/
validation.rs

1//! Validation-data containers and non-authoritative timestamp association hints.
2
3use crate::binding::Bound;
4use crate::container::XADES;
5use crate::material::{self, Catalogue};
6use crate::properties;
7use crate::timestamp::{self, InputError, XADES_141};
8use crate::xml::{Access, Name, Snapshot, Xml};
9use structured_asn1::memory::Work;
10
11mod refs;
12pub(crate) use refs::read as read_references;
13mod ref_catalogue;
14mod ref_digest;
15pub use ref_catalogue::{
16    MaterialDigest, ReferenceCatalogueResult, ReferenceDigests, ReferenceRow, ReferenceSearch,
17    compare_reference_catalogues, compare_reference_catalogues_with_tokens,
18};
19mod ref_issuer;
20mod ref_time;
21pub use ref_time::{ReferenceTime, ReferenceTimeError, matches_reference_time};
22mod ref_envelope;
23mod ref_key;
24mod ref_name;
25mod ref_ocsp;
26pub use ref_envelope::{
27    OcspEnvelopeError, OcspEnvelopeReference, OcspEnvelopeResult, OcspReferenceInput,
28    check_ocsp_envelope_reference,
29};
30pub use ref_issuer::with_reference_issuer_serial;
31pub use ref_key::{
32    ReferenceKeyResult, ResponderKeyError, matches_reference_key, with_responder_key,
33};
34pub use ref_name::{ReferenceNameError, matches_reference_name};
35pub use ref_ocsp::{
36    OcspReferenceError, OcspReferenceIdentity, OcspReferenceResult, check_ocsp_reference,
37};
38mod ref_credential_certificates;
39mod ref_crl;
40mod ref_crl_value;
41mod ref_path;
42mod ref_presence;
43mod ref_roles;
44mod ref_status_required;
45pub use ref_credential_certificates::{
46    CredentialCertificate, CredentialCertificateReferences, CredentialCertificateResult,
47    CredentialCertificateRow, CredentialReferencePlace, check_credential_certificate_references,
48};
49mod ref_status_roles;
50pub use ref_path::{
51    RootPathReferenceResult, RootPathReferences, RootReferenceRow,
52    check_root_certificate_references,
53};
54pub use ref_presence::{
55    ReferencePresence, ReferencePresenceError, ReferencePresenceFailure, ReferencePresenceResult,
56    ReferencePresenceRow, reference_presence,
57};
58pub use ref_roles::{
59    CertificateRoleComparison, CertificateRoleError, CertificateRoleResult, CertificateRoleRow,
60    CertificateRoles, check_certificate_reference_roles,
61};
62pub use ref_status_required::{CredentialStatusSource, PathStatusSource};
63pub use ref_status_roles::{
64    RevocationRoleResult, RevocationRoles, StatusRoleAgreement, StatusRoleComparison,
65    StatusRoleRow, StatusSourceComparison, check_revocation_reference_roles,
66};
67mod ref_fields;
68pub use ref_crl::{
69    CrlReferenceError, CrlReferenceIdentity, CrlReferenceResult, check_crl_reference,
70};
71pub use ref_crl_value::{
72    CrlValueError, CrlValueReference, CrlValueResult, check_crl_value_reference,
73};
74pub use ref_digest::{
75    ReferenceDigest, ReferenceDigestError, ReferenceTarget, compare_reference_digest,
76    compare_reference_target,
77};
78pub use ref_fields::{ReferenceFieldError, ReferenceFieldResult, check_reference_fields};
79pub use refs::{
80    ReferenceEntry, ReferenceField, ReferenceIdentifier, ReferenceKind, ReferenceProperty,
81    ResponderIdentifier,
82};
83
84/// Validation-data container's actual schema role.
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86pub enum Role {
87    /// Material usable for signatures anywhere within the selected `XAdES` signature.
88    Any,
89    /// Material intended to help validate electronic timestamps.
90    Timestamp,
91}
92
93/// A hint is never proof that a certificate/status value belongs to a timestamp.
94#[derive(Debug, Clone, Copy, PartialEq, Eq)]
95pub enum Hint<N> {
96    /// Immediate preceding specified timestamp container takes precedence over
97    /// the URI attribute, including a URI pointing at another timestamp.
98    Adjacent(N),
99    /// No implicit preceding association. Any original URI still needs XML-owned
100    /// resolution; a wrong hint does not make otherwise usable material invalid.
101    Unresolved {
102        /// Whether the original URI attribute exists, distinct from its value.
103        uri_present: bool,
104    },
105}
106
107/// Original structural/processing cause, separate from material authenticity.
108#[derive(Debug)]
109pub enum Error<E, N> {
110    /// Original selected-container/source/resource cause.
111    Input(InputError<E, N>),
112    /// Original inner material catalogue cause; successful partial catalogues
113    /// are not returned when a later child refuses.
114    Property(properties::Error<E, N>),
115    /// `AnyValidationData` forbids URI even though its base schema permits it.
116    ForbiddenUri {
117        /// Original `AnyValidationData` node.
118        node: N,
119    },
120    /// `AnyValidationData` must contain certificate or revocation values, or both.
121    Empty {
122        /// Original empty material container.
123        node: N,
124    },
125}
126
127/// Original complete container, before PKI, path/status or level validation.
128#[derive(Debug)]
129pub struct Data<N, D, G> {
130    snapshot: Snapshot<D, G>,
131    signature: N,
132    node: N,
133    role: Role,
134    hint: Option<Hint<N>>,
135    certificates: Option<Catalogue<N, D, G>>,
136    revocations: Option<Catalogue<N, D, G>>,
137}
138
139impl<N: Copy, D: Copy, G: Copy> Data<N, D, G> {
140    /// Exact XML source generation carrying this material.
141    #[must_use]
142    pub fn snapshot(&self) -> Snapshot<D, G> {
143        self.snapshot
144    }
145    /// Original selected signature, not an application trust decision.
146    #[must_use]
147    pub fn signature(&self) -> N {
148        self.signature
149    }
150    /// Original validation-data property.
151    #[must_use]
152    pub fn node(&self) -> N {
153        self.node
154    }
155    /// Actual schema role.
156    #[must_use]
157    pub fn role(&self) -> Role {
158        self.role
159    }
160    /// Original timestamp association hint; never an authentication receipt.
161    #[must_use]
162    pub fn hint(&self) -> Option<Hint<N>> {
163        self.hint
164    }
165    /// Complete original certificate-value catalogue, if present.
166    #[must_use]
167    pub fn certificates(&self) -> Option<&Catalogue<N, D, G>> {
168        self.certificates.as_ref()
169    }
170    /// Complete original revocation-value catalogue, if present.
171    #[must_use]
172    pub fn revocations(&self) -> Option<&Catalogue<N, D, G>> {
173        self.revocations.as_ref()
174    }
175}
176
177/// Reading in this XML owner's original identity types.
178pub type Reading<X> = Data<<X as Xml>::Id, <X as Xml>::Document, <X as Xml>::Generation>;
179/// Original XML/property refusal types.
180pub type ValidationResult<T, X> = Result<T, Error<<X as Xml>::Error, <X as Xml>::Id>>;
181
182/// Reads one direct unsigned validation-data container of the selected signature.
183///
184/// EN319132-1 V1.3.1 §§5.4.6/5.5.1 separate `AnyValidationData`'s prohibited URI
185/// from `TimeStampValidationData`'s optional non-authoritative hint. For validation,
186/// §5.5.1.2 gives immediate preceding timestamp association priority over the URI;
187/// it does not require rejecting usable material merely for a wrong URI hint.
188/// <https://www.etsi.org/deliver/etsi_en/319100_319199/31913201/01.03.01_60/en_31913201v010301p.pdf>
189pub fn read<X: Xml, W: Work>(
190    access: &Access<'_, X, W>,
191    bound: &Bound<X::Id, X::Document, X::Generation>,
192    node: X::Id,
193) -> ValidationResult<Reading<X>, X> {
194    let parent = timestamp::unsigned_parent(access, bound, node).map_err(Error::Input)?;
195    let mut previous = None;
196    let mut found = false;
197    for child in access.children(parent) {
198        let child = child.map_err(|cause| Error::Input(InputError::Child(cause)))?;
199        if access
200            .equal_id(child, node)
201            .map_err(|cause| Error::Input(InputError::Resource { node: child, cause }))?
202        {
203            found = true;
204            break;
205        }
206        previous = Some(child);
207    }
208    if !found {
209        return Err(Error::Input(InputError::Foreign { node }));
210    }
211    read_at(access, bound.containers().signature(), node, previous)
212}
213
214// The caller already holds this exact node from the same original direct
215// unsigned cursor. Counters reuse the field kernel without fabricating a root
216// B-level binding or reinterpreting the optional URI as authentication.
217pub(crate) fn read_at<X: Xml, W: Work>(
218    access: &Access<'_, X, W>,
219    signature: X::Id,
220    node: X::Id,
221    previous: Option<X::Id>,
222) -> ValidationResult<Reading<X>, X> {
223    let role = if named(access, node, XADES_141, "AnyValidationData")? {
224        Role::Any
225    } else if named(access, node, XADES_141, "TimeStampValidationData")? {
226        Role::Timestamp
227    } else {
228        return Err(Error::Input(InputError::Unexpected { node }));
229    };
230    let uri_present = access
231        .attribute(
232            node,
233            Name {
234                namespace: "",
235                local: "URI",
236            },
237            |value| value.is_some(),
238        )
239        .map_err(|cause| Error::Input(InputError::Access(cause)))?;
240    if role == Role::Any && uri_present {
241        return Err(Error::ForbiddenUri { node });
242    }
243    let mut hint = None;
244    if role == Role::Timestamp {
245        hint = Some(Hint::Unresolved { uri_present });
246        if let Some(prior) = previous
247            && (named(access, prior, XADES, "SignatureTimeStamp")?
248                || named(access, prior, XADES_141, "ArchiveTimeStamp")?
249                || named(access, prior, XADES_141, "SigAndRefsTimeStampV2")?
250                || named(access, prior, XADES_141, "RefsOnlyTimeStampV2")?)
251        {
252            hint = Some(Hint::Adjacent(prior));
253        }
254    }
255    let mut certificates = None;
256    let mut revocations = None;
257    for child in access.children(node) {
258        let child = child.map_err(|cause| Error::Input(InputError::Child(cause)))?;
259        if named(access, child, XADES, "CertificateValues")? {
260            if certificates.is_some() {
261                return Err(Error::Property(properties::Error::Duplicate {
262                    node: child,
263                }));
264            }
265            if revocations.is_some() {
266                return Err(Error::Property(properties::Error::Order { node: child }));
267            }
268            certificates = Some(material::read(access, child).map_err(Error::Property)?);
269        } else if named(access, child, XADES, "RevocationValues")? {
270            if revocations.is_some() {
271                return Err(Error::Property(properties::Error::Duplicate {
272                    node: child,
273                }));
274            }
275            revocations = Some(material::read(access, child).map_err(Error::Property)?);
276        } else {
277            return Err(Error::Input(InputError::Unexpected { node: child }));
278        }
279    }
280    if role == Role::Any
281        && certificates.as_ref().is_none_or(|c| c.entries().is_empty())
282        && revocations.as_ref().is_none_or(|c| c.entries().is_empty())
283    {
284        return Err(Error::Empty { node });
285    }
286    Ok(Data {
287        snapshot: access.snapshot(),
288        signature,
289        node,
290        role,
291        hint,
292        certificates,
293        revocations,
294    })
295}
296
297fn named<X: Xml, W: Work>(
298    access: &Access<'_, X, W>,
299    node: X::Id,
300    namespace: &str,
301    local: &str,
302) -> ValidationResult<bool, X> {
303    access
304        .has_name(node, Name { namespace, local })
305        .map_err(|cause| Error::Input(InputError::Access(cause)))
306}