Skip to main content

structured_xades/
counter.rs

1//! Original enveloped countersignature selection and `XMLDSig` coverage binding.
2
3use crate::binding::{self, Bound, SignatureXml};
4use crate::container::DS;
5use crate::timestamp::{self, InputError};
6use crate::xml::{Access, AccessError, Name, Snapshot, Xml};
7use structured_asn1::memory::Work;
8
9mod walk;
10pub use walk::{WalkError, WalkResult, walk};
11
12/// Counter selection/binding refusal, preserving its original cause and role.
13#[derive(Debug)]
14pub enum Error<E, N> {
15    /// Original container/source/resource cause.
16    Input(InputError<E, N>),
17    /// Actual child-signature or covering-reference refusal.
18    Binding(binding::BindingError<E, N>),
19}
20
21/// Original enveloped counter and parent `SignatureValue` before child checking.
22#[derive(Debug, Clone, Copy)]
23pub struct Counter<N, D, G> {
24    snapshot: Snapshot<D, G>,
25    parent: N,
26    value: N,
27    node: N,
28    child: N,
29}
30
31impl<N: Copy, D: Copy, G: Copy> Counter<N, D, G> {
32    /// Original source identity and generation.
33    #[must_use]
34    pub fn snapshot(&self) -> Snapshot<D, G> {
35        self.snapshot
36    }
37    /// Original countersigned signature.
38    #[must_use]
39    pub fn parent(&self) -> N {
40        self.parent
41    }
42    /// Exact original `SignatureValue` element, including its attributes/content.
43    #[must_use]
44    pub fn value(&self) -> N {
45        self.value
46    }
47    /// Original `CounterSignature` property.
48    #[must_use]
49    pub fn node(&self) -> N {
50        self.node
51    }
52    /// Original enclosed `ds:Signature`, which may be plain `XMLDSig` or `XAdES`.
53    #[must_use]
54    pub fn child(&self) -> N {
55        self.child
56    }
57}
58
59/// Successful mathematical/coverage binding, not a child certificate/path or level.
60#[derive(Debug, Clone, Copy)]
61pub struct Checked<N, D, G> {
62    original: Counter<N, D, G>,
63    reference: N,
64    ordinal: usize,
65}
66
67impl<N: Copy, D: Copy, G: Copy> Checked<N, D, G> {
68    /// Original parent/child/source identities.
69    #[must_use]
70    pub fn original(&self) -> &Counter<N, D, G> {
71        &self.original
72    }
73    /// Actual child `SignedInfo` reference covering the parent `SignatureValue`.
74    #[must_use]
75    pub fn reference(&self) -> N {
76        self.reference
77    }
78    /// Original ordinal in the child's `SignedInfo`, not the parent's references.
79    #[must_use]
80    pub fn ordinal(&self) -> usize {
81        self.ordinal
82    }
83}
84
85/// Counter using this XML owner's stable identity types.
86pub type Original<X> = Counter<<X as Xml>::Id, <X as Xml>::Document, <X as Xml>::Generation>;
87/// Original selection/check cause.
88pub type CounterResult<T, X> = Result<T, Error<<X as Xml>::Error, <X as Xml>::Id>>;
89/// Successfully bound counter using this owner's identity types.
90pub type Binding<X> = Checked<<X as Xml>::Id, <X as Xml>::Document, <X as Xml>::Generation>;
91
92/// Selects exactly one original enclosed Signature under a direct unsigned counter.
93/// No child signature verification, certificate selection or recursive walk occurs.
94pub fn read<X: Xml, W: Work>(
95    access: &Access<'_, X, W>,
96    bound: &Bound<X::Id, X::Document, X::Generation>,
97    node: X::Id,
98) -> CounterResult<Original<X>, X> {
99    let parent = timestamp::unsigned_parent(access, bound, node).map_err(Error::Input)?;
100    let mut found = false;
101    for child in access.children(parent) {
102        let child = child.map_err(|cause| Error::Input(InputError::Child(cause)))?;
103        if access
104            .equal_id(child, node)
105            .map_err(|cause| Error::Input(InputError::Resource { node: child, cause }))?
106        {
107            found = true;
108            break;
109        }
110    }
111    if !found {
112        return Err(Error::Input(InputError::Foreign { node }));
113    }
114    let parent = bound.containers().signature();
115    let value = timestamp::singleton(access, parent, DS, "SignatureValue").map_err(Error::Input)?;
116    walk::member(access, parent, value, node)
117}
118
119/// Consumes one original counter using the actual child's `XMLDSig` receipt.
120///
121/// The owner may expose a separate selected-child handle within the SAME original
122/// verified-request session. This function does not reverify, reset operation
123/// limits or substitute parent receipts for child evidence. EN319132-1 ยง5.2.7.2
124/// requires the complete canonicalized parent `SignatureValue` element; plain
125/// `XMLDSig` countersignatures do not require the baseline's two-reference minimum.
126/// <https://www.etsi.org/deliver/etsi_en/319100_319199/31913201/01.03.01_60/en_31913201v010301p.pdf>
127pub fn bind<X: SignatureXml, W: Work>(
128    child_access: &Access<'_, X, W>,
129    original: Original<X>,
130) -> CounterResult<Binding<X>, X> {
131    if child_access.snapshot() != original.snapshot {
132        return Err(Error::Input(InputError::Access(AccessError::Changed {
133            node: original.child,
134        })));
135    }
136    let (reference, ordinal) = binding::covering_reference(
137        child_access,
138        original.child,
139        binding::Purpose::Counter(original.value),
140    )
141    .map_err(Error::Binding)?;
142    Ok(Checked {
143        original,
144        reference,
145        ordinal,
146    })
147}
148
149fn named<X: Xml, W: Work>(
150    access: &Access<'_, X, W>,
151    node: X::Id,
152    namespace: &str,
153    local: &str,
154) -> CounterResult<bool, X> {
155    access
156        .has_name(node, Name { namespace, local })
157        .map_err(|cause| Error::Input(InputError::Access(cause)))
158}