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}
73
74impl PyCsr {
75 fn csr(&self) -> PyResult<&synta_certificate::csr::CertificationRequest<'static>> {
76 if let Some(v) = self.inner.get() {
77 return Ok(v.as_ref());
78 }
79 let mut decoder = Decoder::new(self.raw, Encoding::Der);
80 let decoded = decoder.decode().map_err(|e| {
81 pyo3::exceptions::PyValueError::new_err(format!(
82 "CertificationRequest DER decode failed: {e}"
83 ))
84 })?;
85 let _ = self.inner.set(Box::new(decoded));
86 Ok(self.inner.get().unwrap().as_ref())
87 }
88}
89
90#[pymethods]
91impl PyCsr {
92 #[staticmethod]
94 fn from_der(py: Python<'_>, data: Bound<'_, PyBytes>) -> PyResult<Self> {
95 let py_bytes = data.unbind();
96 let raw: &'static [u8] = unsafe {
110 let s = py_bytes.bind(py).as_bytes();
111 std::slice::from_raw_parts(s.as_ptr(), s.len())
112 };
113 {
115 let mut d = Decoder::new(raw, Encoding::Der);
116 d.read_tag()
117 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
118 d.read_length()
119 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
120 }
121 Ok(Self {
122 _data: py_bytes,
123 raw,
124 inner: OnceLock::new(),
125 subject_cache: OnceLock::new(),
126 subject_raw_der_cache: OnceLock::new(),
127 signature_algorithm_cache: OnceLock::new(),
128 signature_algorithm_oid_cache: OnceLock::new(),
129 signature_cache: OnceLock::new(),
130 public_key_algorithm_cache: OnceLock::new(),
131 public_key_algorithm_oid_cache: OnceLock::new(),
132 public_key_cache: OnceLock::new(),
133 })
134 }
135
136 #[staticmethod]
147 fn from_pem<'py>(py: Python<'py>, data: Bound<'_, PyBytes>) -> PyResult<Bound<'py, PyAny>> {
148 pem_blocks_to_pyobject(py, data.as_bytes(), |py, bytes| {
149 let obj = Self::from_der(py, bytes)?;
150 Ok(Py::new(py, obj)?.into_bound(py).into_any())
151 })
152 }
153
154 #[staticmethod]
161 fn to_pem<'py>(
162 py: Python<'py>,
163 obj_or_list: Bound<'_, PyAny>,
164 ) -> PyResult<Bound<'py, PyBytes>> {
165 pyobject_to_pem::<Self, _>(py, "CERTIFICATE REQUEST", &obj_or_list, |c| c.raw)
166 }
167
168 #[getter]
170 fn subject<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyString>> {
171 if let Some(cached) = self.subject_cache.get() {
172 return Ok(cached.clone_ref(py).into_bound(py));
173 }
174 let s = name_to_dn_string(&self.csr()?.certification_request_info.subject);
175 let py_str = PyString::new(py, &s).unbind();
176 let _ = self.subject_cache.set(py_str.clone_ref(py));
177 Ok(py_str.into_bound(py))
178 }
179
180 #[getter]
182 fn subject_raw_der<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
183 if let Some(cached) = self.subject_raw_der_cache.get() {
184 return Ok(cached.clone_ref(py).into_bound(py));
185 }
186 let bytes = name_to_der_bytes(&self.csr()?.certification_request_info.subject);
187 let py_bytes = PyBytes::new(py, &bytes).unbind();
188 let _ = self.subject_raw_der_cache.set(py_bytes.clone_ref(py));
189 Ok(py_bytes.into_bound(py))
190 }
191
192 #[getter]
194 fn version(&self) -> PyResult<i64> {
195 Ok(self
196 .csr()?
197 .certification_request_info
198 .version
199 .as_i64()
200 .unwrap_or(0))
201 }
202
203 #[getter]
205 fn signature_algorithm<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyString>> {
206 if let Some(cached) = self.signature_algorithm_cache.get() {
207 return Ok(cached.clone_ref(py).into_bound(py));
208 }
209 let oid = &self.csr()?.signature_algorithm.algorithm;
210 let name = synta_certificate::identify_signature_algorithm(oid);
211 let s = if name != "Other" {
212 name.to_string()
213 } else {
214 oid.to_string()
215 };
216 let py_str = PyString::new(py, &s).unbind();
217 let _ = self.signature_algorithm_cache.set(py_str.clone_ref(py));
218 Ok(py_str.into_bound(py))
219 }
220
221 #[getter]
223 fn signature_algorithm_oid<'py>(
224 &self,
225 py: Python<'py>,
226 ) -> PyResult<Bound<'py, PyObjectIdentifier>> {
227 if let Some(cached) = self.signature_algorithm_oid_cache.get() {
228 return Ok(cached.clone_ref(py).into_bound(py));
229 }
230 let obj = Py::new(
231 py,
232 PyObjectIdentifier::from_oid(self.csr()?.signature_algorithm.algorithm.clone()),
233 )?;
234 let _ = self.signature_algorithm_oid_cache.set(obj.clone_ref(py));
235 Ok(obj.into_bound(py))
236 }
237
238 #[getter]
240 fn signature<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
241 if let Some(cached) = self.signature_cache.get() {
242 return Ok(cached.clone_ref(py).into_bound(py));
243 }
244 let py_bytes = PyBytes::new(py, self.csr()?.signature.as_bytes()).unbind();
245 let _ = self.signature_cache.set(py_bytes.clone_ref(py));
246 Ok(py_bytes.into_bound(py))
247 }
248
249 #[getter]
251 fn public_key_algorithm<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyString>> {
252 if let Some(cached) = self.public_key_algorithm_cache.get() {
253 return Ok(cached.clone_ref(py).into_bound(py));
254 }
255 let oid = &self
256 .csr()?
257 .certification_request_info
258 .subject_pkinfo
259 .algorithm
260 .algorithm;
261 let s = synta_certificate::identify_public_key_algorithm(oid)
262 .map(|s| s.to_string())
263 .unwrap_or_else(|| oid.to_string());
264 let py_str = PyString::new(py, &s).unbind();
265 let _ = self.public_key_algorithm_cache.set(py_str.clone_ref(py));
266 Ok(py_str.into_bound(py))
267 }
268
269 #[getter]
271 fn public_key_algorithm_oid<'py>(
272 &self,
273 py: Python<'py>,
274 ) -> PyResult<Bound<'py, PyObjectIdentifier>> {
275 if let Some(cached) = self.public_key_algorithm_oid_cache.get() {
276 return Ok(cached.clone_ref(py).into_bound(py));
277 }
278 let obj = Py::new(
279 py,
280 PyObjectIdentifier::from_oid(
281 self.csr()?
282 .certification_request_info
283 .subject_pkinfo
284 .algorithm
285 .algorithm
286 .clone(),
287 ),
288 )?;
289 let _ = self.public_key_algorithm_oid_cache.set(obj.clone_ref(py));
290 Ok(obj.into_bound(py))
291 }
292
293 #[getter]
295 fn public_key<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
296 if let Some(cached) = self.public_key_cache.get() {
297 return Ok(cached.clone_ref(py).into_bound(py));
298 }
299 let py_bytes = PyBytes::new(
300 py,
301 self.csr()?
302 .certification_request_info
303 .subject_pkinfo
304 .subject_public_key
305 .as_bytes(),
306 )
307 .unbind();
308 let _ = self.public_key_cache.set(py_bytes.clone_ref(py));
309 Ok(py_bytes.into_bound(py))
310 }
311
312 fn get_extension_value_der<'py>(
330 &self,
331 py: Python<'py>,
332 oid: &Bound<'_, PyAny>,
333 ) -> PyResult<Option<Bound<'py, PyBytes>>> {
334 let target = super::oid_from_pyany(oid)?;
335 let csr = self.csr()?;
336 let attrs = match csr.certification_request_info.attributes.as_ref() {
337 Some(a) => a,
338 None => return Ok(None),
339 };
340 for attr in attrs.elements() {
341 if attr.attr_type.components() != synta_certificate::oids::PKCS9_EXTENSION_REQUEST {
342 continue;
343 }
344 let Some(raw) = attr.attr_values.elements().first() else {
346 return Ok(None);
347 };
348 return Ok(synta_certificate::find_extension_value(
349 raw.as_bytes(),
350 target.components(),
351 )
352 .map(|v| PyBytes::new(py, v)));
353 }
354 Ok(None)
355 }
356
357 fn subject_alt_names<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyList>> {
372 use pyo3::types::{PyBytes, PyList, PyTuple};
373 let list = PyList::empty(py);
374 let csr = self.csr()?;
375 let attrs = match csr.certification_request_info.attributes.as_ref() {
376 Some(a) => a,
377 None => return Ok(list),
378 };
379 for attr in attrs.elements() {
380 if attr.attr_type.components() != synta_certificate::oids::PKCS9_EXTENSION_REQUEST {
381 continue;
382 }
383 let Some(raw) = attr.attr_values.elements().first() else {
384 return Ok(list);
385 };
386 if let Some(san_bytes) = synta_certificate::find_extension_value(
387 raw.as_bytes(),
388 synta_certificate::oids::SUBJECT_ALT_NAME,
389 ) {
390 for (tag_num, content) in synta_certificate::parse_general_names(san_bytes) {
391 let tuple = PyTuple::new(
392 py,
393 [
394 tag_num.into_pyobject(py)?.into_any(),
395 PyBytes::new(py, &content).into_any(),
396 ],
397 )?;
398 list.append(tuple)?;
399 }
400 }
401 break;
402 }
403 Ok(list)
404 }
405
406 fn to_der<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
408 self._data.clone_ref(py).into_bound(py)
409 }
410
411 fn __repr__(&self) -> PyResult<String> {
412 Ok(format!(
413 "CertificationRequest(subject={:?})",
414 name_to_dn_string(&self.csr()?.certification_request_info.subject),
415 ))
416 }
417}
418
419impl PyCsr {
420 pub(crate) fn new_from_der(py: Python<'_>, data: Bound<'_, PyBytes>) -> PyResult<Self> {
424 Self::from_der(py, data)
425 }
426}
427
428#[pyclass(frozen, name = "CertificateList")]
438pub struct PyCrl {
439 pub(super) _data: Py<PyBytes>,
440 pub(super) raw: &'static [u8],
441 inner: OnceLock<Box<synta_certificate::crl::CertificateList<'static>>>,
442 issuer_cache: OnceLock<Py<PyString>>,
443 issuer_raw_der_cache: OnceLock<Py<PyBytes>>,
444 this_update_cache: OnceLock<Py<PyString>>,
445 next_update_cache: OnceLock<Option<Py<PyString>>>,
446 signature_algorithm_cache: OnceLock<Py<PyString>>,
447 signature_algorithm_oid_cache: OnceLock<Py<PyObjectIdentifier>>,
448 signature_value_cache: OnceLock<Py<PyBytes>>,
449}
450
451impl PyCrl {
452 fn crl(&self) -> PyResult<&synta_certificate::crl::CertificateList<'static>> {
453 if let Some(v) = self.inner.get() {
454 return Ok(v.as_ref());
455 }
456 let mut decoder = Decoder::new(self.raw, Encoding::Der);
457 let decoded = decoder.decode().map_err(|e| {
458 pyo3::exceptions::PyValueError::new_err(format!(
459 "CertificateList DER decode failed: {e}"
460 ))
461 })?;
462 let _ = self.inner.set(Box::new(decoded));
463 Ok(self.inner.get().unwrap().as_ref())
464 }
465}
466
467#[pymethods]
468impl PyCrl {
469 #[staticmethod]
471 fn from_der(py: Python<'_>, data: Bound<'_, PyBytes>) -> PyResult<Self> {
472 let py_bytes = data.unbind();
473 let raw: &'static [u8] = unsafe {
487 let s = py_bytes.bind(py).as_bytes();
488 std::slice::from_raw_parts(s.as_ptr(), s.len())
489 };
490 {
491 let mut d = Decoder::new(raw, Encoding::Der);
492 d.read_tag()
493 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
494 d.read_length()
495 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
496 }
497 Ok(Self {
498 _data: py_bytes,
499 raw,
500 inner: OnceLock::new(),
501 issuer_cache: OnceLock::new(),
502 issuer_raw_der_cache: OnceLock::new(),
503 this_update_cache: OnceLock::new(),
504 next_update_cache: OnceLock::new(),
505 signature_algorithm_cache: OnceLock::new(),
506 signature_algorithm_oid_cache: OnceLock::new(),
507 signature_value_cache: OnceLock::new(),
508 })
509 }
510
511 #[staticmethod]
522 fn from_pem<'py>(py: Python<'py>, data: Bound<'_, PyBytes>) -> PyResult<Bound<'py, PyAny>> {
523 pem_blocks_to_pyobject(py, data.as_bytes(), |py, bytes| {
524 let obj = Self::from_der(py, bytes)?;
525 Ok(Py::new(py, obj)?.into_bound(py).into_any())
526 })
527 }
528
529 #[staticmethod]
536 fn to_pem<'py>(
537 py: Python<'py>,
538 obj_or_list: Bound<'_, PyAny>,
539 ) -> PyResult<Bound<'py, PyBytes>> {
540 pyobject_to_pem::<Self, _>(py, "X509 CRL", &obj_or_list, |c| c.raw)
541 }
542
543 #[getter]
549 fn version(&self) -> PyResult<Option<i64>> {
550 Ok(self
551 .crl()?
552 .tbs_cert_list
553 .version
554 .as_ref()
555 .and_then(|v| v.as_i64().ok()))
556 }
557
558 #[getter]
560 fn issuer<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyString>> {
561 if let Some(cached) = self.issuer_cache.get() {
562 return Ok(cached.clone_ref(py).into_bound(py));
563 }
564 let s = name_to_dn_string(&self.crl()?.tbs_cert_list.issuer);
565 let py_str = PyString::new(py, &s).unbind();
566 let _ = self.issuer_cache.set(py_str.clone_ref(py));
567 Ok(py_str.into_bound(py))
568 }
569
570 #[getter]
572 fn issuer_raw_der<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
573 if let Some(cached) = self.issuer_raw_der_cache.get() {
574 return Ok(cached.clone_ref(py).into_bound(py));
575 }
576 let bytes = name_to_der_bytes(&self.crl()?.tbs_cert_list.issuer);
577 let py_bytes = PyBytes::new(py, &bytes).unbind();
578 let _ = self.issuer_raw_der_cache.set(py_bytes.clone_ref(py));
579 Ok(py_bytes.into_bound(py))
580 }
581
582 #[getter]
584 fn this_update<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyString>> {
585 if let Some(cached) = self.this_update_cache.get() {
586 return Ok(cached.clone_ref(py).into_bound(py));
587 }
588 let s = match &self.crl()?.tbs_cert_list.this_update {
589 Time::UtcTime(t) => t.to_string(),
590 Time::GeneralTime(t) => t.to_string(),
591 };
592 let py_str = PyString::new(py, &s).unbind();
593 let _ = self.this_update_cache.set(py_str.clone_ref(py));
594 Ok(py_str.into_bound(py))
595 }
596
597 #[getter]
599 fn next_update<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyString>>> {
600 if let Some(cached) = self.next_update_cache.get() {
601 return Ok(cached.as_ref().map(|s| s.clone_ref(py).into_bound(py)));
602 }
603 let computed: Option<Bound<'py, PyString>> =
604 self.crl()?.tbs_cert_list.next_update.as_ref().map(|t| {
605 let s = match t {
606 Time::UtcTime(t) => t.to_string(),
607 Time::GeneralTime(t) => t.to_string(),
608 };
609 PyString::new(py, &s)
610 });
611 let to_store = computed.as_ref().map(|s| s.as_unbound().clone_ref(py));
612 let _ = self.next_update_cache.set(to_store);
613 Ok(computed)
614 }
615
616 #[getter]
618 fn signature_algorithm<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyString>> {
619 if let Some(cached) = self.signature_algorithm_cache.get() {
620 return Ok(cached.clone_ref(py).into_bound(py));
621 }
622 let oid = &self.crl()?.signature_algorithm.algorithm;
623 let name = synta_certificate::identify_signature_algorithm(oid);
624 let s = if name != "Other" {
625 name.to_string()
626 } else {
627 oid.to_string()
628 };
629 let py_str = PyString::new(py, &s).unbind();
630 let _ = self.signature_algorithm_cache.set(py_str.clone_ref(py));
631 Ok(py_str.into_bound(py))
632 }
633
634 #[getter]
636 fn signature_algorithm_oid<'py>(
637 &self,
638 py: Python<'py>,
639 ) -> PyResult<Bound<'py, PyObjectIdentifier>> {
640 if let Some(cached) = self.signature_algorithm_oid_cache.get() {
641 return Ok(cached.clone_ref(py).into_bound(py));
642 }
643 let obj = Py::new(
644 py,
645 PyObjectIdentifier::from_oid(self.crl()?.signature_algorithm.algorithm.clone()),
646 )?;
647 let _ = self.signature_algorithm_oid_cache.set(obj.clone_ref(py));
648 Ok(obj.into_bound(py))
649 }
650
651 #[getter]
653 fn signature_value<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
654 if let Some(cached) = self.signature_value_cache.get() {
655 return Ok(cached.clone_ref(py).into_bound(py));
656 }
657 let py_bytes = PyBytes::new(py, self.crl()?.signature_value.as_bytes()).unbind();
658 let _ = self.signature_value_cache.set(py_bytes.clone_ref(py));
659 Ok(py_bytes.into_bound(py))
660 }
661
662 #[getter]
672 fn crl_number<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyAny>>> {
673 let Some(n) = self.crl()?.crl_number() else {
674 return Ok(None);
675 };
676 let py_int = if let Ok(v) = n.as_i64() {
677 v.into_pyobject(py)?.into_any()
678 } else if let Ok(v) = n.as_i128() {
679 v.into_pyobject(py)?.into_any()
680 } else {
681 let bytes_obj = PyBytes::new(py, n.as_bytes());
683 let kwargs = pyo3::types::PyDict::new(py);
684 kwargs.set_item(pyo3::intern!(py, "signed"), false)?;
685 py.get_type::<pyo3::types::PyInt>().call_method(
686 pyo3::intern!(py, "from_bytes"),
687 (bytes_obj, pyo3::intern!(py, "big")),
688 Some(&kwargs),
689 )?
690 };
691 Ok(Some(py_int))
692 }
693
694 #[getter]
696 fn revoked_count(&self) -> PyResult<usize> {
697 Ok(self
698 .crl()?
699 .tbs_cert_list
700 .revoked_certificates
701 .as_ref()
702 .map(|v| v.len())
703 .unwrap_or(0))
704 }
705
706 fn get_extension_value_der<'py>(
722 &self,
723 py: Python<'py>,
724 oid: &Bound<'_, PyAny>,
725 ) -> PyResult<Option<Bound<'py, PyBytes>>> {
726 let target = super::oid_from_pyany(oid)?;
727 let exts = match self.crl()?.tbs_cert_list.crl_extensions.as_ref() {
728 Some(e) => e,
729 None => return Ok(None),
730 };
731 for ext in exts {
732 if ext.extn_id == target {
733 return Ok(Some(PyBytes::new(py, ext.extn_value.as_bytes())));
734 }
735 }
736 Ok(None)
737 }
738
739 fn to_der<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
741 self._data.clone_ref(py).into_bound(py)
742 }
743
744 fn __repr__(&self) -> PyResult<String> {
745 let crl = self.crl()?;
746 Ok(format!(
747 "CertificateList(issuer={:?}, revoked_count={})",
748 name_to_dn_string(&crl.tbs_cert_list.issuer),
749 crl.tbs_cert_list
750 .revoked_certificates
751 .as_ref()
752 .map(|v| v.len())
753 .unwrap_or(0),
754 ))
755 }
756}
757
758#[pyclass(frozen, name = "OCSPResponse")]
769pub struct PyOcspResponse {
770 pub(super) _data: Py<PyBytes>,
771 pub(super) raw: &'static [u8],
772 inner: OnceLock<Box<synta_certificate::ocsp::OCSPResponse<'static>>>,
773 status_cache: OnceLock<Py<PyString>>,
774 response_type_oid_cache: OnceLock<Option<Py<PyObjectIdentifier>>>,
775 response_bytes_cache: OnceLock<Option<Py<PyBytes>>>,
776}
777
778impl PyOcspResponse {
779 fn ocsp(&self) -> PyResult<&synta_certificate::ocsp::OCSPResponse<'static>> {
780 if let Some(v) = self.inner.get() {
781 return Ok(v.as_ref());
782 }
783 let mut decoder = Decoder::new(self.raw, Encoding::Der);
784 let decoded = decoder.decode().map_err(|e| {
785 pyo3::exceptions::PyValueError::new_err(format!("OCSPResponse DER decode failed: {e}"))
786 })?;
787 let _ = self.inner.set(Box::new(decoded));
788 Ok(self.inner.get().unwrap().as_ref())
789 }
790}
791
792#[pymethods]
793impl PyOcspResponse {
794 #[staticmethod]
796 fn from_der(py: Python<'_>, data: Bound<'_, PyBytes>) -> PyResult<Self> {
797 let py_bytes = data.unbind();
798 let raw: &'static [u8] = unsafe {
812 let s = py_bytes.bind(py).as_bytes();
813 std::slice::from_raw_parts(s.as_ptr(), s.len())
814 };
815 {
816 let mut d = Decoder::new(raw, Encoding::Der);
817 d.read_tag()
818 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
819 d.read_length()
820 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
821 }
822 Ok(Self {
823 _data: py_bytes,
824 raw,
825 inner: OnceLock::new(),
826 status_cache: OnceLock::new(),
827 response_type_oid_cache: OnceLock::new(),
828 response_bytes_cache: OnceLock::new(),
829 })
830 }
831
832 #[staticmethod]
843 fn from_pem<'py>(py: Python<'py>, data: Bound<'_, PyBytes>) -> PyResult<Bound<'py, PyAny>> {
844 pem_blocks_to_pyobject(py, data.as_bytes(), |py, bytes| {
845 let obj = Self::from_der(py, bytes)?;
846 Ok(Py::new(py, obj)?.into_bound(py).into_any())
847 })
848 }
849
850 #[staticmethod]
857 fn to_pem<'py>(
858 py: Python<'py>,
859 obj_or_list: Bound<'_, PyAny>,
860 ) -> PyResult<Bound<'py, PyBytes>> {
861 pyobject_to_pem::<Self, _>(py, "OCSP RESPONSE", &obj_or_list, |c| c.raw)
862 }
863
864 #[getter]
866 fn status<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyString>> {
867 if let Some(cached) = self.status_cache.get() {
868 return Ok(cached.clone_ref(py).into_bound(py));
869 }
870 let s = ocsp_status_str(self.ocsp()?.response_status);
871 let py_str = PyString::new(py, s).unbind();
872 let _ = self.status_cache.set(py_str.clone_ref(py));
873 Ok(py_str.into_bound(py))
874 }
875
876 #[getter]
882 fn response_type_oid<'py>(
883 &self,
884 py: Python<'py>,
885 ) -> PyResult<Option<Bound<'py, PyObjectIdentifier>>> {
886 if let Some(cached) = self.response_type_oid_cache.get() {
887 return Ok(cached.as_ref().map(|o| o.clone_ref(py).into_bound(py)));
888 }
889 let computed: Option<Py<PyObjectIdentifier>> = self
890 .ocsp()?
891 .response_bytes
892 .as_ref()
893 .map(|rb| Py::new(py, PyObjectIdentifier::from_oid(rb.response_type.clone())))
894 .transpose()?;
895 let _ = self
896 .response_type_oid_cache
897 .set(computed.as_ref().map(|o| o.clone_ref(py)));
898 Ok(computed.map(|o| o.into_bound(py)))
899 }
900
901 #[getter]
907 fn response_bytes<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
908 if let Some(cached) = self.response_bytes_cache.get() {
909 return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
910 }
911 let computed: Option<Bound<'py, PyBytes>> = self
912 .ocsp()?
913 .response_bytes
914 .as_ref()
915 .map(|rb| PyBytes::new(py, rb.response.as_bytes()));
916 let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
917 let _ = self.response_bytes_cache.set(to_store);
918 Ok(computed)
919 }
920
921 fn to_der<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
923 self._data.clone_ref(py).into_bound(py)
924 }
925
926 fn __repr__(&self) -> PyResult<String> {
927 Ok(format!(
928 "OCSPResponse(status={})",
929 ocsp_status_str(self.ocsp()?.response_status),
930 ))
931 }
932}
933
934fn der_certs_to_pylist<'py>(
938 py: Python<'py>,
939 der_certs: Vec<Vec<u8>>,
940) -> PyResult<Bound<'py, PyList>> {
941 let list = PyList::empty(py);
942 for der in der_certs {
943 let py_bytes = PyBytes::new(py, &der);
944 let cert = PyCertificate::new_from_der(py, py_bytes)?;
945 list.append(Py::new(py, cert)?.into_bound(py))?;
946 }
947 Ok(list)
948}
949
950#[pyfunction]
966pub fn load_der_pkcs7_certificates<'py>(
967 py: Python<'py>,
968 data: &[u8],
969) -> PyResult<Bound<'py, PyList>> {
970 let der_certs = synta_certificate::certs_from_pkcs7(data)
971 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
972 der_certs_to_pylist(py, der_certs)
973}
974
975#[pyfunction]
992pub fn load_pem_pkcs7_certificates<'py>(
993 py: Python<'py>,
994 data: &[u8],
995) -> PyResult<Bound<'py, PyList>> {
996 let blocks = synta_certificate::pem_blocks(data);
997 if blocks.is_empty() {
998 return Err(pyo3::exceptions::PyValueError::new_err(
999 "no PEM block found in input",
1000 ));
1001 }
1002 let mut all_certs: Vec<Vec<u8>> = Vec::new();
1003 for (_, der) in &blocks {
1004 let certs = synta_certificate::certs_from_pkcs7(der)
1005 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
1006 all_certs.extend(certs);
1007 }
1008 der_certs_to_pylist(py, all_certs)
1009}
1010
1011#[pyfunction]
1031#[pyo3(signature = (data, password = None))]
1032pub fn load_pkcs12_certificates<'py>(
1033 py: Python<'py>,
1034 data: &[u8],
1035 password: Option<&[u8]>,
1036) -> PyResult<Bound<'py, PyList>> {
1037 let pw = password.unwrap_or(b"");
1038 #[cfg(feature = "openssl")]
1039 let der_certs =
1040 synta_certificate::certs_from_pkcs12(data, pw, &synta_certificate::OpensslDecryptor)
1041 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
1042 #[cfg(not(feature = "openssl"))]
1043 let der_certs = synta_certificate::certs_from_pkcs12(data, pw, &synta_certificate::NoCrypto)
1044 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
1045 der_certs_to_pylist(py, der_certs)
1046}
1047
1048#[pyfunction]
1067#[pyo3(signature = (data, password = None))]
1068pub fn load_pkcs12_keys<'py>(
1069 py: Python<'py>,
1070 data: &[u8],
1071 password: Option<&[u8]>,
1072) -> PyResult<Bound<'py, PyList>> {
1073 let pw = password.unwrap_or(b"");
1074 #[cfg(feature = "openssl")]
1075 let der_keys =
1076 synta_certificate::keys_from_pkcs12(data, pw, &synta_certificate::OpensslDecryptor)
1077 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
1078 #[cfg(not(feature = "openssl"))]
1079 let der_keys = synta_certificate::keys_from_pkcs12(data, pw, &synta_certificate::NoCrypto)
1080 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
1081 let list = PyList::empty(py);
1082 for der in der_keys {
1083 list.append(PyBytes::new(py, &der))?;
1084 }
1085 Ok(list)
1086}
1087
1088#[pyfunction]
1114#[pyo3(signature = (data, password = None))]
1115pub fn load_pkcs12<'py>(
1116 py: Python<'py>,
1117 data: &[u8],
1118 password: Option<&[u8]>,
1119) -> PyResult<Bound<'py, pyo3::types::PyTuple>> {
1120 let pw = password.unwrap_or(b"");
1121 #[cfg(feature = "openssl")]
1122 let pki = synta_certificate::pki_from_pkcs12(data, pw, &synta_certificate::OpensslDecryptor)
1123 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
1124 #[cfg(not(feature = "openssl"))]
1125 let pki = synta_certificate::pki_from_pkcs12(data, pw, &synta_certificate::NoCrypto)
1126 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
1127 let certs = der_certs_to_pylist(py, pki.certs)?;
1128 let keys = PyList::empty(py);
1129 for der in pki.keys {
1130 keys.append(PyBytes::new(py, &der))?;
1131 }
1132 pyo3::types::PyTuple::new(py, [certs.into_any(), keys.into_any()])
1133}
1134
1135#[pyfunction]
1184#[pyo3(signature = (data, password = None))]
1185pub fn read_pki_blocks<'py>(
1186 py: Python<'py>,
1187 data: &[u8],
1188 password: Option<&[u8]>,
1189) -> PyResult<Bound<'py, PyList>> {
1190 let pw = password.unwrap_or(b"");
1191 #[cfg(feature = "openssl")]
1192 let blocks = if password.is_some() {
1193 synta_certificate::read_pki_blocks(
1194 data,
1195 pw,
1196 Some(&synta_certificate::OpensslDecryptor as &dyn synta_certificate::PkiDecryptor),
1197 )
1198 } else {
1199 synta_certificate::read_pki_blocks(data, pw, None::<&dyn synta_certificate::PkiDecryptor>)
1200 };
1201 #[cfg(not(feature = "openssl"))]
1202 let blocks =
1203 synta_certificate::read_pki_blocks(data, pw, None::<&dyn synta_certificate::PkiDecryptor>);
1204 let blocks = blocks.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
1205 let list = PyList::empty(py);
1206 for (label, der) in blocks {
1207 let tuple = pyo3::types::PyTuple::new(
1208 py,
1209 [
1210 PyString::new(py, &label).into_any(),
1211 PyBytes::new(py, &der).into_any(),
1212 ],
1213 )?;
1214 list.append(tuple)?;
1215 }
1216 Ok(list)
1217}
1218
1219#[pyfunction]
1238pub fn encode_extended_key_usage<'py>(
1239 py: Python<'py>,
1240 oids: &Bound<'_, PyList>,
1241) -> PyResult<Bound<'py, PyBytes>> {
1242 use synta::traits::Encode;
1243 let mut eku: Vec<synta::ObjectIdentifier> = Vec::with_capacity(oids.len());
1244 for item in oids.iter() {
1245 eku.push(super::oid_from_pyany(&item)?);
1246 }
1247 let mut enc = synta::Encoder::new(synta::Encoding::Der);
1248 eku.encode(&mut enc)
1249 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
1250 let der = enc
1251 .finish()
1252 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
1253 Ok(PyBytes::new(py, &der))
1254}
1255
1256#[pyfunction]
1279pub fn encode_subject_alt_names<'py>(
1280 py: Python<'py>,
1281 names: &Bound<'_, PyList>,
1282) -> PyResult<Bound<'py, PyBytes>> {
1283 let mut owned: Vec<(u32, Vec<u8>)> = Vec::with_capacity(names.len());
1286 for item in names.iter() {
1287 let tuple = item.cast::<pyo3::types::PyTuple>().map_err(|_| {
1288 pyo3::exceptions::PyTypeError::new_err(
1289 "each element must be a (tag_number, bytes) 2-tuple",
1290 )
1291 })?;
1292 if tuple.len() != 2 {
1293 return Err(pyo3::exceptions::PyTypeError::new_err(
1294 "each element must be a (tag_number, bytes) 2-tuple",
1295 ));
1296 }
1297 let tag_num: u32 = tuple.get_item(0)?.extract()?;
1298 let content: Vec<u8> = tuple.get_item(1)?.extract()?;
1299 owned.push((tag_num, content));
1300 }
1301 let entries: Vec<(u32, &[u8])> = owned.iter().map(|(t, v)| (*t, v.as_slice())).collect();
1302 let der = synta_certificate::encode_general_names(&entries).ok_or_else(|| {
1303 pyo3::exceptions::PyValueError::new_err(
1304 "failed to encode GeneralName entries (invalid content bytes or unsupported tag)",
1305 )
1306 })?;
1307 Ok(PyBytes::new(py, &der))
1308}
1309
1310#[pyfunction]
1323pub fn name_der_equal(a: &[u8], b: &[u8]) -> bool {
1324 a == b
1325}
1326
1327#[pyfunction]
1362#[pyo3(signature = (certificates, private_key = None, password = None))]
1363pub fn create_pkcs12<'py>(
1364 py: Python<'py>,
1365 certificates: &Bound<'_, PyList>,
1366 private_key: Option<Bound<'_, PyAny>>,
1367 password: Option<&[u8]>,
1368) -> PyResult<Bound<'py, PyBytes>> {
1369 use synta_certificate::Pkcs12Builder;
1370
1371 let password = password.unwrap_or(b"");
1372 let mut builder = Pkcs12Builder::new();
1373 for item in certificates.iter() {
1374 if let Ok(py_cert) = item.cast::<PyCertificate>() {
1375 builder = builder.certificate(py_cert.get().raw);
1379 } else if let Ok(py_bytes) = item.cast::<PyBytes>() {
1380 builder = builder.certificate(py_bytes.as_bytes());
1381 } else {
1382 return Err(pyo3::exceptions::PyTypeError::new_err(
1383 "certificates must be synta.Certificate or bytes",
1384 ));
1385 }
1386 }
1387 if let Some(key_arg) = private_key {
1388 if let Ok(py_key) = key_arg.cast::<PyPrivateKey>() {
1389 let key_der = py_key
1391 .get()
1392 .inner
1393 .to_der()
1394 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
1395 builder = builder.private_key(&key_der);
1396 } else if let Ok(py_bytes) = key_arg.cast::<PyBytes>() {
1397 builder = builder.private_key(py_bytes.as_bytes());
1398 } else {
1399 return Err(pyo3::exceptions::PyTypeError::new_err(
1400 "private_key must be synta.PrivateKey or bytes",
1401 ));
1402 }
1403 }
1404
1405 #[cfg(feature = "openssl")]
1406 let pfx_der = builder
1407 .build(password, &synta_certificate::OpensslPkcs12Encryptor::new())
1408 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
1409
1410 #[cfg(not(feature = "openssl"))]
1411 let pfx_der = {
1412 use synta_certificate::NoPkcs12Encryptor;
1413 builder
1414 .build(password, &NoPkcs12Encryptor)
1415 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?
1416 };
1417
1418 Ok(PyBytes::new(py, &pfx_der))
1419}