Skip to main content

_synta/certificate/
ess.rs

1//! Python bindings for RFC 2634 Extended Security Services (ESS) builders.
2//!
3//! Exposes three builder classes:
4//!
5//! - [`PySigningCertificateBuilder`] — ``SigningCertificate`` (§5.4)
6//! - [`PyReceiptRequestBuilder`] — ``ReceiptRequest`` (§2.7)
7//! - [`PyESSSecurityLabelBuilder`] — ``ESSSecurityLabel`` (§3.2)
8
9use pyo3::prelude::*;
10use pyo3::types::PyBytes;
11
12// ── PySigningCertificateBuilder ───────────────────────────────────────────────
13
14/// Python-facing wrapper for [`synta_certificate::SigningCertificateBuilder`].
15///
16/// Builds a DER-encoded ``SigningCertificate`` (RFC 2634 §5.4).
17///
18/// ``SigningCertificate`` is a CMS signed attribute that identifies the
19/// signer's certificate by its SHA-1 hash and optionally by its
20/// issuer/serial number.
21///
22/// At least one certificate ID must be added before calling :meth:`build`.
23///
24/// Example::
25///
26///     import synta
27///
28///     # SHA-1 hash of the signer's DER certificate
29///     import hashlib
30///     cert_sha1 = hashlib.sha1(cert_der).digest()
31///
32///     sc_der = (
33///         synta.SigningCertificateBuilder()
34///         .add_cert_id(cert_sha1, None)
35///         .build()
36///     )
37#[pyclass(name = "SigningCertificateBuilder")]
38pub struct PySigningCertificateBuilder {
39    inner: synta_certificate::SigningCertificateBuilder,
40    built: bool,
41}
42
43#[pymethods]
44impl PySigningCertificateBuilder {
45    /// Create a new, empty ``SigningCertificateBuilder``.
46    #[new]
47    fn new() -> Self {
48        Self {
49            inner: synta_certificate::SigningCertificateBuilder::new(),
50            built: false,
51        }
52    }
53
54    /// Add a certificate entry identified by its SHA-1 hash.
55    ///
56    /// :param cert_hash: 20-byte SHA-1 hash of the complete DER-encoded
57    ///     certificate.
58    /// :param issuer_serial_der: optional pre-encoded ``IssuerSerial``
59    ///     SEQUENCE DER TLV, or ``None`` to omit.
60    /// :raises ValueError: if ``issuer_serial_der`` decoding fails
61    ///     (deferred to :meth:`build`).
62    fn add_cert_id<'py>(
63        slf: Bound<'py, Self>,
64        cert_hash: &[u8],
65        issuer_serial_der: Option<&[u8]>,
66    ) -> Bound<'py, Self> {
67        {
68            let mut guard = slf.borrow_mut();
69            let old = std::mem::replace(
70                &mut guard.inner,
71                synta_certificate::SigningCertificateBuilder::new(),
72            );
73            guard.inner = old.add_cert_id(cert_hash, issuer_serial_der);
74        }
75        slf
76    }
77
78    /// Add a certificate policy OID to the optional ``policies`` field.
79    ///
80    /// :param policy_oid: policy OID as a list of integer arc components.
81    /// :raises ValueError: if the OID is invalid (deferred to :meth:`build`).
82    fn add_policy<'py>(slf: Bound<'py, Self>, policy_oid: Vec<u32>) -> Bound<'py, Self> {
83        {
84            let mut guard = slf.borrow_mut();
85            let old = std::mem::replace(
86                &mut guard.inner,
87                synta_certificate::SigningCertificateBuilder::new(),
88            );
89            guard.inner = old.add_policy(&policy_oid);
90        }
91        slf
92    }
93
94    /// Build the DER-encoded ``SigningCertificate`` SEQUENCE.
95    ///
96    /// :returns: DER bytes of the ``SigningCertificate``.
97    /// :raises ValueError: if no certificate IDs were added, if DER encoding
98    ///     fails, or if called more than once.
99    fn build<'py>(&mut self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
100        if self.built {
101            return Err(pyo3::exceptions::PyValueError::new_err(
102                "build() has already been called; create a new builder",
103            ));
104        }
105        self.built = true;
106        let inner = std::mem::replace(
107            &mut self.inner,
108            synta_certificate::SigningCertificateBuilder::new(),
109        );
110        let der = inner
111            .build()
112            .map_err(pyo3::exceptions::PyValueError::new_err)?;
113        Ok(PyBytes::new(py, &der))
114    }
115
116    fn __repr__(&self) -> String {
117        "SigningCertificateBuilder()".to_string()
118    }
119}
120
121// ── PyReceiptRequestBuilder ───────────────────────────────────────────────────
122
123/// Python-facing wrapper for [`synta_certificate::ReceiptRequestBuilder`].
124///
125/// Builds a DER-encoded ``ReceiptRequest`` (RFC 2634 §2.7).
126///
127/// A ``ReceiptRequest`` is a CMS signed attribute that requests a signed
128/// receipt.  Required fields: ``signed_content_identifier`` and one
129/// ``receipts_from_*`` call.
130///
131/// Example::
132///
133///     import synta
134///
135///     rr_der = (
136///         synta.ReceiptRequestBuilder()
137///         .signed_content_identifier(b"\\x01\\x02\\x03\\x04")
138///         .receipts_from_all()
139///         .add_receipt_to_email("receipts@example.com")
140///         .build()
141///     )
142#[pyclass(name = "ReceiptRequestBuilder")]
143pub struct PyReceiptRequestBuilder {
144    inner: synta_certificate::ReceiptRequestBuilder,
145    built: bool,
146}
147
148#[pymethods]
149impl PyReceiptRequestBuilder {
150    /// Create a new, empty ``ReceiptRequestBuilder``.
151    #[new]
152    fn new() -> Self {
153        Self {
154            inner: synta_certificate::ReceiptRequestBuilder::new(),
155            built: false,
156        }
157    }
158
159    /// Set the ``signedContentIdentifier`` (an OCTET STRING value).
160    ///
161    /// :param id: the content identifier bytes.
162    fn signed_content_identifier<'py>(slf: Bound<'py, Self>, id: &[u8]) -> Bound<'py, Self> {
163        {
164            let mut guard = slf.borrow_mut();
165            let old = std::mem::replace(
166                &mut guard.inner,
167                synta_certificate::ReceiptRequestBuilder::new(),
168            );
169            guard.inner = old.signed_content_identifier(id);
170        }
171        slf
172    }
173
174    /// Request receipts from all recipients (``allReceipts [0]``).
175    fn receipts_from_all<'py>(slf: Bound<'py, Self>) -> Bound<'py, Self> {
176        {
177            let mut guard = slf.borrow_mut();
178            let old = std::mem::replace(
179                &mut guard.inner,
180                synta_certificate::ReceiptRequestBuilder::new(),
181            );
182            guard.inner = old.receipts_from_all();
183        }
184        slf
185    }
186
187    /// Request receipts from first-tier recipients only
188    /// (``firstTierRecipients``, value 1 within the ``allOrFirstTier [0]`` CHOICE).
189    /// (``firstTierOnly [1]``).
190    fn receipts_from_first_tier<'py>(slf: Bound<'py, Self>) -> Bound<'py, Self> {
191        {
192            let mut guard = slf.borrow_mut();
193            let old = std::mem::replace(
194                &mut guard.inner,
195                synta_certificate::ReceiptRequestBuilder::new(),
196            );
197            guard.inner = old.receipts_from_first_tier();
198        }
199        slf
200    }
201
202    /// Add an email address to the ``receiptsTo`` list.
203    ///
204    /// Each entry in ``receiptsTo`` is a ``GeneralNames`` (SEQUENCE OF
205    /// GeneralName).  This convenience method wraps a single RFC 822 name.
206    ///
207    /// :param email: email address string.
208    /// :raises ValueError: if the email address is invalid (deferred to
209    ///     :meth:`build`).
210    fn add_receipt_to_email<'py>(slf: Bound<'py, Self>, email: &str) -> Bound<'py, Self> {
211        {
212            let mut guard = slf.borrow_mut();
213            let old = std::mem::replace(
214                &mut guard.inner,
215                synta_certificate::ReceiptRequestBuilder::new(),
216            );
217            guard.inner = old.add_receipt_to_email(email);
218        }
219        slf
220    }
221
222    /// Add a pre-encoded ``GeneralNames`` DER TLV to the ``receiptsTo`` list.
223    ///
224    /// :param general_names_der: DER-encoded ``GeneralNames`` SEQUENCE TLV.
225    fn add_receipt_to_raw<'py>(
226        slf: Bound<'py, Self>,
227        general_names_der: &[u8],
228    ) -> Bound<'py, Self> {
229        {
230            let mut guard = slf.borrow_mut();
231            let old = std::mem::replace(
232                &mut guard.inner,
233                synta_certificate::ReceiptRequestBuilder::new(),
234            );
235            guard.inner = old.add_receipt_to_raw(general_names_der);
236        }
237        slf
238    }
239
240    /// Build the DER-encoded ``ReceiptRequest`` SEQUENCE.
241    ///
242    /// :returns: DER bytes of the ``ReceiptRequest``.
243    /// :raises ValueError: if required fields are missing, DER encoding fails,
244    ///     or if called more than once.
245    fn build<'py>(&mut self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
246        if self.built {
247            return Err(pyo3::exceptions::PyValueError::new_err(
248                "build() has already been called; create a new builder",
249            ));
250        }
251        self.built = true;
252        let inner = std::mem::replace(
253            &mut self.inner,
254            synta_certificate::ReceiptRequestBuilder::new(),
255        );
256        let der = inner
257            .build()
258            .map_err(pyo3::exceptions::PyValueError::new_err)?;
259        Ok(PyBytes::new(py, &der))
260    }
261
262    fn __repr__(&self) -> String {
263        "ReceiptRequestBuilder()".to_string()
264    }
265}
266
267// ── PyESSSecurityLabelBuilder ─────────────────────────────────────────────────
268
269/// Python-facing wrapper for [`synta_certificate::ESSSecurityLabelBuilder`].
270///
271/// Builds a DER-encoded ``ESSSecurityLabel`` SET (RFC 2634 §3.2).
272///
273/// An ``ESSSecurityLabel`` is a CMS signed attribute carrying an information
274/// security label.  The ``security_policy`` OID is required; all other fields
275/// are optional.
276///
277/// Example::
278///
279///     import synta
280///
281///     # Custom policy OID (1.3.6.1.5.5.7.13.1 is a test OID)
282///     policy_oid = [1, 3, 6, 1, 5, 5, 7, 13, 1]
283///
284///     label_der = (
285///         synta.ESSSecurityLabelBuilder()
286///         .security_policy(policy_oid)
287///         .classification(3)       # CONFIDENTIAL
288///         .privacy_mark_utf8("CONFIDENTIAL")
289///         .build()
290///     )
291#[pyclass(name = "ESSSecurityLabelBuilder")]
292pub struct PyESSSecurityLabelBuilder {
293    inner: synta_certificate::ESSSecurityLabelBuilder,
294    built: bool,
295}
296
297#[pymethods]
298impl PyESSSecurityLabelBuilder {
299    /// Create a new, empty ``ESSSecurityLabelBuilder``.
300    #[new]
301    fn new() -> Self {
302        Self {
303            inner: synta_certificate::ESSSecurityLabelBuilder::new(),
304            built: false,
305        }
306    }
307
308    /// Set the ``securityPolicyIdentifier`` OID.
309    ///
310    /// This field is required.
311    ///
312    /// :param policy_oid: OID arc components as a list of ints.
313    /// :raises ValueError: if the OID is invalid (deferred to :meth:`build`).
314    fn security_policy<'py>(slf: Bound<'py, Self>, policy_oid: Vec<u32>) -> Bound<'py, Self> {
315        {
316            let mut guard = slf.borrow_mut();
317            let old = std::mem::replace(
318                &mut guard.inner,
319                synta_certificate::ESSSecurityLabelBuilder::new(),
320            );
321            guard.inner = old.security_policy(&policy_oid);
322        }
323        slf
324    }
325
326    /// Set the optional ``securityClassification`` value.
327    ///
328    /// Standard RFC 2634 §3.2 values: 0=UNMARKED, 1=UNCLASSIFIED,
329    /// 2=RESTRICTED, 3=CONFIDENTIAL, 4=SECRET, 5=TOP_SECRET.
330    ///
331    /// :param value: classification integer (0–32767).
332    /// :raises ValueError: if ``value`` exceeds 32767 (deferred to :meth:`build`).
333    fn classification<'py>(slf: Bound<'py, Self>, value: u16) -> Bound<'py, Self> {
334        {
335            let mut guard = slf.borrow_mut();
336            let old = std::mem::replace(
337                &mut guard.inner,
338                synta_certificate::ESSSecurityLabelBuilder::new(),
339            );
340            guard.inner = old.classification(value);
341        }
342        slf
343    }
344
345    /// Set the optional ``privacyMark`` as a UTF8String.
346    ///
347    /// :param mark: the privacy mark text string.
348    fn privacy_mark_utf8<'py>(slf: Bound<'py, Self>, mark: &str) -> Bound<'py, Self> {
349        {
350            let mut guard = slf.borrow_mut();
351            let old = std::mem::replace(
352                &mut guard.inner,
353                synta_certificate::ESSSecurityLabelBuilder::new(),
354            );
355            guard.inner = old.privacy_mark_utf8(mark);
356        }
357        slf
358    }
359
360    /// Set the optional ``privacyMark`` as a PrintableString.
361    ///
362    /// :param mark: the privacy mark text string (ASCII printable characters only).
363    /// :raises ValueError: if ``mark`` contains characters not allowed in
364    ///     PrintableString (deferred to :meth:`build`).
365    fn privacy_mark_printable<'py>(slf: Bound<'py, Self>, mark: &str) -> Bound<'py, Self> {
366        {
367            let mut guard = slf.borrow_mut();
368            let old = std::mem::replace(
369                &mut guard.inner,
370                synta_certificate::ESSSecurityLabelBuilder::new(),
371            );
372            guard.inner = old.privacy_mark_printable(mark);
373        }
374        slf
375    }
376
377    /// Add a pre-encoded ``SecurityCategory`` DER TLV to the security categories field.
378    ///
379    /// Each call appends one ``SecurityCategory`` to the ``securityCategories``
380    /// ``SET OF SecurityCategory`` field (RFC 2634 §3.2).
381    ///
382    /// :param security_category_der: DER-encoded ``SecurityCategory`` SEQUENCE TLV.
383    fn add_security_category_raw<'py>(
384        slf: Bound<'py, Self>,
385        security_category_der: &[u8],
386    ) -> Bound<'py, Self> {
387        {
388            let mut guard = slf.borrow_mut();
389            let old = std::mem::replace(
390                &mut guard.inner,
391                synta_certificate::ESSSecurityLabelBuilder::new(),
392            );
393            guard.inner = old.add_security_category_raw(security_category_der);
394        }
395        slf
396    }
397
398    /// Build the DER-encoded ``ESSSecurityLabel`` SET.
399    ///
400    /// :returns: DER bytes of the ``ESSSecurityLabel``.
401    /// :raises ValueError: if ``security_policy`` was not set, DER encoding
402    ///     fails, or if called more than once.
403    fn build<'py>(&mut self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
404        if self.built {
405            return Err(pyo3::exceptions::PyValueError::new_err(
406                "build() has already been called; create a new builder",
407            ));
408        }
409        self.built = true;
410        let inner = std::mem::replace(
411            &mut self.inner,
412            synta_certificate::ESSSecurityLabelBuilder::new(),
413        );
414        let der = inner
415            .build()
416            .map_err(pyo3::exceptions::PyValueError::new_err)?;
417        Ok(PyBytes::new(py, &der))
418    }
419
420    fn __repr__(&self) -> String {
421        "ESSSecurityLabelBuilder()".to_string()
422    }
423}
424
425// ── register ──────────────────────────────────────────────────────────────────
426
427/// Register ESS builder classes into the given module.
428pub(super) fn register_ess_classes(m: &Bound<'_, PyModule>) -> PyResult<()> {
429    m.add_class::<PySigningCertificateBuilder>()?;
430    m.add_class::<PyReceiptRequestBuilder>()?;
431    m.add_class::<PyESSSecurityLabelBuilder>()?;
432    Ok(())
433}