Skip to main content

structured_xades/produce/
basic.rs

1//! Baseline signed-property construction and `XMLDSig` reference instructions.
2
3use super::{DS, Datatype, Emit, Error, Event, Value, Work, Writer, XADES};
4use structured_pkix::Certificate;
5
6/// One original data-object reference and its required MIME description.
7/// `CounterSignature` and `SignedProperties` references are represented separately.
8#[derive(Debug, Clone, Copy)]
9pub struct DataObject<'a> {
10    /// Bare-name reference URI identifying the actual ds:Reference, not its target.
11    pub reference: &'a str,
12    /// RFC2045 media type of the signed data object.
13    pub mime_type: &'a str,
14}
15
16/// The reference role the XML signer must install in `SignedInfo`.
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum ReferenceKind {
19    /// `SignedProperties`, with the `XAdES` reference Type and full coverage.
20    SignedProperties,
21    /// Parent `SignatureValue`, with the `CountersignedSignature` Type.
22    CounterSignature,
23}
24impl ReferenceKind {
25    /// Required Type URI, independent of the XML prefix or ID spelling.
26    #[must_use]
27    pub const fn type_uri(self) -> &'static str {
28        match self {
29            Self::SignedProperties => crate::binding::SIGNED_PROPERTIES,
30            Self::CounterSignature => "http://uri.etsi.org/01903#CountersignedSignature",
31        }
32    }
33}
34
35/// Explicit reference instructions, not evidence that a signature exists.
36#[derive(Debug, Clone, Copy)]
37pub struct BasicReferences<'a> {
38    /// ID of the new `SignedProperties` to protect, without a leading '#'.
39    pub signed_properties: &'a str,
40    /// URI of the countersigned `SignatureValue`, absent for an ordinary signature.
41    pub counter_signature: Option<&'a str>,
42}
43
44/// Minimal interoperable B-B property construction. Optional policy, role,
45/// commitment and object timestamps remain composable in the XML draft, and
46/// are checked by the existing complete preparation/field/level pipeline.
47///
48/// The signing certificate is actual parsed DER. `certificate_digest` is the
49/// selected provider's digest of its COMPLETE bytes, not SPKI or signed bytes.
50/// This input is not a certificate/digest binding receipt; existing certificate
51/// checks must independently bind the final signed properties to the selected key.
52pub struct Basic<'a, 'certificate> {
53    /// URI targeting this Signature by its unique bare-name fragment.
54    pub target: &'a str,
55    /// ID assigned to the new `SignedProperties` element.
56    pub signed_properties_id: &'a str,
57    /// Claimed UTC XML Schema dateTime, validated by the XML writer.
58    pub signing_time: &'a str,
59    /// Actual whole signing certificate.
60    pub certificate: &'a Certificate<'certificate>,
61    /// XML digest-method URI selected by the native provider.
62    pub certificate_digest_method: &'a str,
63    /// Complete certificate digest computed by that provider.
64    pub certificate_digest: &'a [u8],
65    /// Signed data objects excluding `SignedProperties` and the counter target.
66    pub objects: &'a [DataObject<'a>],
67    /// Actual parent `SignatureValue` URI for a baseline countersignature.
68    pub counter_signature: Option<&'a str>,
69}
70
71impl Basic<'_, '_> {
72    /// Emit `QualifyingProperties` inside the owner's one new ds:Object.
73    /// The owner must include `certificate` in KeyInfo/X509Data and install
74    /// the returned `SignedInfo` instructions before `XMLDSig` signing.
75    ///
76    /// EN319132-1 §§4.4.2/5.2.2/5.2.7.2/6.3(a,h–l): direct incorporation,
77    /// `SigningTime`, `SigningCertificateV2` without Cert URI/issuer hint, and one
78    /// `DataObjectFormat` per ordinary object. A pure countersignature has none.
79    /// <https://www.etsi.org/deliver/etsi_en/319100_319199/31913201/01.03.01_60/en_31913201v010301p.pdf>
80    pub fn write<'a, B: Writer, W: Work>(
81        &'a self,
82        writer: &mut B,
83        work: &W,
84    ) -> Result<BasicReferences<'a>, Error<B::Error>> {
85        work.charge_work(1).map_err(Error::Resource)?;
86        if self.target.is_empty()
87            || self.signed_properties_id.is_empty()
88            || self.signing_time.is_empty()
89            || self.certificate_digest_method.is_empty()
90            || self.certificate_digest.is_empty()
91        {
92            return Err(Error::Value("basic.required"));
93        }
94        if self.counter_signature.is_none() && self.objects.is_empty() {
95            return Err(Error::Value("basic.dataObjects"));
96        }
97        for object in self.objects {
98            work.charge_work(1).map_err(Error::Resource)?;
99            if object.reference.is_empty() {
100                return Err(Error::Value("dataObject.reference"));
101            }
102            let mut mime = structured_cms::mime::Syntax::<true>::new();
103            if !mime.feed(object.mime_type, work).map_err(Error::Resource)? || !mime.complete() {
104                return Err(Error::Value("dataObject.mimeType"));
105            }
106        }
107        let mut emit = Emit { writer, work };
108        emit.begin(XADES, "QualifyingProperties")?;
109        emit.attribute("Target", self.target)?;
110        emit.begin(XADES, "SignedProperties")?;
111        emit.attribute("Id", self.signed_properties_id)?;
112        emit.begin(XADES, "SignedSignatureProperties")?;
113        emit.begin(XADES, "SigningTime")?;
114        emit.event(Event::Text(Value {
115            lexical: self.signing_time,
116            datatype: Datatype::ZonedDateTime,
117        }))?;
118        emit.end()?;
119        emit.begin(XADES, "SigningCertificateV2")?;
120        emit.begin(XADES, "Cert")?;
121        emit.begin(XADES, "CertDigest")?;
122        emit.digest(self.certificate_digest_method, self.certificate_digest)?;
123        emit.end()?;
124        emit.end()?;
125        emit.end()?;
126        emit.end()?;
127        if !self.objects.is_empty() {
128            emit.begin(XADES, "SignedDataObjectProperties")?;
129            for object in self.objects {
130                emit.begin(XADES, "DataObjectFormat")?;
131                emit.attribute("ObjectReference", object.reference)?;
132                emit.text(XADES, "MimeType", object.mime_type)?;
133                emit.end()?;
134            }
135            emit.end()?;
136        }
137        emit.end()?;
138        emit.end()?;
139        Ok(BasicReferences {
140            signed_properties: self.signed_properties_id,
141            counter_signature: self.counter_signature,
142        })
143    }
144
145    /// Emit the mandatory original signing certificate in one `X509Data`,
146    /// inside a new `KeyInfo`. No path discovery, key matching or trust is implied.
147    pub fn write_key_info<B: Writer, W: Work>(
148        &self,
149        writer: &mut B,
150        work: &W,
151    ) -> Result<(), Error<B::Error>> {
152        let scoped = self.certificate.execution_in(work);
153        let mut emit = Emit {
154            writer,
155            work: &scoped,
156        };
157        emit.begin(DS, "KeyInfo")?;
158        emit.begin(DS, "X509Data")?;
159        emit.binary(DS, "X509Certificate", self.certificate.as_bytes())?;
160        emit.end()?;
161        emit.end()
162    }
163}