Skip to main content

structured_xades/
policy.rs

1//! Original signature-policy declarations, distinct from application policy.
2
3use crate::container::{DS, XADES};
4use crate::identifier;
5use crate::properties::{self, Error, PropertyResult};
6use crate::timestamp::XADES_141;
7use crate::unsigned;
8use crate::xml::{Access, AccessError, Name, Snapshot, Xml};
9use structured_asn1::memory::{OwnedVec, Work};
10
11mod syntax;
12pub use syntax::{SyntaxError, SyntaxResult, check_qualifier_syntax};
13
14/// Signed schema choice. Neither variant establishes policy acceptance.
15#[derive(Debug)]
16pub enum Declaration<N> {
17    /// Original empty marker; its meaning must come from external context.
18    Implied(N),
19    /// Original explicit version identifier, hash and transforms/qualifiers.
20    Explicit(Explicit<N>),
21}
22
23/// Source-bound declaration selected from the authenticated signed properties.
24/// Public schema values alone cannot construct this original-source association.
25pub struct Reading<N, D, G> {
26    snapshot: Snapshot<D, G>,
27    signature: N,
28    node: N,
29    declaration: Declaration<N>,
30}
31
32impl<N: Copy, D: Copy, G: Copy> Reading<N, D, G> {
33    /// Exact original document and generation.
34    #[must_use]
35    pub fn snapshot(&self) -> Snapshot<D, G> {
36        self.snapshot
37    }
38    /// Exact selected signature covering this policy property.
39    #[must_use]
40    pub fn signature(&self) -> N {
41        self.signature
42    }
43    /// Original `SignaturePolicyIdentifier`.
44    #[must_use]
45    pub fn node(&self) -> N {
46        self.node
47    }
48    /// Original schema choice, before document/digest or application acceptance.
49    #[must_use]
50    pub fn declaration(&self) -> &Declaration<N> {
51        &self.declaration
52    }
53}
54
55/// Policy reading using this XML owner's original source identity types.
56pub type Original<X> = Reading<<X as Xml>::Id, <X as Xml>::Document, <X as Xml>::Generation>;
57
58/// Original unsigned policy-document source, never an implicitly acquired file.
59#[derive(Debug, Clone, Copy)]
60pub enum StoredDocument<N> {
61    /// Original base64-encoded policy document, before decoding/hash comparison.
62    Embedded(N),
63    /// Original local-store URI; this crate performs no file or network access.
64    Local(N),
65}
66
67/// Source-bound original policy store permitted by an explicit signed hash.
68/// Presence is not a successful document digest or application-policy decision.
69pub struct Store<N, D, G> {
70    snapshot: Snapshot<D, G>,
71    signature: N,
72    node: N,
73    specification: identifier::Reading<N>,
74    document: StoredDocument<N>,
75}
76
77impl<N: Copy, D: Copy, G: Copy> Store<N, D, G> {
78    /// Exact original document identity/generation.
79    #[must_use]
80    pub fn snapshot(&self) -> Snapshot<D, G> {
81        self.snapshot
82    }
83    /// Selected signature whose signed policy hash must authenticate the content.
84    #[must_use]
85    pub fn signature(&self) -> N {
86        self.signature
87    }
88    /// Original unsigned `SignaturePolicyStore`.
89    #[must_use]
90    pub fn node(&self) -> N {
91        self.node
92    }
93    /// Original technical-specification identifier, before lexical validation.
94    #[must_use]
95    pub fn specification(&self) -> &identifier::Reading<N> {
96        &self.specification
97    }
98    /// Original source choice; no acquisition or decoding has occurred.
99    #[must_use]
100    pub fn document(&self) -> StoredDocument<N> {
101        self.document
102    }
103}
104
105/// Policy store using this XML owner's original source identity types.
106pub type OriginalStore<X> = Store<<X as Xml>::Id, <X as Xml>::Document, <X as Xml>::Generation>;
107
108/// Reads §5.2.10 and enforces §6.3(m) before source/decoder/provider execution.
109/// Absent or implied signed policy cannot authorize an unsigned policy store.
110/// <https://www.etsi.org/deliver/etsi_en/319100_319199/31913201/01.03.01_60/en_31913201v010301p.pdf>
111pub fn read_store<X: Xml, W: Work>(
112    access: &Access<'_, X, W>,
113    inventory: &unsigned::Reading<X>,
114    policy: Option<&Original<X>>,
115) -> PropertyResult<Option<OriginalStore<X>>, X> {
116    if access.snapshot() != inventory.snapshot() {
117        return Err(Error::Access(AccessError::Changed {
118            node: inventory.signature(),
119        }));
120    }
121    access
122        .query(inventory.signature(), |_, _, _| Ok(()))
123        .map_err(Error::Access)?;
124    if let Some(policy) = policy
125        && (policy.snapshot != inventory.snapshot()
126            || !access
127                .equal_id(policy.signature, inventory.signature())
128                .map_err(|cause| Error::Resource {
129                    node: policy.node,
130                    cause,
131                })?)
132    {
133        return Err(Error::Access(AccessError::Changed { node: policy.node }));
134    }
135    let mut store = None;
136    for index in 0..inventory.entries().len() {
137        access
138            .query(inventory.signature(), |_, _, work| Ok(work.charge_work(1)))
139            .map_err(Error::Access)?
140            .map_err(|cause| Error::Resource {
141                node: inventory.signature(),
142                cause,
143            })?;
144        let entry = inventory.entries()[index];
145        if entry.role == unsigned::Role::PolicyStore {
146            store = Some(entry.node);
147            break;
148        }
149    }
150    let Some(node) = store else { return Ok(None) };
151    if !matches!(
152        policy.map(|value| &value.declaration),
153        Some(Declaration::Explicit(_))
154    ) {
155        return Err(Error::Forbidden { node });
156    }
157    let mut fields = access.children(node);
158    let spec = fields
159        .next()
160        .transpose()
161        .map_err(Error::Child)?
162        .ok_or(Error::Missing {
163            parent: node,
164            local: "SPDocSpecification",
165        })?;
166    if !named(access, spec, XADES_141, "SPDocSpecification")? {
167        return Err(Error::Unexpected { node: spec });
168    }
169    let specification = read_specification(access, spec)?;
170    let value = fields
171        .next()
172        .transpose()
173        .map_err(Error::Child)?
174        .ok_or(Error::Missing {
175            parent: node,
176            local: "SignaturePolicyDocument or SigPolDocLocalURI",
177        })?;
178    let document = if named(access, value, XADES_141, "SignaturePolicyDocument")? {
179        StoredDocument::Embedded(value)
180    } else if named(access, value, XADES_141, "SigPolDocLocalURI")? {
181        StoredDocument::Local(value)
182    } else {
183        return Err(Error::Unexpected { node: value });
184    };
185    properties::simple_content(access, value)?;
186    if let Some(extra) = fields.next() {
187        return Err(Error::Unexpected {
188            node: extra.map_err(Error::Child)?,
189        });
190    }
191    Ok(Some(Store {
192        snapshot: inventory.snapshot(),
193        signature: inventory.signature(),
194        node,
195        specification,
196        document,
197    }))
198}
199
200/// An original qualifier, including schema-open extension payloads.
201#[derive(Debug)]
202pub struct Qualifier<N> {
203    /// Original `SigPolicyQualifier` wrapper, whose `AnyType` content remains open.
204    pub node: N,
205    /// Every original direct `SPDocSpecification` in this schema-open wrapper.
206    /// Presence is not selection of one technical specification or its meaning.
207    pub specifications: OwnedVec<N>,
208}
209
210/// Explicit original policy declaration before content/hash or trust checks.
211#[derive(Debug)]
212pub struct Explicit<N> {
213    /// Original `SignaturePolicyId`, not a fabricated Reference instruction.
214    pub node: N,
215    /// Original version identifier, before its XML-owned URI/OID lexical check.
216    pub identifier: identifier::Reading<N>,
217    /// Optional original `XMLDSig` transforms; no transform execution is inferred.
218    pub transforms: Option<N>,
219    /// Original selected digest method.
220    pub method: N,
221    /// Original base64 digest value, not yet decoded or compared.
222    pub digest: N,
223    /// Complete original qualifier catalogue in order.
224    pub qualifiers: OwnedVec<Qualifier<N>>,
225}
226
227/// Reads the selected covered policy declaration without implicit acquisition.
228/// EN319132-1 V1.3.1 §5.2.9 requires exactly one schema choice. A specification
229/// transform requires `SPDocSpecification` before any transform/provider work.
230/// This function does not identify an implied application policy, execute a
231/// policy document or turn unsigned `PolicyStore` content into authenticated data.
232/// <https://www.etsi.org/deliver/etsi_en/319100_319199/31913201/01.03.01_60/en_31913201v010301p.pdf>
233pub fn read<X: Xml, W: Work>(
234    access: &Access<'_, X, W>,
235    signed: &properties::Reading<X>,
236) -> PropertyResult<Option<Original<X>>, X> {
237    if access.snapshot() != signed.snapshot() {
238        return Err(Error::Access(AccessError::Changed {
239            node: signed.parent(),
240        }));
241    }
242    access
243        .query(signed.parent(), |_, _, _| Ok(()))
244        .map_err(Error::Access)?;
245    let Some(parent) = signed.policy() else {
246        return Ok(None);
247    };
248    let mut children = access.children(parent);
249    let node = children
250        .next()
251        .transpose()
252        .map_err(Error::Child)?
253        .ok_or(Error::Missing {
254            parent,
255            local: "SignaturePolicyId or SignaturePolicyImplied",
256        })?;
257    if let Some(extra) = children.next() {
258        return Err(Error::Unexpected {
259            node: extra.map_err(Error::Child)?,
260        });
261    }
262    if named(access, node, XADES, "SignaturePolicyImplied")? {
263        if let Some(extra) = access.children(node).next() {
264            return Err(Error::Unexpected {
265                node: extra.map_err(Error::Child)?,
266            });
267        }
268        return Ok(Some(Reading {
269            snapshot: signed.snapshot(),
270            signature: signed.signature(),
271            node: parent,
272            declaration: Declaration::Implied(node),
273        }));
274    }
275    if !named(access, node, XADES, "SignaturePolicyId")? {
276        return Err(Error::Unexpected { node });
277    }
278    explicit(access, node).map(|value| {
279        Some(Reading {
280            snapshot: signed.snapshot(),
281            signature: signed.signature(),
282            node: parent,
283            declaration: Declaration::Explicit(value),
284        })
285    })
286}
287
288fn explicit<X: Xml, W: Work>(
289    access: &Access<'_, X, W>,
290    node: X::Id,
291) -> PropertyResult<Explicit<X::Id>, X> {
292    let mut fields = [None; 4];
293    let mut previous = 0;
294    for child in access.children(node) {
295        let child = child.map_err(Error::Child)?;
296        let mut selected = None;
297        for (rank, (ns, local)) in [
298            (XADES, "SigPolicyId"),
299            (DS, "Transforms"),
300            (XADES, "SigPolicyHash"),
301            (XADES, "SigPolicyQualifiers"),
302        ]
303        .iter()
304        .enumerate()
305        {
306            if named(access, child, ns, local)? {
307                selected = Some(rank);
308                break;
309            }
310        }
311        let rank = selected.ok_or(Error::Unexpected { node: child })?;
312        if fields[rank].is_some() {
313            return Err(Error::Duplicate { node: child });
314        }
315        if rank < previous {
316            return Err(Error::Order { node: child });
317        }
318        fields[rank] = Some(child);
319        previous = rank;
320    }
321    let identifier = identifier::read(
322        access,
323        fields[0].ok_or(Error::Missing {
324            parent: node,
325            local: "SigPolicyId",
326        })?,
327    )?;
328    let (method, digest) = properties::digest_value(
329        access,
330        fields[2].ok_or(Error::Missing {
331            parent: node,
332            local: "SigPolicyHash",
333        })?,
334    )?;
335    let qualifiers = qualifiers(access, node, fields[3])?;
336    if let Some(transforms) = fields[1] {
337        check_transforms(access, transforms, &qualifiers)?;
338    }
339    Ok(Explicit {
340        node,
341        identifier,
342        transforms: fields[1],
343        method,
344        digest,
345        qualifiers,
346    })
347}
348
349fn qualifiers<X: Xml, W: Work>(
350    access: &Access<'_, X, W>,
351    enclosure: X::Id,
352    parent: Option<X::Id>,
353) -> PropertyResult<OwnedVec<Qualifier<X::Id>>, X> {
354    let mut output = access
355        .query(enclosure, |_, _, work| Ok(work.vector()))
356        .map_err(Error::Access)?;
357    if let Some(parent) = parent {
358        for child in access.children(parent) {
359            let node = child.map_err(Error::Child)?;
360            if !named(access, node, XADES, "SigPolicyQualifier")? {
361                return Err(Error::Unexpected { node });
362            }
363            // AnyType may mix text and arbitrary elements. Inspect its direct
364            // children without imposing an element-only content model.
365            let mut specifications = access
366                .query(node, |_, _, work| Ok(work.vector()))
367                .map_err(Error::Access)?;
368            let mut child = access.first_child(node).map_err(Error::Access)?;
369            while let Some(value) = child {
370                if named(access, value, XADES_141, "SPDocSpecification")? {
371                    read_specification(access, value)?;
372                    specifications
373                        .try_push(value)
374                        .map_err(|cause| Error::Resource { node: value, cause })?;
375                } else if named(access, value, XADES, "SPURI")? {
376                    properties::simple_content(access, value)?;
377                } else if named(access, value, XADES, "SPUserNotice")? {
378                    user_notice(access, value)?;
379                }
380                child = access.next_sibling(node, value).map_err(Error::Access)?;
381            }
382            output
383                .try_push(Qualifier {
384                    node,
385                    specifications: specifications.into_owned(),
386                })
387                .map_err(|cause| Error::Resource { node, cause })?;
388        }
389        if output.is_empty() {
390            return Err(Error::Missing {
391                parent,
392                local: "SigPolicyQualifier",
393            });
394        }
395    }
396    Ok(output.into_owned())
397}
398
399fn user_notice<X: Xml, W: Work>(access: &Access<'_, X, W>, parent: X::Id) -> PropertyResult<(), X> {
400    user_notice_with(access, parent, |number| {
401        properties::simple_content(access, number)
402    })
403}
404
405// Both structural inspection and lexical qualification use this same grammar.
406// Integer validity belongs to the XML owner's arbitrary-precision scalar kernel.
407fn user_notice_with<X: Xml, W: Work, E: From<Error<X::Error, X::Id>>>(
408    access: &Access<'_, X, W>,
409    parent: X::Id,
410    mut integer: impl FnMut(X::Id) -> Result<(), E>,
411) -> Result<(), E> {
412    let mut notice = None;
413    let mut text = None;
414    for child in access.children(parent) {
415        let node = child.map_err(Error::Child)?;
416        if named(access, node, XADES, "NoticeRef")? {
417            if notice.is_some() {
418                return Err(Error::Duplicate { node }.into());
419            }
420            if text.is_some() {
421                return Err(Error::Order { node }.into());
422            }
423            notice = Some(node);
424            let mut fields = access.children(node);
425            let organization =
426                fields
427                    .next()
428                    .transpose()
429                    .map_err(Error::Child)?
430                    .ok_or(Error::Missing {
431                        parent: node,
432                        local: "Organization",
433                    })?;
434            if !named(access, organization, XADES, "Organization")? {
435                return Err(Error::Unexpected { node: organization }.into());
436            }
437            properties::simple_content(access, organization)?;
438            let numbers =
439                fields
440                    .next()
441                    .transpose()
442                    .map_err(Error::Child)?
443                    .ok_or(Error::Missing {
444                        parent: node,
445                        local: "NoticeNumbers",
446                    })?;
447            if !named(access, numbers, XADES, "NoticeNumbers")? {
448                return Err(Error::Unexpected { node: numbers }.into());
449            }
450            if let Some(extra) = fields.next() {
451                return Err(Error::Unexpected {
452                    node: extra.map_err(Error::Child)?,
453                }
454                .into());
455            }
456            for number in access.children(numbers) {
457                let number = number.map_err(Error::Child)?;
458                if !named(access, number, XADES, "int")? {
459                    return Err(Error::Unexpected { node: number }.into());
460                }
461                integer(number)?;
462            }
463        } else if named(access, node, XADES, "ExplicitText")? {
464            if text.is_some() {
465                return Err(Error::Duplicate { node }.into());
466            }
467            properties::simple_content(access, node)?;
468            text = Some(node);
469        } else {
470            return Err(Error::Unexpected { node }.into());
471        }
472    }
473    Ok(())
474}
475
476fn check_transforms<X: Xml, W: Work>(
477    access: &Access<'_, X, W>,
478    parent: X::Id,
479    qualifiers: &[Qualifier<X::Id>],
480) -> PropertyResult<(), X> {
481    let mut populated = false;
482    let mut specification = None;
483    for child in access.children(parent) {
484        let node = child.map_err(Error::Child)?;
485        if !named(access, node, DS, "Transform")? {
486            return Err(Error::Unexpected { node });
487        }
488        let special = access.attribute(node, Name { namespace: "", local: "Algorithm" }, |value| {
489            let Some(value) = value else { return Ok(None) };
490            let value = access.trim_xml_whitespace(value)?;
491            access.equal(value, "http://uri.etsi.org/01903/v1.3.2/SignaturePolicy/SPDocDigestAsInSpecification").map(Some)
492        }).map_err(Error::Access)?.map_err(|cause| Error::Resource { node, cause })?;
493        let Some(special) = special else {
494            return Err(Error::Missing {
495                parent: node,
496                local: "Algorithm",
497            });
498        };
499        if special {
500            specification = Some(node);
501        }
502        populated = true;
503    }
504    if !populated {
505        return Err(Error::Missing {
506            parent,
507            local: "Transform",
508        });
509    }
510    if let Some(node) = specification {
511        let mut index = 0;
512        while index < qualifiers.len() {
513            // Admit the next metadata access before selecting its slice item.
514            access
515                .query(node, |_, _, work| Ok(work.charge_work(1)))
516                .map_err(Error::Access)?
517                .map_err(|cause| Error::Resource { node, cause })?;
518            if !qualifiers[index].specifications.is_empty() {
519                return Ok(());
520            }
521            index += 1;
522        }
523        return Err(Error::Missing {
524            parent: node,
525            local: "SPDocSpecification",
526        });
527    }
528    Ok(())
529}
530
531fn named<X: Xml, W: Work>(
532    access: &Access<'_, X, W>,
533    node: X::Id,
534    namespace: &str,
535    local: &str,
536) -> PropertyResult<bool, X> {
537    access
538        .has_name(node, Name { namespace, local })
539        .map_err(Error::Access)
540}
541
542fn read_specification<X: Xml, W: Work>(
543    access: &Access<'_, X, W>,
544    node: X::Id,
545) -> PropertyResult<identifier::Reading<X::Id>, X> {
546    let reading = identifier::read(access, node)?;
547    if reading.qualifier == Some(identifier::Qualifier::Uri) {
548        return Err(Error::Forbidden {
549            node: reading.identifier,
550        });
551    }
552    Ok(reading)
553}