1use std::sync::OnceLock;
5
6use pyo3::prelude::*;
7use pyo3::types::{PyBytes, PyList, PyString};
8
9use synta::traits::Encode;
10use synta::{Decoder, Encoding};
11use synta_certificate::Time;
12
13use super::cert::{pem_blocks_to_pyobject, pyobject_to_pem, PyCertificate};
14use crate::crypto_keys::PyPrivateKey;
15use crate::types::PyObjectIdentifier;
16
17fn name_to_dn_string(name: &synta_certificate::Name<'_>) -> String {
21 let mut enc = synta::Encoder::new(synta::Encoding::Der);
22 if name.encode(&mut enc).is_err() {
23 return String::new();
24 }
25 synta_certificate::name::format_dn(&enc.finish().unwrap_or_default())
26}
27
28fn name_to_der_bytes(name: &synta_certificate::Name<'_>) -> Vec<u8> {
30 let mut enc = synta::Encoder::new(synta::Encoding::Der);
31 if name.encode(&mut enc).is_err() {
32 return Vec::new();
33 }
34 enc.finish().unwrap_or_default()
35}
36
37fn ocsp_status_str(status: synta_certificate::ocsp::OCSPResponseStatus) -> &'static str {
39 use synta_certificate::ocsp::OCSPResponseStatus::*;
40 match status {
41 Successful => "successful",
42 MalformedRequest => "malformedRequest",
43 InternalError => "internalError",
44 TryLater => "tryLater",
45 SigRequired => "sigRequired",
46 Unauthorized => "unauthorized",
47 }
48}
49
50#[pyclass(frozen, name = "CertificationRequest")]
60pub struct PyCsr {
61 pub(super) _data: Py<PyBytes>,
62 pub(super) raw: &'static [u8],
63 inner: OnceLock<Box<synta_certificate::csr::CertificationRequest<'static>>>,
64 subject_cache: OnceLock<Py<PyString>>,
65 subject_raw_der_cache: OnceLock<Py<PyBytes>>,
66 signature_algorithm_cache: OnceLock<Py<PyString>>,
67 signature_algorithm_oid_cache: OnceLock<Py<PyObjectIdentifier>>,
68 signature_cache: OnceLock<Py<PyBytes>>,
69 public_key_algorithm_cache: OnceLock<Py<PyString>>,
70 public_key_algorithm_oid_cache: OnceLock<Py<PyObjectIdentifier>>,
71 public_key_cache: OnceLock<Py<PyBytes>>,
72 spki_der_cache: OnceLock<Py<PyBytes>>,
73}
74
75impl PyCsr {
76 fn csr(&self) -> PyResult<&synta_certificate::csr::CertificationRequest<'static>> {
77 if let Some(v) = self.inner.get() {
78 return Ok(v.as_ref());
79 }
80 let mut decoder = Decoder::new(self.raw, Encoding::Der);
81 let decoded = decoder.decode().map_err(|e| {
82 pyo3::exceptions::PyValueError::new_err(format!(
83 "CertificationRequest DER decode failed: {e}"
84 ))
85 })?;
86 let _ = self.inner.set(Box::new(decoded));
87 Ok(self.inner.get().unwrap().as_ref())
88 }
89}
90
91#[pymethods]
92impl PyCsr {
93 #[staticmethod]
95 fn from_der(py: Python<'_>, data: Bound<'_, PyBytes>) -> PyResult<Self> {
96 let py_bytes = data.unbind();
97 let raw: &'static [u8] = unsafe {
111 let s = py_bytes.bind(py).as_bytes();
112 std::slice::from_raw_parts(s.as_ptr(), s.len())
113 };
114 {
116 let mut d = Decoder::new(raw, Encoding::Der);
117 d.read_tag()
118 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
119 d.read_length()
120 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
121 }
122 Ok(Self {
123 _data: py_bytes,
124 raw,
125 inner: OnceLock::new(),
126 subject_cache: OnceLock::new(),
127 subject_raw_der_cache: OnceLock::new(),
128 signature_algorithm_cache: OnceLock::new(),
129 signature_algorithm_oid_cache: OnceLock::new(),
130 signature_cache: OnceLock::new(),
131 public_key_algorithm_cache: OnceLock::new(),
132 public_key_algorithm_oid_cache: OnceLock::new(),
133 public_key_cache: OnceLock::new(),
134 spki_der_cache: OnceLock::new(),
135 })
136 }
137
138 #[staticmethod]
149 fn from_pem<'py>(py: Python<'py>, data: Bound<'_, PyBytes>) -> PyResult<Bound<'py, PyAny>> {
150 pem_blocks_to_pyobject(py, data.as_bytes(), |py, bytes| {
151 let obj = Self::from_der(py, bytes)?;
152 Ok(Py::new(py, obj)?.into_bound(py).into_any())
153 })
154 }
155
156 #[staticmethod]
163 fn to_pem<'py>(
164 py: Python<'py>,
165 obj_or_list: Bound<'_, PyAny>,
166 ) -> PyResult<Bound<'py, PyBytes>> {
167 pyobject_to_pem::<Self, _>(py, "CERTIFICATE REQUEST", &obj_or_list, |c| c.raw)
168 }
169
170 #[getter]
172 fn subject<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyString>> {
173 if let Some(cached) = self.subject_cache.get() {
174 return Ok(cached.clone_ref(py).into_bound(py));
175 }
176 let s = name_to_dn_string(&self.csr()?.certification_request_info.subject);
177 let py_str = PyString::new(py, &s).unbind();
178 let _ = self.subject_cache.set(py_str.clone_ref(py));
179 Ok(py_str.into_bound(py))
180 }
181
182 #[getter]
184 fn subject_raw_der<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
185 if let Some(cached) = self.subject_raw_der_cache.get() {
186 return Ok(cached.clone_ref(py).into_bound(py));
187 }
188 let bytes = name_to_der_bytes(&self.csr()?.certification_request_info.subject);
189 let py_bytes = PyBytes::new(py, &bytes).unbind();
190 let _ = self.subject_raw_der_cache.set(py_bytes.clone_ref(py));
191 Ok(py_bytes.into_bound(py))
192 }
193
194 #[getter]
196 fn version(&self) -> PyResult<i64> {
197 Ok(self
198 .csr()?
199 .certification_request_info
200 .version
201 .as_i64()
202 .unwrap_or(0))
203 }
204
205 #[getter]
207 fn signature_algorithm<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyString>> {
208 if let Some(cached) = self.signature_algorithm_cache.get() {
209 return Ok(cached.clone_ref(py).into_bound(py));
210 }
211 let oid = &self.csr()?.signature_algorithm.algorithm;
212 let name = synta_certificate::identify_signature_algorithm(oid);
213 let s = if name != "Other" {
214 name.to_string()
215 } else {
216 oid.to_string()
217 };
218 let py_str = PyString::new(py, &s).unbind();
219 let _ = self.signature_algorithm_cache.set(py_str.clone_ref(py));
220 Ok(py_str.into_bound(py))
221 }
222
223 #[getter]
225 fn signature_algorithm_oid<'py>(
226 &self,
227 py: Python<'py>,
228 ) -> PyResult<Bound<'py, PyObjectIdentifier>> {
229 if let Some(cached) = self.signature_algorithm_oid_cache.get() {
230 return Ok(cached.clone_ref(py).into_bound(py));
231 }
232 let obj = Py::new(
233 py,
234 PyObjectIdentifier::from_oid(self.csr()?.signature_algorithm.algorithm.clone()),
235 )?;
236 let _ = self.signature_algorithm_oid_cache.set(obj.clone_ref(py));
237 Ok(obj.into_bound(py))
238 }
239
240 #[getter]
242 fn signature<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
243 if let Some(cached) = self.signature_cache.get() {
244 return Ok(cached.clone_ref(py).into_bound(py));
245 }
246 let py_bytes = PyBytes::new(py, self.csr()?.signature.as_bytes()).unbind();
247 let _ = self.signature_cache.set(py_bytes.clone_ref(py));
248 Ok(py_bytes.into_bound(py))
249 }
250
251 #[getter]
253 fn public_key_algorithm<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyString>> {
254 if let Some(cached) = self.public_key_algorithm_cache.get() {
255 return Ok(cached.clone_ref(py).into_bound(py));
256 }
257 let oid = &self
258 .csr()?
259 .certification_request_info
260 .subject_pkinfo
261 .algorithm
262 .algorithm;
263 let s = synta_certificate::identify_public_key_algorithm(oid)
264 .map(|s| s.to_string())
265 .unwrap_or_else(|| oid.to_string());
266 let py_str = PyString::new(py, &s).unbind();
267 let _ = self.public_key_algorithm_cache.set(py_str.clone_ref(py));
268 Ok(py_str.into_bound(py))
269 }
270
271 #[getter]
273 fn public_key_algorithm_oid<'py>(
274 &self,
275 py: Python<'py>,
276 ) -> PyResult<Bound<'py, PyObjectIdentifier>> {
277 if let Some(cached) = self.public_key_algorithm_oid_cache.get() {
278 return Ok(cached.clone_ref(py).into_bound(py));
279 }
280 let obj = Py::new(
281 py,
282 PyObjectIdentifier::from_oid(
283 self.csr()?
284 .certification_request_info
285 .subject_pkinfo
286 .algorithm
287 .algorithm
288 .clone(),
289 ),
290 )?;
291 let _ = self.public_key_algorithm_oid_cache.set(obj.clone_ref(py));
292 Ok(obj.into_bound(py))
293 }
294
295 #[getter]
297 fn public_key<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
298 if let Some(cached) = self.public_key_cache.get() {
299 return Ok(cached.clone_ref(py).into_bound(py));
300 }
301 let py_bytes = PyBytes::new(
302 py,
303 self.csr()?
304 .certification_request_info
305 .subject_pkinfo
306 .subject_public_key
307 .as_bytes(),
308 )
309 .unbind();
310 let _ = self.public_key_cache.set(py_bytes.clone_ref(py));
311 Ok(py_bytes.into_bound(py))
312 }
313
314 #[getter]
332 fn subject_public_key_info_der<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
333 if let Some(cached) = self.spki_der_cache.get() {
334 return Ok(cached.clone_ref(py).into_bound(py));
335 }
336 let spki = &self.csr()?.certification_request_info.subject_pkinfo;
337 let bytes = spki
338 .to_der()
339 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("SPKI encode: {e}")))?;
340 let py_bytes = PyBytes::new(py, &bytes).unbind();
341 let _ = self.spki_der_cache.set(py_bytes.clone_ref(py));
342 Ok(py_bytes.into_bound(py))
343 }
344
345 fn get_extension_value_der<'py>(
363 &self,
364 py: Python<'py>,
365 oid: &Bound<'_, PyAny>,
366 ) -> PyResult<Option<Bound<'py, PyBytes>>> {
367 let target = super::oid_from_pyany(oid)?;
368 let csr = self.csr()?;
369 let attrs = match csr.certification_request_info.attributes.as_ref() {
370 Some(a) => a,
371 None => return Ok(None),
372 };
373 for attr in attrs.elements() {
374 if attr.attr_type.components() != synta_certificate::oids::PKCS9_EXTENSION_REQUEST {
375 continue;
376 }
377 let Some(raw) = attr.attr_values.elements().first() else {
379 return Ok(None);
380 };
381 return Ok(synta_certificate::find_extension_value(
382 raw.as_bytes(),
383 target.components(),
384 )
385 .map(|v| PyBytes::new(py, v)));
386 }
387 Ok(None)
388 }
389
390 fn subject_alt_names<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyList>> {
405 use pyo3::types::{PyBytes, PyList, PyTuple};
406 let list = PyList::empty(py);
407 let csr = self.csr()?;
408 let attrs = match csr.certification_request_info.attributes.as_ref() {
409 Some(a) => a,
410 None => return Ok(list),
411 };
412 for attr in attrs.elements() {
413 if attr.attr_type.components() != synta_certificate::oids::PKCS9_EXTENSION_REQUEST {
414 continue;
415 }
416 let Some(raw) = attr.attr_values.elements().first() else {
417 return Ok(list);
418 };
419 if let Some(san_bytes) = synta_certificate::find_extension_value(
420 raw.as_bytes(),
421 synta_certificate::oids::SUBJECT_ALT_NAME,
422 ) {
423 for (tag_num, content) in synta_certificate::parse_general_names(san_bytes) {
424 let tuple = PyTuple::new(
425 py,
426 [
427 tag_num.into_pyobject(py)?.into_any(),
428 PyBytes::new(py, &content).into_any(),
429 ],
430 )?;
431 list.append(tuple)?;
432 }
433 }
434 break;
435 }
436 Ok(list)
437 }
438
439 fn to_der<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
441 self._data.clone_ref(py).into_bound(py)
442 }
443
444 fn verify_self_signature(&self) -> PyResult<()> {
457 use synta_certificate::{default_signature_verifier, SignatureVerifier};
458
459 let csr = self.csr()?;
460
461 let tbs_der = csr.certification_request_info.to_der().map_err(|e| {
463 pyo3::exceptions::PyValueError::new_err(format!("CSR info encode: {e}"))
464 })?;
465
466 let sig_alg_der = csr
468 .signature_algorithm
469 .to_der()
470 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("CSR alg encode: {e}")))?;
471
472 let spki_der = csr
474 .certification_request_info
475 .subject_pkinfo
476 .to_der()
477 .map_err(|e| {
478 pyo3::exceptions::PyValueError::new_err(format!("CSR SPKI encode: {e}"))
479 })?;
480
481 default_signature_verifier()
483 .verify_certificate_signature(
484 &tbs_der,
485 &sig_alg_der,
486 csr.signature.as_bytes(),
487 &spki_der,
488 )
489 .map_err(|e| {
490 pyo3::exceptions::PyValueError::new_err(format!("CSR self-signature invalid: {e}"))
491 })
492 }
493
494 fn __repr__(&self) -> PyResult<String> {
495 Ok(format!(
496 "CertificationRequest(subject={:?})",
497 name_to_dn_string(&self.csr()?.certification_request_info.subject),
498 ))
499 }
500}
501
502impl PyCsr {
503 pub(crate) fn new_from_der(py: Python<'_>, data: Bound<'_, PyBytes>) -> PyResult<Self> {
507 Self::from_der(py, data)
508 }
509}
510
511#[pyclass(frozen, name = "CertificateList")]
521pub struct PyCrl {
522 pub(super) _data: Py<PyBytes>,
523 pub(super) raw: &'static [u8],
524 inner: OnceLock<Box<synta_certificate::crl::CertificateList<'static>>>,
525 issuer_cache: OnceLock<Py<PyString>>,
526 issuer_raw_der_cache: OnceLock<Py<PyBytes>>,
527 this_update_cache: OnceLock<Py<PyString>>,
528 next_update_cache: OnceLock<Option<Py<PyString>>>,
529 signature_algorithm_cache: OnceLock<Py<PyString>>,
530 signature_algorithm_oid_cache: OnceLock<Py<PyObjectIdentifier>>,
531 signature_value_cache: OnceLock<Py<PyBytes>>,
532}
533
534impl PyCrl {
535 fn crl(&self) -> PyResult<&synta_certificate::crl::CertificateList<'static>> {
536 if let Some(v) = self.inner.get() {
537 return Ok(v.as_ref());
538 }
539 let mut decoder = Decoder::new(self.raw, Encoding::Der);
540 let decoded = decoder.decode().map_err(|e| {
541 pyo3::exceptions::PyValueError::new_err(format!(
542 "CertificateList DER decode failed: {e}"
543 ))
544 })?;
545 let _ = self.inner.set(Box::new(decoded));
546 Ok(self.inner.get().unwrap().as_ref())
547 }
548}
549
550#[pymethods]
551impl PyCrl {
552 #[staticmethod]
554 fn from_der(py: Python<'_>, data: Bound<'_, PyBytes>) -> PyResult<Self> {
555 let py_bytes = data.unbind();
556 let raw: &'static [u8] = unsafe {
570 let s = py_bytes.bind(py).as_bytes();
571 std::slice::from_raw_parts(s.as_ptr(), s.len())
572 };
573 {
574 let mut d = Decoder::new(raw, Encoding::Der);
575 d.read_tag()
576 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
577 d.read_length()
578 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
579 }
580 Ok(Self {
581 _data: py_bytes,
582 raw,
583 inner: OnceLock::new(),
584 issuer_cache: OnceLock::new(),
585 issuer_raw_der_cache: OnceLock::new(),
586 this_update_cache: OnceLock::new(),
587 next_update_cache: OnceLock::new(),
588 signature_algorithm_cache: OnceLock::new(),
589 signature_algorithm_oid_cache: OnceLock::new(),
590 signature_value_cache: OnceLock::new(),
591 })
592 }
593
594 #[staticmethod]
605 fn from_pem<'py>(py: Python<'py>, data: Bound<'_, PyBytes>) -> PyResult<Bound<'py, PyAny>> {
606 pem_blocks_to_pyobject(py, data.as_bytes(), |py, bytes| {
607 let obj = Self::from_der(py, bytes)?;
608 Ok(Py::new(py, obj)?.into_bound(py).into_any())
609 })
610 }
611
612 #[staticmethod]
619 fn to_pem<'py>(
620 py: Python<'py>,
621 obj_or_list: Bound<'_, PyAny>,
622 ) -> PyResult<Bound<'py, PyBytes>> {
623 pyobject_to_pem::<Self, _>(py, "X509 CRL", &obj_or_list, |c| c.raw)
624 }
625
626 #[getter]
632 fn version(&self) -> PyResult<Option<i64>> {
633 Ok(self
634 .crl()?
635 .tbs_cert_list
636 .version
637 .as_ref()
638 .and_then(|v| v.as_i64().ok()))
639 }
640
641 #[getter]
643 fn issuer<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyString>> {
644 if let Some(cached) = self.issuer_cache.get() {
645 return Ok(cached.clone_ref(py).into_bound(py));
646 }
647 let s = name_to_dn_string(&self.crl()?.tbs_cert_list.issuer);
648 let py_str = PyString::new(py, &s).unbind();
649 let _ = self.issuer_cache.set(py_str.clone_ref(py));
650 Ok(py_str.into_bound(py))
651 }
652
653 #[getter]
655 fn issuer_raw_der<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
656 if let Some(cached) = self.issuer_raw_der_cache.get() {
657 return Ok(cached.clone_ref(py).into_bound(py));
658 }
659 let bytes = name_to_der_bytes(&self.crl()?.tbs_cert_list.issuer);
660 let py_bytes = PyBytes::new(py, &bytes).unbind();
661 let _ = self.issuer_raw_der_cache.set(py_bytes.clone_ref(py));
662 Ok(py_bytes.into_bound(py))
663 }
664
665 #[getter]
667 fn this_update<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyString>> {
668 if let Some(cached) = self.this_update_cache.get() {
669 return Ok(cached.clone_ref(py).into_bound(py));
670 }
671 let s = match &self.crl()?.tbs_cert_list.this_update {
672 Time::UtcTime(t) => t.to_string(),
673 Time::GeneralTime(t) => t.to_string(),
674 };
675 let py_str = PyString::new(py, &s).unbind();
676 let _ = self.this_update_cache.set(py_str.clone_ref(py));
677 Ok(py_str.into_bound(py))
678 }
679
680 #[getter]
682 fn next_update<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyString>>> {
683 if let Some(cached) = self.next_update_cache.get() {
684 return Ok(cached.as_ref().map(|s| s.clone_ref(py).into_bound(py)));
685 }
686 let computed: Option<Bound<'py, PyString>> =
687 self.crl()?.tbs_cert_list.next_update.as_ref().map(|t| {
688 let s = match t {
689 Time::UtcTime(t) => t.to_string(),
690 Time::GeneralTime(t) => t.to_string(),
691 };
692 PyString::new(py, &s)
693 });
694 let to_store = computed.as_ref().map(|s| s.as_unbound().clone_ref(py));
695 let _ = self.next_update_cache.set(to_store);
696 Ok(computed)
697 }
698
699 #[getter]
701 fn signature_algorithm<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyString>> {
702 if let Some(cached) = self.signature_algorithm_cache.get() {
703 return Ok(cached.clone_ref(py).into_bound(py));
704 }
705 let oid = &self.crl()?.signature_algorithm.algorithm;
706 let name = synta_certificate::identify_signature_algorithm(oid);
707 let s = if name != "Other" {
708 name.to_string()
709 } else {
710 oid.to_string()
711 };
712 let py_str = PyString::new(py, &s).unbind();
713 let _ = self.signature_algorithm_cache.set(py_str.clone_ref(py));
714 Ok(py_str.into_bound(py))
715 }
716
717 #[getter]
719 fn signature_algorithm_oid<'py>(
720 &self,
721 py: Python<'py>,
722 ) -> PyResult<Bound<'py, PyObjectIdentifier>> {
723 if let Some(cached) = self.signature_algorithm_oid_cache.get() {
724 return Ok(cached.clone_ref(py).into_bound(py));
725 }
726 let obj = Py::new(
727 py,
728 PyObjectIdentifier::from_oid(self.crl()?.signature_algorithm.algorithm.clone()),
729 )?;
730 let _ = self.signature_algorithm_oid_cache.set(obj.clone_ref(py));
731 Ok(obj.into_bound(py))
732 }
733
734 #[getter]
736 fn signature_value<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
737 if let Some(cached) = self.signature_value_cache.get() {
738 return Ok(cached.clone_ref(py).into_bound(py));
739 }
740 let py_bytes = PyBytes::new(py, self.crl()?.signature_value.as_bytes()).unbind();
741 let _ = self.signature_value_cache.set(py_bytes.clone_ref(py));
742 Ok(py_bytes.into_bound(py))
743 }
744
745 #[getter]
755 fn crl_number<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyAny>>> {
756 let Some(n) = self.crl()?.crl_number() else {
757 return Ok(None);
758 };
759 let py_int = if let Ok(v) = n.as_i64() {
760 v.into_pyobject(py)?.into_any()
761 } else if let Ok(v) = n.as_i128() {
762 v.into_pyobject(py)?.into_any()
763 } else {
764 let bytes_obj = PyBytes::new(py, n.as_bytes());
766 let kwargs = pyo3::types::PyDict::new(py);
767 kwargs.set_item(pyo3::intern!(py, "signed"), false)?;
768 py.get_type::<pyo3::types::PyInt>().call_method(
769 pyo3::intern!(py, "from_bytes"),
770 (bytes_obj, pyo3::intern!(py, "big")),
771 Some(&kwargs),
772 )?
773 };
774 Ok(Some(py_int))
775 }
776
777 #[getter]
779 fn revoked_count(&self) -> PyResult<usize> {
780 Ok(self
781 .crl()?
782 .tbs_cert_list
783 .revoked_certificates
784 .as_ref()
785 .map(|v| v.len())
786 .unwrap_or(0))
787 }
788
789 fn get_extension_value_der<'py>(
805 &self,
806 py: Python<'py>,
807 oid: &Bound<'_, PyAny>,
808 ) -> PyResult<Option<Bound<'py, PyBytes>>> {
809 let target = super::oid_from_pyany(oid)?;
810 let exts = match self.crl()?.tbs_cert_list.crl_extensions.as_ref() {
811 Some(e) => e,
812 None => return Ok(None),
813 };
814 for ext in exts {
815 if ext.extn_id == target {
816 return Ok(Some(PyBytes::new(py, ext.extn_value.as_bytes())));
817 }
818 }
819 Ok(None)
820 }
821
822 fn to_der<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
824 self._data.clone_ref(py).into_bound(py)
825 }
826
827 fn verify_issued_by(&self, issuer: &super::cert::PyCertificate) -> PyResult<()> {
842 use synta::traits::Encode;
843 use synta_certificate::{default_signature_verifier, SignatureVerifier};
844
845 let crl = self.crl()?;
846 let issuer_cert = issuer.cert()?;
847
848 let crl_issuer_der = name_to_der_bytes(&crl.tbs_cert_list.issuer);
850 if crl_issuer_der.as_slice() != issuer_cert.tbs_certificate.subject.as_bytes() {
851 return Err(pyo3::exceptions::PyValueError::new_err(
852 "CRL issuer name does not match subject of provided certificate",
853 ));
854 }
855
856 let mut tbs_enc = synta::Encoder::new(synta::Encoding::Der);
858 crl.tbs_cert_list
859 .encode(&mut tbs_enc)
860 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("CRL TBS encode: {e}")))?;
861 let tbs_der = tbs_enc
862 .finish()
863 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("CRL TBS finish: {e}")))?;
864
865 let mut alg_enc = synta::Encoder::new(synta::Encoding::Der);
867 crl.signature_algorithm
868 .encode(&mut alg_enc)
869 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("CRL alg encode: {e}")))?;
870 let sig_alg_der = alg_enc
871 .finish()
872 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("CRL alg finish: {e}")))?;
873
874 let mut spki_enc = synta::Encoder::new(synta::Encoding::Der);
876 issuer_cert
877 .tbs_certificate
878 .subject_public_key_info
879 .encode(&mut spki_enc)
880 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("SPKI encode: {e}")))?;
881 let spki_der = spki_enc
882 .finish()
883 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("SPKI finish: {e}")))?;
884
885 default_signature_verifier()
887 .verify_certificate_signature(
888 &tbs_der,
889 &sig_alg_der,
890 crl.signature_value.as_bytes(),
891 &spki_der,
892 )
893 .map_err(|e| {
894 pyo3::exceptions::PyValueError::new_err(format!("CRL signature invalid: {e}"))
895 })
896 }
897
898 fn __repr__(&self) -> PyResult<String> {
899 let crl = self.crl()?;
900 Ok(format!(
901 "CertificateList(issuer={:?}, revoked_count={})",
902 name_to_dn_string(&crl.tbs_cert_list.issuer),
903 crl.tbs_cert_list
904 .revoked_certificates
905 .as_ref()
906 .map(|v| v.len())
907 .unwrap_or(0),
908 ))
909 }
910}
911
912#[pyclass(frozen, name = "OCSPResponse")]
923pub struct PyOcspResponse {
924 pub(super) _data: Py<PyBytes>,
925 pub(super) raw: &'static [u8],
926 inner: OnceLock<Box<synta_certificate::ocsp::OCSPResponse<'static>>>,
927 status_cache: OnceLock<Py<PyString>>,
928 response_type_oid_cache: OnceLock<Option<Py<PyObjectIdentifier>>>,
929 response_bytes_cache: OnceLock<Option<Py<PyBytes>>>,
930}
931
932impl PyOcspResponse {
933 fn ocsp(&self) -> PyResult<&synta_certificate::ocsp::OCSPResponse<'static>> {
934 if let Some(v) = self.inner.get() {
935 return Ok(v.as_ref());
936 }
937 let mut decoder = Decoder::new(self.raw, Encoding::Der);
938 let decoded = decoder.decode().map_err(|e| {
939 pyo3::exceptions::PyValueError::new_err(format!("OCSPResponse DER decode failed: {e}"))
940 })?;
941 let _ = self.inner.set(Box::new(decoded));
942 Ok(self.inner.get().unwrap().as_ref())
943 }
944}
945
946#[pymethods]
947impl PyOcspResponse {
948 #[staticmethod]
950 fn from_der(py: Python<'_>, data: Bound<'_, PyBytes>) -> PyResult<Self> {
951 let py_bytes = data.unbind();
952 let raw: &'static [u8] = unsafe {
966 let s = py_bytes.bind(py).as_bytes();
967 std::slice::from_raw_parts(s.as_ptr(), s.len())
968 };
969 {
970 let mut d = Decoder::new(raw, Encoding::Der);
971 d.read_tag()
972 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
973 d.read_length()
974 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
975 }
976 Ok(Self {
977 _data: py_bytes,
978 raw,
979 inner: OnceLock::new(),
980 status_cache: OnceLock::new(),
981 response_type_oid_cache: OnceLock::new(),
982 response_bytes_cache: OnceLock::new(),
983 })
984 }
985
986 #[staticmethod]
997 fn from_pem<'py>(py: Python<'py>, data: Bound<'_, PyBytes>) -> PyResult<Bound<'py, PyAny>> {
998 pem_blocks_to_pyobject(py, data.as_bytes(), |py, bytes| {
999 let obj = Self::from_der(py, bytes)?;
1000 Ok(Py::new(py, obj)?.into_bound(py).into_any())
1001 })
1002 }
1003
1004 #[staticmethod]
1011 fn to_pem<'py>(
1012 py: Python<'py>,
1013 obj_or_list: Bound<'_, PyAny>,
1014 ) -> PyResult<Bound<'py, PyBytes>> {
1015 pyobject_to_pem::<Self, _>(py, "OCSP RESPONSE", &obj_or_list, |c| c.raw)
1016 }
1017
1018 #[getter]
1020 fn status<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyString>> {
1021 if let Some(cached) = self.status_cache.get() {
1022 return Ok(cached.clone_ref(py).into_bound(py));
1023 }
1024 let s = ocsp_status_str(self.ocsp()?.response_status);
1025 let py_str = PyString::new(py, s).unbind();
1026 let _ = self.status_cache.set(py_str.clone_ref(py));
1027 Ok(py_str.into_bound(py))
1028 }
1029
1030 #[getter]
1036 fn response_type_oid<'py>(
1037 &self,
1038 py: Python<'py>,
1039 ) -> PyResult<Option<Bound<'py, PyObjectIdentifier>>> {
1040 if let Some(cached) = self.response_type_oid_cache.get() {
1041 return Ok(cached.as_ref().map(|o| o.clone_ref(py).into_bound(py)));
1042 }
1043 let computed: Option<Py<PyObjectIdentifier>> = self
1044 .ocsp()?
1045 .response_bytes
1046 .as_ref()
1047 .map(|rb| Py::new(py, PyObjectIdentifier::from_oid(rb.response_type.clone())))
1048 .transpose()?;
1049 let _ = self
1050 .response_type_oid_cache
1051 .set(computed.as_ref().map(|o| o.clone_ref(py)));
1052 Ok(computed.map(|o| o.into_bound(py)))
1053 }
1054
1055 #[getter]
1061 fn response_bytes<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
1062 if let Some(cached) = self.response_bytes_cache.get() {
1063 return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
1064 }
1065 let computed: Option<Bound<'py, PyBytes>> = self
1066 .ocsp()?
1067 .response_bytes
1068 .as_ref()
1069 .map(|rb| PyBytes::new(py, rb.response.as_bytes()));
1070 let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
1071 let _ = self.response_bytes_cache.set(to_store);
1072 Ok(computed)
1073 }
1074
1075 fn to_der<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
1077 self._data.clone_ref(py).into_bound(py)
1078 }
1079
1080 fn verify_signature(&self, responder: &super::cert::PyCertificate) -> PyResult<()> {
1097 use synta_certificate::{default_signature_verifier, SignatureVerifier};
1098
1099 let ocsp = self.ocsp()?;
1100
1101 let rb = ocsp.response_bytes.as_ref().ok_or_else(|| {
1103 pyo3::exceptions::PyValueError::new_err(
1104 "OCSP response carries no responseBytes (error status response)",
1105 )
1106 })?;
1107
1108 use synta_certificate::oids::ID_PKIX_OCSP_BASIC;
1110 if rb.response_type.components() != ID_PKIX_OCSP_BASIC {
1111 return Err(pyo3::exceptions::PyValueError::new_err(format!(
1112 "unsupported OCSP responseType: {}",
1113 rb.response_type,
1114 )));
1115 }
1116
1117 let basic_der = rb.response.as_bytes();
1119 let basic: synta_certificate::ocsp::BasicOCSPResponse<'_> = {
1120 let mut dec = Decoder::new(basic_der, Encoding::Der);
1121 dec.decode().map_err(|e| {
1122 pyo3::exceptions::PyValueError::new_err(format!(
1123 "BasicOCSPResponse decode failed: {e}"
1124 ))
1125 })?
1126 };
1127
1128 let tbs_der = basic.tbs_response_data.to_der().map_err(|e| {
1130 pyo3::exceptions::PyValueError::new_err(format!("ResponseData encode: {e}"))
1131 })?;
1132
1133 let sig_alg_der = basic.signature_algorithm.to_der().map_err(|e| {
1135 pyo3::exceptions::PyValueError::new_err(format!("OCSP alg encode: {e}"))
1136 })?;
1137
1138 let issuer_cert = responder.cert()?;
1140 let spki_der = issuer_cert
1141 .tbs_certificate
1142 .subject_public_key_info
1143 .to_der()
1144 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("SPKI encode: {e}")))?;
1145
1146 default_signature_verifier()
1148 .verify_certificate_signature(
1149 &tbs_der,
1150 &sig_alg_der,
1151 basic.signature.as_bytes(),
1152 &spki_der,
1153 )
1154 .map_err(|e| {
1155 pyo3::exceptions::PyValueError::new_err(format!("OCSP signature invalid: {e}"))
1156 })
1157 }
1158
1159 fn __repr__(&self) -> PyResult<String> {
1160 Ok(format!(
1161 "OCSPResponse(status={})",
1162 ocsp_status_str(self.ocsp()?.response_status),
1163 ))
1164 }
1165}
1166
1167type OcspRequest2024 = synta_certificate::ocsp_2024_88_types::OCSPRequest<'static>;
1170
1171#[pyclass(frozen, name = "CertID")]
1173pub struct PyCertID {
1174 hash_alg_oid: synta::ObjectIdentifier,
1175 issuer_name_hash: Vec<u8>,
1176 issuer_key_hash: Vec<u8>,
1177 serial: synta::Integer,
1178 hash_algorithm_oid_cache: OnceLock<Py<PyObjectIdentifier>>,
1179 serial_number_cache: OnceLock<Py<PyAny>>,
1180}
1181
1182impl PyCertID {
1183 fn from_cert_id(c: &synta_certificate::ocsp_2024_88_types::CertID<'_>) -> Self {
1184 Self {
1185 hash_alg_oid: c.hash_algorithm.algorithm.clone(),
1186 issuer_name_hash: c.issuer_name_hash.as_bytes().to_vec(),
1187 issuer_key_hash: c.issuer_key_hash.as_bytes().to_vec(),
1188 serial: c.serial_number.clone(),
1189 hash_algorithm_oid_cache: OnceLock::new(),
1190 serial_number_cache: OnceLock::new(),
1191 }
1192 }
1193}
1194
1195#[pymethods]
1196impl PyCertID {
1197 #[getter]
1199 fn hash_algorithm_oid<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyObjectIdentifier>> {
1200 if let Some(cached) = self.hash_algorithm_oid_cache.get() {
1201 return Ok(cached.clone_ref(py).into_bound(py));
1202 }
1203 let oid = Py::new(py, PyObjectIdentifier::from_oid(self.hash_alg_oid.clone()))?;
1204 let _ = self.hash_algorithm_oid_cache.set(oid.clone_ref(py));
1205 Ok(oid.into_bound(py))
1206 }
1207
1208 #[getter]
1210 fn issuer_name_hash<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
1211 PyBytes::new(py, &self.issuer_name_hash)
1212 }
1213
1214 #[getter]
1216 fn issuer_key_hash<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
1217 PyBytes::new(py, &self.issuer_key_hash)
1218 }
1219
1220 #[getter]
1222 fn serial_number<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
1223 if let Some(cached) = self.serial_number_cache.get() {
1224 return Ok(cached.clone_ref(py).into_bound(py));
1225 }
1226 let py_int: Py<PyAny> = if let Ok(v) = self.serial.as_i64() {
1227 v.into_pyobject(py)?.into_any().unbind()
1228 } else if let Ok(v) = self.serial.as_i128() {
1229 v.into_pyobject(py)?.into_any().unbind()
1230 } else {
1231 let bytes_obj = PyBytes::new(py, self.serial.as_bytes());
1232 let kwargs = pyo3::types::PyDict::new(py);
1233 kwargs.set_item(pyo3::intern!(py, "signed"), true)?;
1234 py.get_type::<pyo3::types::PyInt>()
1235 .call_method(
1236 pyo3::intern!(py, "from_bytes"),
1237 (bytes_obj, pyo3::intern!(py, "big")),
1238 Some(&kwargs),
1239 )
1240 .map(|r| r.unbind())?
1241 };
1242 let _ = self.serial_number_cache.set(py_int.clone_ref(py));
1243 Ok(py_int.into_bound(py))
1244 }
1245
1246 fn __repr__(&self) -> String {
1247 format!(
1248 "CertID(hash_alg={}, serial=<{} bytes>)",
1249 self.hash_alg_oid,
1250 self.serial.as_bytes().len(),
1251 )
1252 }
1253}
1254
1255#[pyclass(frozen, name = "OCSPRequest")]
1263pub struct PyOcspRequest {
1264 pub(super) _data: Py<PyBytes>,
1265 pub(super) raw: &'static [u8],
1266 inner: OnceLock<Box<OcspRequest2024>>,
1267 request_list_cache: OnceLock<Py<PyList>>,
1268 requestor_name_cache: OnceLock<Option<Py<PyBytes>>>,
1269 request_extensions_cache: OnceLock<Option<Py<PyBytes>>>,
1270}
1271
1272impl PyOcspRequest {
1273 fn req(&self) -> PyResult<&OcspRequest2024> {
1274 if let Some(v) = self.inner.get() {
1275 return Ok(v.as_ref());
1276 }
1277 let mut decoder = Decoder::new(self.raw, Encoding::Der);
1278 let decoded = decoder.decode::<OcspRequest2024>().map_err(|e| {
1279 pyo3::exceptions::PyValueError::new_err(format!("OCSPRequest DER decode failed: {e}"))
1280 })?;
1281 let _ = self.inner.set(Box::new(decoded));
1282 Ok(self.inner.get().unwrap().as_ref())
1283 }
1284}
1285
1286#[pymethods]
1287impl PyOcspRequest {
1288 #[staticmethod]
1290 fn from_der(py: Python<'_>, data: Bound<'_, PyBytes>) -> PyResult<Self> {
1291 let py_bytes = data.unbind();
1292 let raw: &'static [u8] = unsafe {
1306 let s = py_bytes.bind(py).as_bytes();
1307 std::slice::from_raw_parts(s.as_ptr(), s.len())
1308 };
1309 {
1310 let mut d = Decoder::new(raw, Encoding::Der);
1311 d.read_tag()
1312 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
1313 d.read_length()
1314 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
1315 }
1316 Ok(Self {
1317 _data: py_bytes,
1318 raw,
1319 inner: OnceLock::new(),
1320 request_list_cache: OnceLock::new(),
1321 requestor_name_cache: OnceLock::new(),
1322 request_extensions_cache: OnceLock::new(),
1323 })
1324 }
1325
1326 #[staticmethod]
1328 fn from_pem<'py>(py: Python<'py>, data: Bound<'_, PyBytes>) -> PyResult<Bound<'py, PyAny>> {
1329 pem_blocks_to_pyobject(py, data.as_bytes(), |py, bytes| {
1330 let obj = Self::from_der(py, bytes)?;
1331 Ok(Py::new(py, obj)?.into_bound(py).into_any())
1332 })
1333 }
1334
1335 #[staticmethod]
1337 fn to_pem<'py>(
1338 py: Python<'py>,
1339 obj_or_list: Bound<'_, PyAny>,
1340 ) -> PyResult<Bound<'py, PyBytes>> {
1341 pyobject_to_pem::<Self, _>(py, "OCSP REQUEST", &obj_or_list, |c| c.raw)
1342 }
1343
1344 fn to_der<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
1346 PyBytes::new(py, self.raw)
1347 }
1348
1349 #[getter]
1351 fn request_list<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyList>> {
1352 if let Some(cached) = self.request_list_cache.get() {
1353 return Ok(cached.clone_ref(py).into_bound(py));
1354 }
1355 let req = self.req()?;
1356 let list = PyList::empty(py);
1357 for request in &req.tbs_request.request_list {
1358 let cert_id = PyCertID::from_cert_id(&request.req_cert);
1359 list.append(Py::new(py, cert_id)?)?;
1360 }
1361 let py_list = list.unbind();
1362 let _ = self.request_list_cache.set(py_list.clone_ref(py));
1363 Ok(py_list.into_bound(py))
1364 }
1365
1366 #[getter]
1368 fn requestor_name<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
1369 if let Some(cached) = self.requestor_name_cache.get() {
1370 return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
1371 }
1372 let computed: Option<Bound<'py, PyBytes>> = self
1373 .req()?
1374 .tbs_request
1375 .requestor_name
1376 .as_ref()
1377 .map(|gn| -> PyResult<Bound<'py, PyBytes>> {
1378 let mut enc = synta::Encoder::new(synta::Encoding::Der);
1379 gn.encode(&mut enc)
1380 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
1381 let der = enc
1382 .finish()
1383 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
1384 Ok(PyBytes::new(py, &der))
1385 })
1386 .transpose()?;
1387 let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
1388 let _ = self.requestor_name_cache.set(to_store);
1389 Ok(computed)
1390 }
1391
1392 #[getter]
1394 fn request_extensions<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
1395 if let Some(cached) = self.request_extensions_cache.get() {
1396 return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
1397 }
1398 let computed: Option<Bound<'py, PyBytes>> = self
1399 .req()?
1400 .tbs_request
1401 .request_extensions
1402 .as_ref()
1403 .map(|exts| -> PyResult<Bound<'py, PyBytes>> {
1404 let mut enc = synta::Encoder::new(synta::Encoding::Der);
1405 exts.encode(&mut enc)
1406 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
1407 let der = enc
1408 .finish()
1409 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
1410 Ok(PyBytes::new(py, &der))
1411 })
1412 .transpose()?;
1413 let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
1414 let _ = self.request_extensions_cache.set(to_store);
1415 Ok(computed)
1416 }
1417
1418 fn __repr__(&self) -> PyResult<String> {
1419 Ok(format!(
1420 "OCSPRequest(requests={})",
1421 self.req()?.tbs_request.request_list.len(),
1422 ))
1423 }
1424}
1425
1426fn der_certs_to_pylist<'py>(
1430 py: Python<'py>,
1431 der_certs: Vec<Vec<u8>>,
1432) -> PyResult<Bound<'py, PyList>> {
1433 let list = PyList::empty(py);
1434 for der in der_certs {
1435 let py_bytes = PyBytes::new(py, &der);
1436 let cert = PyCertificate::new_from_der(py, py_bytes)?;
1437 list.append(Py::new(py, cert)?.into_bound(py))?;
1438 }
1439 Ok(list)
1440}
1441
1442#[pyfunction]
1458pub fn load_der_pkcs7_certificates<'py>(
1459 py: Python<'py>,
1460 data: &[u8],
1461) -> PyResult<Bound<'py, PyList>> {
1462 let der_certs = synta_certificate::certs_from_pkcs7(data)
1463 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
1464 der_certs_to_pylist(py, der_certs)
1465}
1466
1467#[pyfunction]
1484pub fn load_pem_pkcs7_certificates<'py>(
1485 py: Python<'py>,
1486 data: &[u8],
1487) -> PyResult<Bound<'py, PyList>> {
1488 let blocks = synta_certificate::pem_blocks(data);
1489 if blocks.is_empty() {
1490 return Err(pyo3::exceptions::PyValueError::new_err(
1491 "no PEM block found in input",
1492 ));
1493 }
1494 let mut all_certs: Vec<Vec<u8>> = Vec::new();
1495 for (_, der) in &blocks {
1496 let certs = synta_certificate::certs_from_pkcs7(der)
1497 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
1498 all_certs.extend(certs);
1499 }
1500 der_certs_to_pylist(py, all_certs)
1501}
1502
1503#[pyfunction]
1523#[pyo3(signature = (data, password = None))]
1524pub fn load_pkcs12_certificates<'py>(
1525 py: Python<'py>,
1526 data: &[u8],
1527 password: Option<&[u8]>,
1528) -> PyResult<Bound<'py, PyList>> {
1529 let pw = password.unwrap_or(b"");
1530 let der_certs =
1531 synta_certificate::certs_from_pkcs12(data, pw, &synta_certificate::DefaultCrypto)
1532 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
1533 der_certs_to_pylist(py, der_certs)
1534}
1535
1536#[pyfunction]
1555#[pyo3(signature = (data, password = None))]
1556pub fn load_pkcs12_keys<'py>(
1557 py: Python<'py>,
1558 data: &[u8],
1559 password: Option<&[u8]>,
1560) -> PyResult<Bound<'py, PyList>> {
1561 let pw = password.unwrap_or(b"");
1562 let der_keys = synta_certificate::keys_from_pkcs12(data, pw, &synta_certificate::DefaultCrypto)
1563 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
1564 let list = PyList::empty(py);
1565 for der in der_keys {
1566 list.append(PyBytes::new(py, &der))?;
1567 }
1568 Ok(list)
1569}
1570
1571#[pyfunction]
1595#[pyo3(signature = (data, password = None))]
1596pub fn load_pkcs12<'py>(
1597 py: Python<'py>,
1598 data: &[u8],
1599 password: Option<&[u8]>,
1600) -> PyResult<Bound<'py, pyo3::types::PyTuple>> {
1601 let pw = password.unwrap_or(b"");
1602 let pki = synta_certificate::pki_from_pkcs12(data, pw, &synta_certificate::DefaultCrypto)
1603 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
1604 let certs = der_certs_to_pylist(py, pki.certs)?;
1605 let keys = PyList::empty(py);
1606 for der in pki.keys {
1607 keys.append(PyBytes::new(py, &der))?;
1608 }
1609 pyo3::types::PyTuple::new(py, [certs.into_any(), keys.into_any()])
1610}
1611
1612#[pyfunction]
1660#[pyo3(signature = (data, password = None))]
1661pub fn read_pki_blocks<'py>(
1662 py: Python<'py>,
1663 data: &[u8],
1664 password: Option<&[u8]>,
1665) -> PyResult<Bound<'py, PyList>> {
1666 let pw = password.unwrap_or(b"");
1667 let blocks = if password.is_some() {
1668 synta_certificate::read_pki_blocks(
1669 data,
1670 pw,
1671 Some(&synta_certificate::DefaultCrypto as &dyn synta_certificate::PkiDecryptor),
1672 )
1673 } else {
1674 synta_certificate::read_pki_blocks(data, pw, None::<&dyn synta_certificate::PkiDecryptor>)
1675 };
1676 let blocks = blocks.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
1677 let list = PyList::empty(py);
1678 for (label, der) in blocks {
1679 let tuple = pyo3::types::PyTuple::new(
1680 py,
1681 [
1682 PyString::new(py, &label).into_any(),
1683 PyBytes::new(py, &der).into_any(),
1684 ],
1685 )?;
1686 list.append(tuple)?;
1687 }
1688 Ok(list)
1689}
1690
1691#[pyfunction]
1710pub fn encode_extended_key_usage<'py>(
1711 py: Python<'py>,
1712 oids: &Bound<'_, PyList>,
1713) -> PyResult<Bound<'py, PyBytes>> {
1714 use synta::traits::Encode;
1715 let mut eku: Vec<synta::ObjectIdentifier> = Vec::with_capacity(oids.len());
1716 for item in oids.iter() {
1717 eku.push(super::oid_from_pyany(&item)?);
1718 }
1719 let mut enc = synta::Encoder::new(synta::Encoding::Der);
1720 eku.encode(&mut enc)
1721 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
1722 let der = enc
1723 .finish()
1724 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
1725 Ok(PyBytes::new(py, &der))
1726}
1727
1728#[pyfunction]
1751pub fn encode_subject_alt_names<'py>(
1752 py: Python<'py>,
1753 names: &Bound<'_, PyList>,
1754) -> PyResult<Bound<'py, PyBytes>> {
1755 let mut owned: Vec<(u32, Vec<u8>)> = Vec::with_capacity(names.len());
1758 for item in names.iter() {
1759 let tuple = item.cast::<pyo3::types::PyTuple>().map_err(|_| {
1760 pyo3::exceptions::PyTypeError::new_err(
1761 "each element must be a (tag_number, bytes) 2-tuple",
1762 )
1763 })?;
1764 if tuple.len() != 2 {
1765 return Err(pyo3::exceptions::PyTypeError::new_err(
1766 "each element must be a (tag_number, bytes) 2-tuple",
1767 ));
1768 }
1769 let tag_num: u32 = tuple.get_item(0)?.extract()?;
1770 let content: Vec<u8> = tuple.get_item(1)?.extract()?;
1771 owned.push((tag_num, content));
1772 }
1773 let entries: Vec<(u32, &[u8])> = owned.iter().map(|(t, v)| (*t, v.as_slice())).collect();
1774 let der = synta_certificate::encode_general_names(&entries).ok_or_else(|| {
1775 pyo3::exceptions::PyValueError::new_err(
1776 "failed to encode GeneralName entries (invalid content bytes or unsupported tag)",
1777 )
1778 })?;
1779 Ok(PyBytes::new(py, &der))
1780}
1781
1782#[pyfunction]
1795pub fn name_der_equal(a: &[u8], b: &[u8]) -> bool {
1796 a == b
1797}
1798
1799#[pyfunction]
1847#[pyo3(signature = (certificates, private_key = None, password = None, cipher = None, mac_algorithm = None))]
1848pub fn create_pkcs12<'py>(
1849 py: Python<'py>,
1850 certificates: &Bound<'_, PyList>,
1851 private_key: Option<Bound<'_, PyAny>>,
1852 password: Option<&[u8]>,
1853 cipher: Option<&str>,
1854 mac_algorithm: Option<&str>,
1855) -> PyResult<Bound<'py, PyBytes>> {
1856 use pyo3::exceptions::PyValueError;
1857 use synta_certificate::{
1858 OpensslPkcs12Encryptor, Pkcs12Builder, Pkcs12Cipher, Pkcs12Config, Pkcs12HmacAlgorithm,
1859 };
1860
1861 let pkcs12_cipher = match cipher.unwrap_or("aes256") {
1862 "aes128" => Pkcs12Cipher::Aes128Cbc,
1863 "aes256" => Pkcs12Cipher::Aes256Cbc,
1864 "3des" => {
1865 return Err(PyValueError::new_err(
1866 "cipher '3des' is not supported; use 'aes128' or 'aes256'",
1867 ))
1868 }
1869 other => {
1870 return Err(PyValueError::new_err(format!(
1871 "unknown cipher: {other}; use 'aes128' or 'aes256'"
1872 )))
1873 }
1874 };
1875 let pkcs12_mac = match mac_algorithm.unwrap_or("sha256") {
1876 "sha256" => Pkcs12HmacAlgorithm::Sha256,
1877 "sha384" => Pkcs12HmacAlgorithm::Sha384,
1878 "sha512" => Pkcs12HmacAlgorithm::Sha512,
1879 "sha1" => {
1880 return Err(PyValueError::new_err(
1881 "mac_algorithm 'sha1' is not supported; use 'sha256', 'sha384', or 'sha512'",
1882 ))
1883 }
1884 other => {
1885 return Err(PyValueError::new_err(format!(
1886 "unknown mac_algorithm: {other}; use 'sha256', 'sha384', or 'sha512'"
1887 )))
1888 }
1889 };
1890 let config = Pkcs12Config {
1891 cipher: pkcs12_cipher,
1892 encryption_prf: pkcs12_mac,
1893 mac_algorithm: pkcs12_mac,
1894 ..Pkcs12Config::default()
1895 };
1896 let encryptor = OpensslPkcs12Encryptor::with_config(config);
1897
1898 let password = password.unwrap_or(b"");
1899 let mut builder = Pkcs12Builder::new();
1900 for item in certificates.iter() {
1901 if let Ok(py_cert) = item.cast::<PyCertificate>() {
1902 builder = builder.certificate(py_cert.get().raw);
1906 } else if let Ok(py_bytes) = item.cast::<PyBytes>() {
1907 builder = builder.certificate(py_bytes.as_bytes());
1908 } else {
1909 return Err(pyo3::exceptions::PyTypeError::new_err(
1910 "certificates must be synta.Certificate or bytes",
1911 ));
1912 }
1913 }
1914 if let Some(key_arg) = private_key {
1915 if let Ok(py_key) = key_arg.cast::<PyPrivateKey>() {
1916 let key_der = py_key
1918 .get()
1919 .inner
1920 .to_der()
1921 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
1922 builder = builder.private_key(&key_der);
1923 } else if let Ok(py_bytes) = key_arg.cast::<PyBytes>() {
1924 builder = builder.private_key(py_bytes.as_bytes());
1925 } else {
1926 return Err(pyo3::exceptions::PyTypeError::new_err(
1927 "private_key must be synta.PrivateKey or bytes",
1928 ));
1929 }
1930 }
1931
1932 let pfx_der = builder
1933 .build(password, &encryptor)
1934 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
1935
1936 Ok(PyBytes::new(py, &pfx_der))
1937}
1938
1939fn py_dt_to_gen_time_str(dt: &Bound<'_, PyAny>) -> PyResult<String> {
1948 if dt.getattr("tzinfo")?.is_none() {
1949 return Err(pyo3::exceptions::PyValueError::new_err(
1950 "datetime must be timezone-aware (e.g. datetime.timezone.utc)",
1951 ));
1952 }
1953 let ts: f64 = dt.call_method0("timestamp")?.extract()?;
1954 let secs = ts.floor() as i64;
1955 let gt = synta::GeneralizedTime::from_unix(secs).ok_or_else(|| {
1956 pyo3::exceptions::PyValueError::new_err("datetime out of valid ASN.1 year range 1–9999")
1957 })?;
1958 Ok(gt.to_string())
1959}
1960
1961#[pyclass(name = "CertificateListBuilder")]
1987pub struct PyCertificateListBuilder {
1988 inner: synta_certificate::CertificateListBuilder,
1989}
1990
1991#[pymethods]
1992impl PyCertificateListBuilder {
1993 #[new]
1995 fn new() -> Self {
1996 Self {
1997 inner: synta_certificate::CertificateListBuilder::new(),
1998 }
1999 }
2000
2001 fn issuer<'py>(slf: Bound<'py, Self>, name_der: &[u8]) -> Bound<'py, Self> {
2003 let mut guard = slf.borrow_mut();
2004 let old = std::mem::replace(
2005 &mut guard.inner,
2006 synta_certificate::CertificateListBuilder::new(),
2007 );
2008 guard.inner = old.issuer(name_der);
2009 drop(guard);
2010 slf
2011 }
2012
2013 fn this_update<'py>(slf: Bound<'py, Self>, time: &str) -> Bound<'py, Self> {
2015 let mut guard = slf.borrow_mut();
2016 let old = std::mem::replace(
2017 &mut guard.inner,
2018 synta_certificate::CertificateListBuilder::new(),
2019 );
2020 guard.inner = old.this_update(time);
2021 drop(guard);
2022 slf
2023 }
2024
2025 fn next_update<'py>(slf: Bound<'py, Self>, time: &str) -> Bound<'py, Self> {
2027 let mut guard = slf.borrow_mut();
2028 let old = std::mem::replace(
2029 &mut guard.inner,
2030 synta_certificate::CertificateListBuilder::new(),
2031 );
2032 guard.inner = old.next_update(time);
2033 drop(guard);
2034 slf
2035 }
2036
2037 fn revoke<'py>(
2044 slf: Bound<'py, Self>,
2045 serial: &[u8],
2046 revocation_date: &str,
2047 reason: Option<u8>,
2048 ) -> Bound<'py, Self> {
2049 let mut guard = slf.borrow_mut();
2050 let old = std::mem::replace(
2051 &mut guard.inner,
2052 synta_certificate::CertificateListBuilder::new(),
2053 );
2054 guard.inner = old.revoke(serial, revocation_date, reason);
2055 drop(guard);
2056 slf
2057 }
2058
2059 fn this_update_utc<'py>(
2067 slf: Bound<'py, Self>,
2068 dt: &Bound<'_, PyAny>,
2069 ) -> PyResult<Bound<'py, Self>> {
2070 let time_str = py_dt_to_gen_time_str(dt)?;
2071 let mut guard = slf.borrow_mut();
2072 let old = std::mem::replace(
2073 &mut guard.inner,
2074 synta_certificate::CertificateListBuilder::new(),
2075 );
2076 guard.inner = old.this_update(&time_str);
2077 drop(guard);
2078 Ok(slf)
2079 }
2080
2081 fn next_update_utc<'py>(
2086 slf: Bound<'py, Self>,
2087 dt: &Bound<'_, PyAny>,
2088 ) -> PyResult<Bound<'py, Self>> {
2089 let time_str = py_dt_to_gen_time_str(dt)?;
2090 let mut guard = slf.borrow_mut();
2091 let old = std::mem::replace(
2092 &mut guard.inner,
2093 synta_certificate::CertificateListBuilder::new(),
2094 );
2095 guard.inner = old.next_update(&time_str);
2096 drop(guard);
2097 Ok(slf)
2098 }
2099
2100 fn revoke_utc<'py>(
2109 slf: Bound<'py, Self>,
2110 serial: &[u8],
2111 revocation_dt: &Bound<'_, PyAny>,
2112 reason: Option<u8>,
2113 ) -> PyResult<Bound<'py, Self>> {
2114 let time_str = py_dt_to_gen_time_str(revocation_dt)?;
2115 let mut guard = slf.borrow_mut();
2116 let old = std::mem::replace(
2117 &mut guard.inner,
2118 synta_certificate::CertificateListBuilder::new(),
2119 );
2120 guard.inner = old.revoke(serial, &time_str, reason);
2121 drop(guard);
2122 Ok(slf)
2123 }
2124
2125 fn signature_algorithm<'py>(slf: Bound<'py, Self>, alg_der: &[u8]) -> Bound<'py, Self> {
2129 let mut guard = slf.borrow_mut();
2130 let old = std::mem::replace(
2131 &mut guard.inner,
2132 synta_certificate::CertificateListBuilder::new(),
2133 );
2134 guard.inner = old.signature_algorithm(alg_der);
2135 drop(guard);
2136 slf
2137 }
2138
2139 fn add_extension<'py>(
2148 slf: Bound<'py, Self>,
2149 oid: Bound<'_, pyo3::PyAny>,
2150 critical: bool,
2151 value_der: &[u8],
2152 ) -> PyResult<Bound<'py, Self>> {
2153 use std::str::FromStr;
2154 let parsed_oid: synta::ObjectIdentifier = if let Ok(s) = oid.extract::<String>() {
2155 synta::ObjectIdentifier::from_str(&s)
2156 .map_err(|_| pyo3::exceptions::PyValueError::new_err(format!("invalid OID: {s}")))?
2157 } else if let Ok(o) = oid.extract::<pyo3::PyRef<'_, PyObjectIdentifier>>() {
2158 o.inner.clone()
2159 } else {
2160 return Err(pyo3::exceptions::PyTypeError::new_err(
2161 "oid must be a str or ObjectIdentifier",
2162 ));
2163 };
2164 let mut guard = slf.borrow_mut();
2165 let old = std::mem::replace(
2166 &mut guard.inner,
2167 synta_certificate::CertificateListBuilder::new(),
2168 );
2169 guard.inner = old.add_crl_extension(parsed_oid.components(), critical, value_der);
2170 drop(guard);
2171 Ok(slf)
2172 }
2173
2174 fn build<'py>(&mut self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
2180 let inner = std::mem::replace(
2181 &mut self.inner,
2182 synta_certificate::CertificateListBuilder::new(),
2183 );
2184 let der = inner
2185 .build()
2186 .map_err(pyo3::exceptions::PyValueError::new_err)?;
2187 Ok(PyBytes::new(py, &der))
2188 }
2189
2190 #[staticmethod]
2199 fn assemble<'py>(
2200 py: Python<'py>,
2201 tbs_der: &[u8],
2202 sig_alg_der: &[u8],
2203 signature: &[u8],
2204 ) -> PyResult<Bound<'py, PyBytes>> {
2205 let der =
2206 synta_certificate::CertificateListBuilder::assemble(tbs_der, sig_alg_der, signature)
2207 .map_err(pyo3::exceptions::PyValueError::new_err)?;
2208 Ok(PyBytes::new(py, &der))
2209 }
2210
2211 fn __repr__(&self) -> String {
2212 "CertificateListBuilder()".to_string()
2213 }
2214}
2215
2216#[pyclass(frozen, name = "OCSPSingleResponse")]
2234pub struct PyOCSPSingleResponse {
2235 pub(crate) hash_algorithm_der: Vec<u8>,
2236 pub(crate) issuer_name_hash: Vec<u8>,
2237 pub(crate) issuer_key_hash: Vec<u8>,
2238 pub(crate) serial: Vec<u8>,
2239 pub(crate) status: u8,
2240 pub(crate) this_update: String,
2241 pub(crate) next_update: Option<String>,
2242}
2243
2244#[pymethods]
2245impl PyOCSPSingleResponse {
2246 #[new]
2257 #[pyo3(signature = (hash_algorithm_der, issuer_name_hash, issuer_key_hash, serial, status, this_update, next_update=None))]
2258 fn new(
2259 hash_algorithm_der: &[u8],
2260 issuer_name_hash: &[u8],
2261 issuer_key_hash: &[u8],
2262 serial: &[u8],
2263 status: u8,
2264 this_update: &str,
2265 next_update: Option<&str>,
2266 ) -> Self {
2267 Self {
2268 hash_algorithm_der: hash_algorithm_der.to_vec(),
2269 issuer_name_hash: issuer_name_hash.to_vec(),
2270 issuer_key_hash: issuer_key_hash.to_vec(),
2271 serial: serial.to_vec(),
2272 status,
2273 this_update: this_update.to_owned(),
2274 next_update: next_update.map(str::to_owned),
2275 }
2276 }
2277}
2278
2279#[pyclass(name = "OCSPResponseBuilder")]
2307pub struct PyOCSPResponseBuilder {
2308 inner: synta_certificate::OCSPResponseBuilder,
2309}
2310
2311#[pymethods]
2312impl PyOCSPResponseBuilder {
2313 #[new]
2315 fn new() -> Self {
2316 Self {
2317 inner: synta_certificate::OCSPResponseBuilder::new(),
2318 }
2319 }
2320
2321 fn responder_name<'py>(slf: Bound<'py, Self>, name_der: &[u8]) -> Bound<'py, Self> {
2326 let mut guard = slf.borrow_mut();
2327 let old = std::mem::replace(
2328 &mut guard.inner,
2329 synta_certificate::OCSPResponseBuilder::new(),
2330 );
2331 guard.inner = old.responder_name(name_der);
2332 drop(guard);
2333 slf
2334 }
2335
2336 fn responder_key_hash<'py>(slf: Bound<'py, Self>, key_hash: &[u8]) -> Bound<'py, Self> {
2343 let mut guard = slf.borrow_mut();
2344 let old = std::mem::replace(
2345 &mut guard.inner,
2346 synta_certificate::OCSPResponseBuilder::new(),
2347 );
2348 guard.inner = old.responder_key_hash(key_hash);
2349 drop(guard);
2350 slf
2351 }
2352
2353 fn produced_at<'py>(slf: Bound<'py, Self>, time: &str) -> Bound<'py, Self> {
2358 let mut guard = slf.borrow_mut();
2359 let old = std::mem::replace(
2360 &mut guard.inner,
2361 synta_certificate::OCSPResponseBuilder::new(),
2362 );
2363 guard.inner = old.produced_at(time);
2364 drop(guard);
2365 slf
2366 }
2367
2368 fn add_response<'py>(
2373 slf: Bound<'py, Self>,
2374 response: &PyOCSPSingleResponse,
2375 ) -> Bound<'py, Self> {
2376 let mut guard = slf.borrow_mut();
2377 let old = std::mem::replace(
2378 &mut guard.inner,
2379 synta_certificate::OCSPResponseBuilder::new(),
2380 );
2381 guard.inner = old.add_response(synta_certificate::SingleResponseSpec {
2382 hash_algorithm_der: &response.hash_algorithm_der,
2383 issuer_name_hash: &response.issuer_name_hash,
2384 issuer_key_hash: &response.issuer_key_hash,
2385 serial: &response.serial,
2386 status: response.status,
2387 this_update: &response.this_update,
2388 next_update: response.next_update.as_deref(),
2389 });
2390 drop(guard);
2391 slf
2392 }
2393
2394 fn build_tbs<'py>(&mut self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
2399 let inner = std::mem::replace(
2400 &mut self.inner,
2401 synta_certificate::OCSPResponseBuilder::new(),
2402 );
2403 let der = inner
2404 .build_tbs()
2405 .map_err(pyo3::exceptions::PyValueError::new_err)?;
2406 Ok(PyBytes::new(py, &der))
2407 }
2408
2409 #[staticmethod]
2416 fn assemble<'py>(
2417 py: Python<'py>,
2418 tbs_der: &[u8],
2419 sig_alg_der: &[u8],
2420 signature: &[u8],
2421 ) -> PyResult<Bound<'py, PyBytes>> {
2422 let der = synta_certificate::OCSPResponseBuilder::assemble(tbs_der, sig_alg_der, signature)
2423 .map_err(pyo3::exceptions::PyValueError::new_err)?;
2424 Ok(PyBytes::new(py, &der))
2425 }
2426
2427 fn __repr__(&self) -> String {
2428 "OCSPResponseBuilder()".to_string()
2429 }
2430}
2431
2432#[pyclass(frozen, name = "OCSPCertIDSpec")]
2447pub struct PyOCSPCertIDSpec {
2448 pub(crate) hash_algorithm_der: Vec<u8>,
2449 pub(crate) issuer_name_hash: Vec<u8>,
2450 pub(crate) issuer_key_hash: Vec<u8>,
2451 pub(crate) serial: Vec<u8>,
2452}
2453
2454#[pymethods]
2455impl PyOCSPCertIDSpec {
2456 #[new]
2464 fn new(
2465 hash_algorithm_der: &[u8],
2466 issuer_name_hash: &[u8],
2467 issuer_key_hash: &[u8],
2468 serial: &[u8],
2469 ) -> Self {
2470 Self {
2471 hash_algorithm_der: hash_algorithm_der.to_vec(),
2472 issuer_name_hash: issuer_name_hash.to_vec(),
2473 issuer_key_hash: issuer_key_hash.to_vec(),
2474 serial: serial.to_vec(),
2475 }
2476 }
2477}
2478
2479#[pyclass(name = "OCSPRequestBuilder")]
2508pub struct PyOCSPRequestBuilder {
2509 inner: synta_certificate::OCSPRequestBuilder,
2510 built: bool,
2511}
2512
2513#[pymethods]
2514impl PyOCSPRequestBuilder {
2515 #[new]
2517 fn new() -> Self {
2518 Self {
2519 inner: synta_certificate::OCSPRequestBuilder::new(),
2520 built: false,
2521 }
2522 }
2523
2524 fn requestor_name<'py>(slf: Bound<'py, Self>, name_der: &[u8]) -> Bound<'py, Self> {
2530 let mut guard = slf.borrow_mut();
2531 let old = std::mem::replace(
2532 &mut guard.inner,
2533 synta_certificate::OCSPRequestBuilder::new(),
2534 );
2535 guard.inner = old.requestor_name(name_der);
2536 drop(guard);
2537 slf
2538 }
2539
2540 fn add_request<'py>(slf: Bound<'py, Self>, spec: &PyOCSPCertIDSpec) -> Bound<'py, Self> {
2545 let mut guard = slf.borrow_mut();
2546 let old = std::mem::replace(
2547 &mut guard.inner,
2548 synta_certificate::OCSPRequestBuilder::new(),
2549 );
2550 guard.inner = old.add_request(synta_certificate::CertIDSpec {
2551 hash_algorithm_der: &spec.hash_algorithm_der,
2552 issuer_name_hash: &spec.issuer_name_hash,
2553 issuer_key_hash: &spec.issuer_key_hash,
2554 serial: &spec.serial,
2555 });
2556 drop(guard);
2557 slf
2558 }
2559
2560 fn build_tbs<'py>(&mut self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
2569 if self.built {
2570 return Err(pyo3::exceptions::PyValueError::new_err(
2571 "build_tbs already called on this OCSPRequestBuilder",
2572 ));
2573 }
2574 self.built = true;
2575 let inner = std::mem::replace(
2576 &mut self.inner,
2577 synta_certificate::OCSPRequestBuilder::new(),
2578 );
2579 let der = inner
2580 .build_tbs()
2581 .map_err(pyo3::exceptions::PyValueError::new_err)?;
2582 Ok(PyBytes::new(py, &der))
2583 }
2584
2585 fn build_tbs_inner<'py>(&mut self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
2594 if self.built {
2595 return Err(pyo3::exceptions::PyValueError::new_err(
2596 "build_tbs_inner already called on this OCSPRequestBuilder",
2597 ));
2598 }
2599 self.built = true;
2600 let inner = std::mem::replace(
2601 &mut self.inner,
2602 synta_certificate::OCSPRequestBuilder::new(),
2603 );
2604 let der = inner
2605 .build_tbs_inner()
2606 .map_err(pyo3::exceptions::PyValueError::new_err)?;
2607 Ok(PyBytes::new(py, &der))
2608 }
2609
2610 #[staticmethod]
2617 fn assemble<'py>(
2618 py: Python<'py>,
2619 tbs_der: &[u8],
2620 sig_alg_der: &[u8],
2621 signature: &[u8],
2622 ) -> PyResult<Bound<'py, PyBytes>> {
2623 let der = synta_certificate::OCSPRequestBuilder::assemble(tbs_der, sig_alg_der, signature)
2624 .map_err(pyo3::exceptions::PyValueError::new_err)?;
2625 Ok(PyBytes::new(py, &der))
2626 }
2627
2628 fn __repr__(&self) -> String {
2629 "OCSPRequestBuilder()".to_string()
2630 }
2631}