_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}