Skip to main content

structured_pades/permissions/
placement.rs

1//! Original signature-field and certification placement, independent of math.
2
3use super::Transforms;
4use crate::{
5    document::{Document, Graph},
6    error::{Error, ErrorKind, ReadFailure, Syntax},
7    fields::Fields,
8    model::admit,
9    object::Type,
10    range::RangeSource,
11    signature::Signature,
12};
13use structured_asn1::memory::Work;
14
15/// Original signature's normative field/certification association.
16pub struct Placement {
17    field: Option<usize>,
18    certification: bool,
19    first: bool,
20    permissions: Option<bool>,
21    complete: bool,
22}
23impl Placement {
24    /// Original field ordinal in the complete historical `AcroForm` census.
25    #[must_use]
26    pub fn field(&self) -> Option<usize> {
27        self.field
28    }
29    /// Actual signed Reference includes a `DocMDP` transformation.
30    #[must_use]
31    pub fn is_certification(&self) -> bool {
32        self.certification
33    }
34    /// No other author-signed field existed in this signed snapshot.
35    #[must_use]
36    pub fn is_first_signed_field(&self) -> bool {
37        self.first
38    }
39    /// Whether a present catalog Perms.DocMDP selects this exact certification.
40    /// Absence is allowed: ISO32000-1 distinguishes detection from prevention.
41    #[must_use]
42    pub fn catalog_permission_binding(&self) -> Option<bool> {
43        self.permissions
44    }
45    /// All original field and certification placement requirements hold.
46    #[must_use]
47    pub fn all_requirements_met(&self) -> bool {
48        self.complete
49    }
50}
51
52impl<'s, S: RangeSource> Document<'s, S> {
53    /// ISO32000-1 §12.8.2.2.1 requires `DocMDP` in the first signed field;
54    /// §12.8.4 makes the catalog reference the prevention mechanism. It is
55    /// not a prerequisite for a signed transform's modification detection.
56    /// <https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf>
57    pub fn signature_placement<W: Work>(
58        &self,
59        signature: Signature<'_, 's, S::Document, S::Generation>,
60        graph: &Graph<'s, S::Document, S::Generation>,
61        fields: &Fields<'_, 's, S::Document, S::Generation>,
62        transforms: &Transforms,
63        operation: &W,
64    ) -> Result<Placement, ReadFailure<S::Error>> {
65        let work = self.session.query(operation);
66        let location = signature.location();
67        self.check(&work, location)?;
68        if !core::ptr::eq(fields.graph, graph)
69            || !core::ptr::eq(graph.session, self.session)
70            || graph.stamp != self.stamp
71            || transforms.location() != location
72        {
73            return Err(Error {
74                kind: ErrorKind::Foreign,
75                location,
76            }
77            .into());
78        }
79        let field = fields.signature(location, &work)?;
80        let certification = transforms.permission().is_some();
81        let mut first = true;
82        for (ordinal, entry) in fields.values().iter().enumerate() {
83            admit(&work, location, 1)?;
84            if !entry.is_signature() || Some(ordinal) == field {
85                continue;
86            }
87            let Some(value) = fields.value(ordinal, &work)? else {
88                continue;
89            };
90            if value.direct().kind() == Type::Null {
91                continue;
92            }
93            if value.direct().kind() != Type::Dictionary {
94                return Err(Error {
95                    kind: ErrorKind::Syntax(Syntax::Dictionary),
96                    location: value.direct().location(),
97                }
98                .into());
99            }
100            // EN319142-1 §5.4.3 excludes document timestamp dictionaries
101            // from DocMDP evaluation. Their own format/math remains separate.
102            if let Some(kind) = value.get(b"Type", &work)?
103                && kind.direct().name_equals(b"DocTimeStamp", &work)?
104            {
105                continue;
106            }
107            first = false;
108        }
109        let permissions = if let Some(perms) = graph.root().get(b"Perms", &work)? {
110            perms
111                .get(b"DocMDP", &work)?
112                .map(|value| value.direct().location() == location)
113        } else {
114            None
115        };
116        let complete = field.is_some()
117            && (!certification || first)
118            && match permissions {
119                Some(true) => certification,
120                Some(false) => !certification,
121                None => true,
122            };
123        self.check(&work, location)?;
124        Ok(Placement {
125            field,
126            certification,
127            first,
128            permissions,
129            complete,
130        })
131    }
132}