Skip to main content

structured_asic/
xml.rs

1//! Original signature-file wrappers and complete XML signature occurrence census.
2//!
3//! XML parsing and canonicalization belong to the supplied XML owner. This
4//! inspection neither detaches children nor attests ZIP-to-XML provenance.
5
6use structured_asn1::memory::{MemoryError, OwnedVec, Work};
7use structured_xades::{
8    attributes::{self, AttributeXml},
9    reference::VisitError,
10    xml::{Access, AccessError, Name, Node, Snapshot, Xml},
11};
12
13pub mod producer;
14
15/// ODF1.2 digital-signature wrapper namespace.
16pub const ODF: &str = "urn:oasis:names:tc:opendocument:xmlns:digitalsignature:1.0";
17/// OCF container/signature-wrapper namespace.
18pub const OCF: &str = "urn:oasis:names:tc:opendocument:xmlns:container";
19
20/// Original root form, independent of its signature or profile outcomes.
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum Wrapper {
23    /// One bare ds:Signature, permitted by EN319162-1 §4.4.3.2(3e).
24    Bare,
25    /// The normative asic:XAdESSignatures wrapper.
26    Asic,
27    /// ODF's dsig:document-signatures wrapper, with its own version attribute.
28    Odf,
29    /// OCF's signatures wrapper.
30    Ocf,
31    /// Another wrapper, permitted by §4.4.3.2(3d) only with signature siblings.
32    Other,
33    /// The supplied root is not an XML element.
34    NotElement,
35}
36
37/// Every original ds:Signature, including malformed or misplaced descendants.
38#[derive(Debug, Clone, Copy)]
39pub struct Signature<N> {
40    /// Stable original node; no DOM/view borrow escapes inspection.
41    pub node: N,
42    /// Its actual parent, or None for a bare signature root.
43    pub parent: Option<N>,
44    /// Nearest enclosing signature, not an assertion that this is a legal counter.
45    pub enclosing: Option<N>,
46    /// A bare root or a direct signature child of the original wrapper.
47    pub direct: bool,
48}
49
50/// Readable wrapper problems, retaining every original occurrence.
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum FaultKind {
53    /// The selected original root is not an element.
54    Root,
55    /// No direct signature was present.
56    Missing,
57    /// A wrapper child is not a ds:Signature element.
58    Child,
59    /// Non-whitespace text appears directly in the wrapper.
60    Text,
61    /// A declared wrapper does not permit the original attribute.
62    Attribute,
63    /// Original schema-instance semantics need separate assessment.
64    InstanceUnassessed,
65    /// ODF's required version is absent or not the fixed token 1.2.
66    OdfVersion,
67    /// A signature occurs below an invalid wrapper child, outside another signature.
68    Misplaced,
69}
70
71/// One readable problem at an exact original XML node.
72#[derive(Debug, Clone, Copy)]
73pub struct Fault<N> {
74    /// Original node, never a guessed byte offset.
75    pub node: N,
76    /// Independent wrapper/census problem.
77    pub kind: FaultKind,
78}
79
80/// Original XML, callback and resource causes remain terminal and distinct.
81#[derive(Debug)]
82pub enum Error<E, N> {
83    /// Original XML owner or common generation/current-work refusal.
84    Access(AccessError<E, N>),
85    /// Attribute traversal's first original processing/callback refusal.
86    Attributes(AccessError<VisitError<E, MemoryError>, N>),
87    /// Scoped storage or comparison admission failed before work.
88    Resource {
89        /// Actual attempted original node.
90        node: N,
91        /// Original refusal class.
92        cause: MemoryError,
93    },
94}
95
96/// Raw wrapper and complete descendant census, not a verified profile receipt.
97pub struct Reading<N, D, G> {
98    snapshot: Snapshot<D, G>,
99    root: N,
100    wrapper: Wrapper,
101    signatures: OwnedVec<Signature<N>>,
102    faults: OwnedVec<Fault<N>>,
103}
104impl<N: Copy, D: Copy, G: Copy> Reading<N, D, G> {
105    /// Actual XML source and generation observed throughout the traversal.
106    #[must_use]
107    pub fn snapshot(&self) -> Snapshot<D, G> {
108        self.snapshot
109    }
110    /// Original root; children retain its canonicalization context.
111    #[must_use]
112    pub fn root(&self) -> N {
113        self.root
114    }
115    /// Original namespace-qualified root classification.
116    #[must_use]
117    pub fn wrapper(&self) -> Wrapper {
118        self.wrapper
119    }
120    /// All original descendant signatures in document order, including negatives.
121    #[must_use]
122    pub fn signatures(&self) -> &[Signature<N>] {
123        &self.signatures
124    }
125    /// Complete wrapper/census diagnostics in discovery order.
126    #[must_use]
127    pub fn faults(&self) -> &[Fault<N>] {
128        &self.faults
129    }
130}
131
132struct Frame<N> {
133    parent: N,
134    next: Option<N>,
135    enclosing: Option<N>,
136}
137enum Kind {
138    Element { signature: bool, wrapper: Wrapper },
139    Text,
140    Other,
141}
142
143fn classify<X: Xml, W: Work>(
144    access: &Access<'_, X, W>,
145    node: X::Id,
146) -> Result<Kind, Error<X::Error, X::Id>> {
147    access
148        .inspect(node, |value| match value {
149            Node::Element(name) => {
150                let cost = name
151                    .namespace
152                    .len()
153                    .checked_add(name.local.len())
154                    .and_then(|count| count.checked_mul(4))
155                    .ok_or(MemoryError::WorkExceeded)?;
156                access.execution().charge_work(cost as u64)?;
157                let signature = name.namespace == crate::manifest::DS && name.local == "Signature";
158                let wrapper = if signature {
159                    Wrapper::Bare
160                } else if name.namespace == crate::manifest::NAMESPACE
161                    && name.local == "XAdESSignatures"
162                {
163                    Wrapper::Asic
164                } else if name.namespace == ODF && name.local == "document-signatures" {
165                    Wrapper::Odf
166                } else if name.namespace == OCF && name.local == "signatures" {
167                    Wrapper::Ocf
168                } else {
169                    Wrapper::Other
170                };
171                Ok(Kind::Element { signature, wrapper })
172            }
173            Node::Text(text) => {
174                access.execution().charge_work(text.len() as u64)?;
175                for byte in text.bytes() {
176                    if !matches!(byte, b' ' | b'\r' | b'\n' | b'\t') {
177                        return Ok(Kind::Text);
178                    }
179                }
180                Ok(Kind::Other)
181            }
182            Node::Other => Ok(Kind::Other),
183        })
184        .map_err(Error::Access)?
185        .map_err(|cause| Error::Resource { node, cause })
186}
187
188fn fault<N, W: Work, E>(
189    output: &mut OwnedVec<Fault<N>, W>,
190    node: N,
191    kind: FaultKind,
192) -> Result<(), Error<E, N>>
193where
194    N: Copy,
195{
196    output
197        .try_push(Fault { node, kind })
198        .map_err(|cause| Error::Resource { node, cause })
199}
200
201/// EN319162-1 §4.4.3.2(3) permits all five wrapper forms for general E-XAdES;
202/// the baseline and simple-container wrapper restrictions are separate checks.
203/// The traversal is iterative and retains nested/misplaced occurrences even
204/// after a readable wrapper fault. No successful partial reading survives refusal.
205/// <https://www.etsi.org/deliver/etsi_en/319100_319199/31916201/01.01.01_60/en_31916201v010101p.pdf>
206pub fn inspect<X: AttributeXml, W: Work>(xml: &X, root: X::Id, work: &W) -> InspectionResult<X> {
207    let snapshot = xml.snapshot();
208    let access = Access::new(xml, snapshot, work);
209    let mut signatures = work.vector();
210    let mut faults = work.vector();
211    let root_kind = classify(&access, root)?;
212    let wrapper = match root_kind {
213        Kind::Element { signature, wrapper } => {
214            if signature {
215                signatures
216                    .try_push(Signature {
217                        node: root,
218                        parent: None,
219                        enclosing: None,
220                        direct: true,
221                    })
222                    .map_err(|cause| Error::Resource { node: root, cause })?;
223            }
224            wrapper
225        }
226        Kind::Text | Kind::Other => {
227            fault(&mut faults, root, FaultKind::Root)?;
228            Wrapper::NotElement
229        }
230    };
231    inspect_wrapper(&access, root, wrapper, &mut faults)?;
232    inspect_descendants(&access, root, wrapper, &mut signatures, &mut faults)?;
233    access.inspect(root, |_| ()).map_err(Error::Access)?;
234    Ok(Reading {
235        snapshot,
236        root,
237        wrapper,
238        signatures: signatures.into_owned(),
239        faults: faults.into_owned(),
240    })
241}
242
243/// Complete original wrapper/signature census or unchanged owner refusal.
244pub type InspectionResult<X> = Result<
245    Reading<<X as Xml>::Id, <X as Xml>::Document, <X as Xml>::Generation>,
246    Error<<X as Xml>::Error, <X as Xml>::Id>,
247>;
248
249fn inspect_wrapper<X: AttributeXml, W: Work, O: Work>(
250    access: &Access<'_, X, W>,
251    root: X::Id,
252    wrapper: Wrapper,
253    faults: &mut OwnedVec<Fault<X::Id>, O>,
254) -> Result<(), Error<X::Error, X::Id>> {
255    let work = access.execution();
256    if matches!(wrapper, Wrapper::Asic | Wrapper::Odf | Wrapper::Ocf) {
257        attributes::visit(access, root, |name| {
258            work.charge_work(name.namespace.len() as u64 + name.local.len() as u64 + 1)?;
259            if wrapper == Wrapper::Odf && name.namespace == ODF && name.local == "version" {
260                return Ok(());
261            }
262            let kind = if name.namespace == "http://www.w3.org/2001/XMLSchema-instance" {
263                FaultKind::InstanceUnassessed
264            } else {
265                FaultKind::Attribute
266            };
267            faults.try_push(Fault { node: root, kind })
268        })
269        .map_err(Error::Attributes)?;
270    }
271    if wrapper == Wrapper::Odf {
272        // ODF1.2 part3 §5.4.1 and its normative dsig RNG: required fixed
273        // token 1.2. The RNG value uses the default token whitespace semantics.
274        // https://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-dsig-schema.rng
275        let valid = access
276            .attribute(
277                root,
278                Name {
279                    namespace: ODF,
280                    local: "version",
281                },
282                |value| {
283                    let Some(value) = value else {
284                        return Ok(false);
285                    };
286                    work.charge_work(value.len() as u64)?;
287                    Ok(value.trim_matches([' ', '\t', '\r', '\n']) == "1.2")
288                },
289            )
290            .map_err(Error::Access)?
291            .map_err(|cause| Error::Resource { node: root, cause })?;
292        if !valid {
293            fault(faults, root, FaultKind::OdfVersion)?;
294        }
295    }
296    Ok(())
297}
298
299fn inspect_descendants<X: AttributeXml, W: Work, O: Work>(
300    access: &Access<'_, X, W>,
301    root: X::Id,
302    wrapper: Wrapper,
303    signatures: &mut OwnedVec<Signature<X::Id>, O>,
304    faults: &mut OwnedVec<Fault<X::Id>, O>,
305) -> Result<(), Error<X::Error, X::Id>> {
306    let work = access.execution();
307    let mut stack = work.vector();
308    if wrapper != Wrapper::NotElement {
309        let next = access.first_child(root).map_err(Error::Access)?;
310        stack
311            .try_push(Frame {
312                parent: root,
313                next,
314                enclosing: if wrapper == Wrapper::Bare {
315                    Some(root)
316                } else {
317                    None
318                },
319            })
320            .map_err(|cause| Error::Resource { node: root, cause })?;
321    }
322    let mut direct = usize::from(wrapper == Wrapper::Bare);
323    while let Some(frame) = stack.last_mut() {
324        let Some(node) = frame.next else {
325            stack.pop();
326            continue;
327        };
328        let parent = frame.parent;
329        let outer = frame.enclosing;
330        frame.next = access.next_sibling(parent, node).map_err(Error::Access)?;
331        let kind = classify(access, node)?;
332        let is_direct = parent == root && wrapper != Wrapper::Bare;
333        let (is_element, enclosing) = match kind {
334            Kind::Element { signature, .. } => {
335                if signature {
336                    signatures
337                        .try_push(Signature {
338                            node,
339                            parent: Some(parent),
340                            enclosing: outer,
341                            direct: is_direct,
342                        })
343                        .map_err(|cause| Error::Resource { node, cause })?;
344                    if is_direct {
345                        direct += 1;
346                    } else if outer.is_none() {
347                        fault(faults, node, FaultKind::Misplaced)?;
348                    }
349                } else if is_direct {
350                    fault(faults, node, FaultKind::Child)?;
351                }
352                (true, if signature { Some(node) } else { outer })
353            }
354            Kind::Text => {
355                if is_direct {
356                    fault(faults, node, FaultKind::Text)?;
357                }
358                (false, outer)
359            }
360            Kind::Other => (false, outer),
361        };
362        if is_element {
363            let next = access.first_child(node).map_err(Error::Access)?;
364            stack
365                .try_push(Frame {
366                    parent: node,
367                    next,
368                    enclosing,
369                })
370                .map_err(|cause| Error::Resource { node, cause })?;
371        }
372    }
373    if direct == 0 {
374        fault(faults, root, FaultKind::Missing)?;
375    }
376    Ok(())
377}