Skip to main content

structured_xades/
material.rs

1//! Original embedded validation material, separate from authentication/completeness.
2
3use crate::binary::{self, Located, LocatedValue, Location, OwnedBinaryXml, OwnedDecoded};
4use crate::container::{DS, XADES};
5use crate::header;
6use crate::xml::{Access, AccessError, Xml};
7use structured_asn1::{Encoding, Reader};
8use structured_pkix::{Certificate, crl::CertificateList, ocsp::Response};
9
10mod catalogue;
11mod checks;
12pub use checks::{CheckError, CheckResult, Checks, check_participants};
13mod conditional;
14pub use conditional::{
15    ConditionalError, ConditionalFailure, ConditionalFinding, ConditionalPlacement,
16    ConditionalResult, conditional_placement,
17};
18mod counters;
19pub mod credentials;
20pub use catalogue::{Catalogue, Entry, read};
21mod pipeline;
22pub use pipeline::{
23    Basic, Error as PipelineError, Opaque, PipelineResult, Placement, Retained, View,
24    with_counter_values, with_values,
25};
26pub(crate) mod exclusivity;
27#[cfg(test)]
28mod fixture;
29mod index;
30pub use index::{Archived, Index, IndexResult, Occurrence, index, index_with_tokens};
31mod timestamp_placement;
32pub use exclusivity::{
33    ExclusiveFinding, ExclusiveFindingKind, ExclusivePlacement, ExclusivityError,
34    ExclusivityResult, exclusivity,
35};
36pub use timestamp_placement::{
37    TimestampPlacement, TimestampPlacementError, TimestampPlacementFailure,
38    TimestampPlacementFinding, TimestampPlacementResult, timestamp_placement,
39};
40
41/// The `XAdES` schema of one original encapsulated material element.
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
43pub enum Kind {
44    /// `EncapsulatedX509Certificate`, containing one DER certificate.
45    Certificate,
46    /// `EncapsulatedCRLValue`, containing one DER `CertificateList`.
47    Crl,
48    /// `EncapsulatedOCSPValue`, containing a whole DER `OCSPResponse`.
49    Ocsp,
50}
51
52impl Kind {
53    fn local(self) -> &'static str {
54        match self {
55            Self::Certificate => "EncapsulatedX509Certificate",
56            Self::Crl => "EncapsulatedCRLValue",
57            Self::Ocsp => "EncapsulatedOCSPValue",
58        }
59    }
60}
61
62/// A retained original source before PKI parsing or any authentication decision.
63#[derive(Debug)]
64pub struct Source<N, D, G> {
65    kind: Kind,
66    original: Located<OwnedDecoded, N, D, G>,
67}
68
69/// Material source using the XML owner's stable source identities.
70pub type Material<X> = Source<<X as Xml>::Id, <X as Xml>::Document, <X as Xml>::Generation>;
71
72/// Refusal before a material source has been retained.
73#[derive(Debug)]
74pub enum RetainError<E, N> {
75    /// Caller selected no original certificate in the selected `KeyInfo` header.
76    HeaderIndex {
77        /// Original selected signature.
78        signature: N,
79        /// Caller-provided certificate ordinal.
80        index: usize,
81    },
82    /// Original XML/source/resource refusal.
83    Access(AccessError<E, N>),
84    /// The selected original node does not have this material's expanded name.
85    Name {
86        /// Original rejected node.
87        node: N,
88        /// Required material schema.
89        kind: Kind,
90    },
91}
92
93/// Retention result using the XML owner's original error and identity types.
94pub type RetainResult<T, X> = Result<T, RetainError<<X as Xml>::Error, <X as Xml>::Id>>;
95
96/// A decoded material value. Parsing does not establish a signature, status,
97/// authority, freshness, path or baseline level. OCSP protocol failures remain
98/// protocol failures rather than disappearing from a material catalogue.
99#[derive(Debug)]
100#[expect(
101    clippy::large_enum_variant,
102    reason = "fixed-size parsed value avoids an extra allocation per material; any retained collection admits its full enum capacity"
103)]
104pub enum Value<'source> {
105    /// Original certificate, retaining its producing session and ranges.
106    Certificate(Certificate<'source>),
107    /// Original CRL, not yet authenticated or checked for scope/freshness.
108    Crl(CertificateList<'source>),
109    /// Whole original OCSP envelope, with its basic response still decoded lazily.
110    Ocsp(Response<'source>),
111}
112
113/// Refusal while parsing exactly one retained material value.
114#[derive(Debug)]
115pub enum Cause {
116    /// Original reader/resource error, in decoded-source coordinates.
117    Encoding(structured_asn1::Error),
118    /// Original PKI schema/resource error, in decoded-source coordinates.
119    Pki(structured_pkix::Error),
120    /// No ASN.1 value in this material source.
121    Empty {
122        /// First decoded-source offset.
123        offset: u64,
124    },
125    /// Bytes remain after the one specified PKI value.
126    Trailing {
127        /// First unconsumed decoded-source offset.
128        offset: u64,
129    },
130}
131
132/// One parsed material value or its unchanged cause, both in the original source.
133pub type ReadResult<'source, N, D, G> =
134    Result<Located<Value<'source>, N, D, G>, Located<Cause, N, D, G>>;
135
136impl<N: Copy, D: Copy, G: Copy> Source<N, D, G> {
137    /// Actual material schema, not a conclusion about its validity or use.
138    #[must_use]
139    pub fn kind(&self) -> Kind {
140        self.kind
141    }
142
143    /// Original XML enclosure; inner offsets are in this distinct decoded source.
144    #[must_use]
145    pub fn location(&self) -> Location<N, D, G> {
146        self.original.location
147    }
148
149    /// Original decoded window, without another copy or exposing mutable storage.
150    #[must_use]
151    pub fn bytes(&self) -> &[u8] {
152        &self.original.value.bytes
153    }
154
155    /// Parses exactly one value using the existing PKIX kernel and both budgets.
156    /// Multiple results can simultaneously borrow a finalized source catalogue.
157    /// EN319132-1 V1.3.1 ยงยง5.4.2/5.4.3 fix DER for these binary values; in
158    /// particular an OCSP value is `OCSPResponse`, not bare `BasicOCSPResponse`.
159    /// <https://www.etsi.org/deliver/etsi_en/319100_319199/31913201/01.03.01_60/en_31913201v010301p.pdf>
160    pub fn read_in<'source>(
161        &'source self,
162        operation: &Reader<'source>,
163    ) -> ReadResult<'source, N, D, G> {
164        let read = || {
165            let mut reader = self
166                .original
167                .value
168                .reader_in(operation, Encoding::Der)
169                .map_err(Cause::Encoding)?;
170            let element = reader
171                .next_element()
172                .map_err(Cause::Encoding)?
173                .ok_or(Cause::Empty {
174                    offset: reader.offset(),
175                })?;
176            let value = match self.kind {
177                Kind::Certificate => Value::Certificate(
178                    Certificate::read_element(&reader, &element).map_err(Cause::Pki)?,
179                ),
180                Kind::Crl => Value::Crl(
181                    CertificateList::read_element(&reader, &element).map_err(Cause::Pki)?,
182                ),
183                Kind::Ocsp => {
184                    Value::Ocsp(Response::read_element(&reader, &element).map_err(Cause::Pki)?)
185                }
186            };
187            if !reader.is_empty() {
188                return Err(Cause::Trailing {
189                    offset: reader.offset(),
190                });
191            }
192            Ok(value)
193        };
194        match read() {
195            Ok(value) => Ok(Located {
196                location: self.location(),
197                value,
198            }),
199            Err(value) => Err(Located {
200                location: self.location(),
201                value,
202            }),
203        }
204    }
205}
206
207/// Retains one exact original material node after validating its expanded name.
208/// Its containing property and selected-signature relationship must separately
209/// be established by the property reader. This performs no acquisition or trust
210/// decision and cannot promote an arbitrary parsed value to authenticated data.
211pub fn retain<X: OwnedBinaryXml>(
212    xml: &X,
213    location: Location<X::Id, X::Document, X::Generation>,
214    kind: Kind,
215    operation: &Reader<'_>,
216) -> RetainResult<Material<X>, X> {
217    retain_expected(xml, location, kind, XADES, kind.local(), operation)
218}
219
220/// Retains an actual selected `KeyInfo/X509Data/X509Certificate` using the same
221/// owned base64/source and PKIX kernels as embedded material. Parsing uses the
222/// existing shared PKIX DER certificate profile; it does not normalize another
223/// certificate encoding or assert that all generic `XMLDSig` encodings are DER.
224/// Neither `KeyInfo` membership nor retention establishes selected-key/path trust.
225pub fn retain_header<X: OwnedBinaryXml>(
226    xml: &X,
227    header: &header::Reading<X>,
228    index: usize,
229    operation: &Reader<'_>,
230) -> RetainResult<Material<X>, X> {
231    let execution = operation.execution();
232    let access = Access::new(xml, header.snapshot(), &execution);
233    access
234        .query(header.signature(), |_, _, _| Ok(()))
235        .map_err(RetainError::Access)?;
236    let Some(&node) = header.certificates().get(index) else {
237        return Err(RetainError::HeaderIndex {
238            signature: header.signature(),
239            index,
240        });
241    };
242    retain_expected(
243        xml,
244        Location {
245            snapshot: header.snapshot(),
246            node,
247        },
248        Kind::Certificate,
249        DS,
250        "X509Certificate",
251        operation,
252    )
253}
254
255fn retain_expected<X: OwnedBinaryXml>(
256    xml: &X,
257    location: Location<X::Id, X::Document, X::Generation>,
258    kind: Kind,
259    namespace: &str,
260    local: &str,
261    operation: &Reader<'_>,
262) -> RetainResult<Material<X>, X> {
263    let execution = operation.execution();
264    let access = Access::new(xml, location.snapshot, &execution);
265    if !access
266        .has_name(location.node, crate::xml::Name { namespace, local })
267        .map_err(RetainError::Access)?
268    {
269        return Err(RetainError::Name {
270            node: location.node,
271            kind,
272        });
273    }
274    let original: LocatedValue<OwnedDecoded, X> =
275        binary::owned_source(xml, location, operation).map_err(RetainError::Access)?;
276    Ok(Source { kind, original })
277}