Skip to main content

structured_xades/
role.rs

1//! Original signed role claims, distinct from independently validated credentials.
2
3use crate::container::XADES;
4use crate::properties::{Error, PropertyResult, Reading};
5use crate::xml::{Access, AccessError, Name, Snapshot, Xml};
6use structured_asn1::memory::{OwnedVec, Work};
7
8/// Role-value syntax; signature coverage does not authenticate an external issuer.
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum Kind {
11    /// Signer's opaque self-asserted role.
12    Claimed,
13    /// Original encapsulated X509 attribute certificate.
14    AttributeCertificate,
15    /// Opaque other attribute certificate, needing an explicit format handler.
16    OtherCertificate,
17    /// Original signed assertion, needing its own selected-signature checking.
18    Assertion,
19}
20
21/// Original value in its enclosing role group.
22#[derive(Debug, Clone, Copy)]
23pub struct Entry<N> {
24    /// Original credential/claim payload node.
25    pub node: N,
26    /// Original containing group, not a reconstructed wrapper.
27    pub group: N,
28    /// Actual syntax role, without a trust conclusion.
29    pub kind: Kind,
30}
31
32/// Original role catalogue for later credential/path/status processing.
33#[derive(Debug)]
34pub struct Roles<N, D, G> {
35    snapshot: Snapshot<D, G>,
36    signature: N,
37    node: N,
38    entries: OwnedVec<Entry<N>>,
39}
40
41impl<N: Copy, D: Copy, G: Copy> Roles<N, D, G> {
42    /// Original signed document/generation.
43    #[must_use]
44    pub fn snapshot(&self) -> Snapshot<D, G> {
45        self.snapshot
46    }
47    /// Signature protecting these original role claims.
48    #[must_use]
49    pub fn signature(&self) -> N {
50        self.signature
51    }
52    /// Original `SignerRoleV2` container.
53    #[must_use]
54    pub fn node(&self) -> N {
55        self.node
56    }
57    /// Original values, retaining all independently signed credentials.
58    #[must_use]
59    pub fn entries(&self) -> &[Entry<N>] {
60        &self.entries
61    }
62}
63
64/// Role catalogue using the XML owner's identity types.
65pub type Catalogue<X> = Roles<<X as Xml>::Id, <X as Xml>::Document, <X as Xml>::Generation>;
66
67/// Reads the optional current role property through its original signed source.
68/// EN319132-1 ยง5.2.6 / Annex C permits empty `SignerRoleV2`, but each present list
69/// requires a member. `CertifiedRole` selects exactly one certificate syntax.
70/// The resulting explicit credentials drive conditional validation-material
71/// requirements; their presence must not become an issuer/authentication verdict.
72/// <https://www.etsi.org/deliver/etsi_en/319100_319199/31913201/01.03.01_60/en_31913201v010301p.pdf>
73pub fn read<X: Xml, W: Work>(
74    access: &Access<'_, X, W>,
75    signed: &Reading<X>,
76) -> PropertyResult<Option<Catalogue<X>>, X> {
77    if access.snapshot() != signed.snapshot() {
78        return Err(Error::Access(AccessError::Changed {
79            node: signed.parent(),
80        }));
81    }
82    // Even absence belongs to the original current generation.
83    access
84        .query(signed.parent(), |_, _, _| Ok(()))
85        .map_err(Error::Access)?;
86    let Some(node) = signed.role() else {
87        return Ok(None);
88    };
89    read_node(access, signed.snapshot(), signed.signature(), node).map(Some)
90}
91
92// Counter actor discovery uses the SAME structural property sections and role
93// kernel without requiring that a counter itself claims the root's B level.
94pub(crate) fn locate<X: Xml, W: Work>(
95    access: &Access<'_, X, W>,
96    layout: &crate::container::Layout<X>,
97) -> PropertyResult<Option<X::Id>, X> {
98    let (parent, _) = crate::properties::sections(access, layout)?;
99    let Some(parent) = parent else {
100        return Ok(None);
101    };
102    let mut selected = None;
103    for child in access.children(parent) {
104        let node = child.map_err(Error::Child)?;
105        if access
106            .has_name(
107                node,
108                Name {
109                    namespace: XADES,
110                    local: "SignerRoleV2",
111                },
112            )
113            .map_err(Error::Access)?
114        {
115            if selected.is_some() {
116                return Err(Error::Duplicate { node });
117            }
118            selected = Some(node);
119        }
120    }
121    Ok(selected)
122}
123
124// Caller has established whole-SignedProperties authentication and selected
125// this original node from those properties, never an arbitrary external node.
126pub(crate) fn read_node<X: Xml, W: Work>(
127    access: &Access<'_, X, W>,
128    snapshot: Snapshot<X::Document, X::Generation>,
129    signature: X::Id,
130    node: X::Id,
131) -> PropertyResult<Catalogue<X>, X> {
132    if snapshot != access.snapshot() {
133        return Err(Error::Access(AccessError::Changed { node }));
134    }
135    let mut entries = access
136        .query(node, |_, _, work| Ok(work.vector()))
137        .map_err(Error::Access)?;
138    let mut previous = None;
139    for child in access.children(node) {
140        let group = child.map_err(Error::Child)?;
141        let rank = rank(access, group)?;
142        if previous == Some(rank) {
143            return Err(Error::Duplicate { node: group });
144        }
145        if previous.is_some_and(|value| value > rank) {
146            return Err(Error::Order { node: group });
147        }
148        previous = Some(rank);
149        let mut populated = false;
150        for child in access.children(group) {
151            let child = child.map_err(Error::Child)?;
152            let (payload, kind) = match rank {
153                0 => {
154                    expect(access, child, "ClaimedRole")?;
155                    (child, Kind::Claimed)
156                }
157                1 => {
158                    expect(access, child, "CertifiedRole")?;
159                    credential(access, child)?
160                }
161                _ => {
162                    expect(access, child, "SignedAssertion")?;
163                    (child, Kind::Assertion)
164                }
165            };
166            entries
167                .try_push(Entry {
168                    node: payload,
169                    group,
170                    kind,
171                })
172                .map_err(|cause| Error::Resource { node: child, cause })?;
173            populated = true;
174        }
175        if !populated {
176            return Err(Error::Missing {
177                parent: group,
178                local: "role value",
179            });
180        }
181    }
182    Ok(Roles {
183        snapshot,
184        signature,
185        node,
186        entries: entries.into_owned(),
187    })
188}
189
190fn rank<X: Xml, W: Work>(access: &Access<'_, X, W>, node: X::Id) -> PropertyResult<usize, X> {
191    for (rank, local) in ["ClaimedRoles", "CertifiedRolesV2", "SignedAssertions"]
192        .into_iter()
193        .enumerate()
194    {
195        if access
196            .has_name(
197                node,
198                Name {
199                    namespace: XADES,
200                    local,
201                },
202            )
203            .map_err(Error::Access)?
204        {
205            return Ok(rank);
206        }
207    }
208    Err(Error::Unexpected { node })
209}
210
211fn expect<X: Xml, W: Work>(
212    access: &Access<'_, X, W>,
213    node: X::Id,
214    local: &'static str,
215) -> PropertyResult<(), X> {
216    if !access
217        .has_name(
218            node,
219            Name {
220                namespace: XADES,
221                local,
222            },
223        )
224        .map_err(Error::Access)?
225    {
226        return Err(Error::Unexpected { node });
227    }
228    Ok(())
229}
230
231fn credential<X: Xml, W: Work>(
232    access: &Access<'_, X, W>,
233    node: X::Id,
234) -> PropertyResult<(X::Id, Kind), X> {
235    let mut children = access.children(node);
236    let value = children
237        .next()
238        .transpose()
239        .map_err(Error::Child)?
240        .ok_or(Error::Missing {
241            parent: node,
242            local: "attribute certificate",
243        })?;
244    let kind = if access
245        .has_name(
246            value,
247            Name {
248                namespace: XADES,
249                local: "X509AttributeCertificate",
250            },
251        )
252        .map_err(Error::Access)?
253    {
254        Kind::AttributeCertificate
255    } else {
256        expect(access, value, "OtherAttributeCertificate")?;
257        Kind::OtherCertificate
258    };
259    if let Some(extra) = children.next() {
260        return Err(Error::Unexpected {
261            node: extra.map_err(Error::Child)?,
262        });
263    }
264    Ok((value, kind))
265}