use std::sync::OnceLock;
use pyo3::prelude::*;
use pyo3::types::{PyBytes, PyList, PyString};
use synta::traits::Encode;
use synta::{Decoder, Encoding};
use synta_certificate::Time;
use super::cert::{pem_blocks_to_pyobject, pyobject_to_pem, PyCertificate};
use crate::crypto_keys::PyPrivateKey;
use crate::types::PyObjectIdentifier;
fn name_to_dn_string(name: &synta_certificate::Name<'_>) -> String {
let mut enc = synta::Encoder::new(synta::Encoding::Der);
if name.encode(&mut enc).is_err() {
return String::new();
}
synta_certificate::name::format_dn(&enc.finish().unwrap_or_default())
}
fn name_to_der_bytes(name: &synta_certificate::Name<'_>) -> Vec<u8> {
let mut enc = synta::Encoder::new(synta::Encoding::Der);
if name.encode(&mut enc).is_err() {
return Vec::new();
}
enc.finish().unwrap_or_default()
}
fn ocsp_status_str(status: synta_certificate::ocsp::OCSPResponseStatus) -> &'static str {
use synta_certificate::ocsp::OCSPResponseStatus::*;
match status {
Successful => "successful",
MalformedRequest => "malformedRequest",
InternalError => "internalError",
TryLater => "tryLater",
SigRequired => "sigRequired",
Unauthorized => "unauthorized",
}
}
#[pyclass(frozen, name = "CertificationRequest")]
pub struct PyCsr {
pub(super) _data: Py<PyBytes>,
pub(super) raw: &'static [u8],
inner: OnceLock<Box<synta_certificate::csr::CertificationRequest<'static>>>,
subject_cache: OnceLock<Py<PyString>>,
subject_raw_der_cache: OnceLock<Py<PyBytes>>,
signature_algorithm_cache: OnceLock<Py<PyString>>,
signature_algorithm_oid_cache: OnceLock<Py<PyObjectIdentifier>>,
signature_cache: OnceLock<Py<PyBytes>>,
public_key_algorithm_cache: OnceLock<Py<PyString>>,
public_key_algorithm_oid_cache: OnceLock<Py<PyObjectIdentifier>>,
public_key_cache: OnceLock<Py<PyBytes>>,
spki_der_cache: OnceLock<Py<PyBytes>>,
}
impl PyCsr {
fn csr(&self) -> PyResult<&synta_certificate::csr::CertificationRequest<'static>> {
if let Some(v) = self.inner.get() {
return Ok(v.as_ref());
}
let mut decoder = Decoder::new(self.raw, Encoding::Der);
let decoded = decoder.decode().map_err(|e| {
pyo3::exceptions::PyValueError::new_err(format!(
"CertificationRequest DER decode failed: {e}"
))
})?;
let _ = self.inner.set(Box::new(decoded));
Ok(self.inner.get().unwrap().as_ref())
}
}
#[pymethods]
impl PyCsr {
#[staticmethod]
fn from_der(py: Python<'_>, data: Bound<'_, PyBytes>) -> PyResult<Self> {
let py_bytes = data.unbind();
let raw: &'static [u8] = unsafe {
let s = py_bytes.bind(py).as_bytes();
std::slice::from_raw_parts(s.as_ptr(), s.len())
};
{
let mut d = Decoder::new(raw, Encoding::Der);
d.read_tag()
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
d.read_length()
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
}
Ok(Self {
_data: py_bytes,
raw,
inner: OnceLock::new(),
subject_cache: OnceLock::new(),
subject_raw_der_cache: OnceLock::new(),
signature_algorithm_cache: OnceLock::new(),
signature_algorithm_oid_cache: OnceLock::new(),
signature_cache: OnceLock::new(),
public_key_algorithm_cache: OnceLock::new(),
public_key_algorithm_oid_cache: OnceLock::new(),
public_key_cache: OnceLock::new(),
spki_der_cache: OnceLock::new(),
})
}
#[staticmethod]
fn from_pem<'py>(py: Python<'py>, data: Bound<'_, PyBytes>) -> PyResult<Bound<'py, PyAny>> {
pem_blocks_to_pyobject(py, data.as_bytes(), |py, bytes| {
let obj = Self::from_der(py, bytes)?;
Ok(Py::new(py, obj)?.into_bound(py).into_any())
})
}
#[staticmethod]
fn to_pem<'py>(
py: Python<'py>,
obj_or_list: Bound<'_, PyAny>,
) -> PyResult<Bound<'py, PyBytes>> {
pyobject_to_pem::<Self, _>(py, "CERTIFICATE REQUEST", &obj_or_list, |c| c.raw)
}
#[getter]
fn subject<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyString>> {
if let Some(cached) = self.subject_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let s = name_to_dn_string(&self.csr()?.certification_request_info.subject);
let py_str = PyString::new(py, &s).unbind();
let _ = self.subject_cache.set(py_str.clone_ref(py));
Ok(py_str.into_bound(py))
}
#[getter]
fn subject_raw_der<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
if let Some(cached) = self.subject_raw_der_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let bytes = name_to_der_bytes(&self.csr()?.certification_request_info.subject);
let py_bytes = PyBytes::new(py, &bytes).unbind();
let _ = self.subject_raw_der_cache.set(py_bytes.clone_ref(py));
Ok(py_bytes.into_bound(py))
}
#[getter]
fn version(&self) -> PyResult<i64> {
Ok(self
.csr()?
.certification_request_info
.version
.as_i64()
.unwrap_or(0))
}
#[getter]
fn signature_algorithm<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyString>> {
if let Some(cached) = self.signature_algorithm_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let oid = &self.csr()?.signature_algorithm.algorithm;
let name = synta_certificate::identify_signature_algorithm(oid);
let s = if name != "Other" {
name.to_string()
} else {
oid.to_string()
};
let py_str = PyString::new(py, &s).unbind();
let _ = self.signature_algorithm_cache.set(py_str.clone_ref(py));
Ok(py_str.into_bound(py))
}
#[getter]
fn signature_algorithm_oid<'py>(
&self,
py: Python<'py>,
) -> PyResult<Bound<'py, PyObjectIdentifier>> {
if let Some(cached) = self.signature_algorithm_oid_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let obj = Py::new(
py,
PyObjectIdentifier::from_oid(self.csr()?.signature_algorithm.algorithm.clone()),
)?;
let _ = self.signature_algorithm_oid_cache.set(obj.clone_ref(py));
Ok(obj.into_bound(py))
}
#[getter]
fn signature<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
if let Some(cached) = self.signature_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let py_bytes = PyBytes::new(py, self.csr()?.signature.as_bytes()).unbind();
let _ = self.signature_cache.set(py_bytes.clone_ref(py));
Ok(py_bytes.into_bound(py))
}
#[getter]
fn public_key_algorithm<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyString>> {
if let Some(cached) = self.public_key_algorithm_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let oid = &self
.csr()?
.certification_request_info
.subject_pkinfo
.algorithm
.algorithm;
let s = synta_certificate::identify_public_key_algorithm(oid)
.map(|s| s.to_string())
.unwrap_or_else(|| oid.to_string());
let py_str = PyString::new(py, &s).unbind();
let _ = self.public_key_algorithm_cache.set(py_str.clone_ref(py));
Ok(py_str.into_bound(py))
}
#[getter]
fn public_key_algorithm_oid<'py>(
&self,
py: Python<'py>,
) -> PyResult<Bound<'py, PyObjectIdentifier>> {
if let Some(cached) = self.public_key_algorithm_oid_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let obj = Py::new(
py,
PyObjectIdentifier::from_oid(
self.csr()?
.certification_request_info
.subject_pkinfo
.algorithm
.algorithm
.clone(),
),
)?;
let _ = self.public_key_algorithm_oid_cache.set(obj.clone_ref(py));
Ok(obj.into_bound(py))
}
#[getter]
fn public_key<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
if let Some(cached) = self.public_key_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let py_bytes = PyBytes::new(
py,
self.csr()?
.certification_request_info
.subject_pkinfo
.subject_public_key
.as_bytes(),
)
.unbind();
let _ = self.public_key_cache.set(py_bytes.clone_ref(py));
Ok(py_bytes.into_bound(py))
}
#[getter]
fn subject_public_key_info_der<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
if let Some(cached) = self.spki_der_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let spki = &self.csr()?.certification_request_info.subject_pkinfo;
let bytes = spki
.to_der()
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("SPKI encode: {e}")))?;
let py_bytes = PyBytes::new(py, &bytes).unbind();
let _ = self.spki_der_cache.set(py_bytes.clone_ref(py));
Ok(py_bytes.into_bound(py))
}
fn get_extension_value_der<'py>(
&self,
py: Python<'py>,
oid: &Bound<'_, PyAny>,
) -> PyResult<Option<Bound<'py, PyBytes>>> {
let target = super::oid_from_pyany(oid)?;
let csr = self.csr()?;
let attrs = match csr.certification_request_info.attributes.as_ref() {
Some(a) => a,
None => return Ok(None),
};
for attr in attrs.elements() {
if attr.attr_type.components() != synta_certificate::oids::PKCS9_EXTENSION_REQUEST {
continue;
}
let Some(raw) = attr.attr_values.elements().first() else {
return Ok(None);
};
return Ok(synta_certificate::find_extension_value(
raw.as_bytes(),
target.components(),
)
.map(|v| PyBytes::new(py, v)));
}
Ok(None)
}
fn subject_alt_names<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyList>> {
use pyo3::types::{PyBytes, PyList, PyTuple};
let list = PyList::empty(py);
let csr = self.csr()?;
let attrs = match csr.certification_request_info.attributes.as_ref() {
Some(a) => a,
None => return Ok(list),
};
for attr in attrs.elements() {
if attr.attr_type.components() != synta_certificate::oids::PKCS9_EXTENSION_REQUEST {
continue;
}
let Some(raw) = attr.attr_values.elements().first() else {
return Ok(list);
};
if let Some(san_bytes) = synta_certificate::find_extension_value(
raw.as_bytes(),
synta_certificate::oids::SUBJECT_ALT_NAME,
) {
for (tag_num, content) in synta_certificate::parse_general_names(san_bytes) {
let tuple = PyTuple::new(
py,
[
tag_num.into_pyobject(py)?.into_any(),
PyBytes::new(py, &content).into_any(),
],
)?;
list.append(tuple)?;
}
}
break;
}
Ok(list)
}
fn to_der<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
self._data.clone_ref(py).into_bound(py)
}
fn verify_self_signature(&self) -> PyResult<()> {
use synta_certificate::{default_signature_verifier, SignatureVerifier};
let csr = self.csr()?;
let tbs_der = csr.certification_request_info.to_der().map_err(|e| {
pyo3::exceptions::PyValueError::new_err(format!("CSR info encode: {e}"))
})?;
let sig_alg_der = csr
.signature_algorithm
.to_der()
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("CSR alg encode: {e}")))?;
let spki_der = csr
.certification_request_info
.subject_pkinfo
.to_der()
.map_err(|e| {
pyo3::exceptions::PyValueError::new_err(format!("CSR SPKI encode: {e}"))
})?;
default_signature_verifier()
.verify_certificate_signature(
&tbs_der,
&sig_alg_der,
csr.signature.as_bytes(),
&spki_der,
)
.map_err(|e| {
pyo3::exceptions::PyValueError::new_err(format!("CSR self-signature invalid: {e}"))
})
}
fn __repr__(&self) -> PyResult<String> {
Ok(format!(
"CertificationRequest(subject={:?})",
name_to_dn_string(&self.csr()?.certification_request_info.subject),
))
}
}
impl PyCsr {
pub(crate) fn new_from_der(py: Python<'_>, data: Bound<'_, PyBytes>) -> PyResult<Self> {
Self::from_der(py, data)
}
}
#[pyclass(frozen, name = "CertificateList")]
pub struct PyCrl {
pub(super) _data: Py<PyBytes>,
pub(super) raw: &'static [u8],
inner: OnceLock<Box<synta_certificate::crl::CertificateList<'static>>>,
issuer_cache: OnceLock<Py<PyString>>,
issuer_raw_der_cache: OnceLock<Py<PyBytes>>,
this_update_cache: OnceLock<Py<PyString>>,
next_update_cache: OnceLock<Option<Py<PyString>>>,
signature_algorithm_cache: OnceLock<Py<PyString>>,
signature_algorithm_oid_cache: OnceLock<Py<PyObjectIdentifier>>,
signature_value_cache: OnceLock<Py<PyBytes>>,
}
impl PyCrl {
fn crl(&self) -> PyResult<&synta_certificate::crl::CertificateList<'static>> {
if let Some(v) = self.inner.get() {
return Ok(v.as_ref());
}
let mut decoder = Decoder::new(self.raw, Encoding::Der);
let decoded = decoder.decode().map_err(|e| {
pyo3::exceptions::PyValueError::new_err(format!(
"CertificateList DER decode failed: {e}"
))
})?;
let _ = self.inner.set(Box::new(decoded));
Ok(self.inner.get().unwrap().as_ref())
}
}
#[pymethods]
impl PyCrl {
#[staticmethod]
fn from_der(py: Python<'_>, data: Bound<'_, PyBytes>) -> PyResult<Self> {
let py_bytes = data.unbind();
let raw: &'static [u8] = unsafe {
let s = py_bytes.bind(py).as_bytes();
std::slice::from_raw_parts(s.as_ptr(), s.len())
};
{
let mut d = Decoder::new(raw, Encoding::Der);
d.read_tag()
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
d.read_length()
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
}
Ok(Self {
_data: py_bytes,
raw,
inner: OnceLock::new(),
issuer_cache: OnceLock::new(),
issuer_raw_der_cache: OnceLock::new(),
this_update_cache: OnceLock::new(),
next_update_cache: OnceLock::new(),
signature_algorithm_cache: OnceLock::new(),
signature_algorithm_oid_cache: OnceLock::new(),
signature_value_cache: OnceLock::new(),
})
}
#[staticmethod]
fn from_pem<'py>(py: Python<'py>, data: Bound<'_, PyBytes>) -> PyResult<Bound<'py, PyAny>> {
pem_blocks_to_pyobject(py, data.as_bytes(), |py, bytes| {
let obj = Self::from_der(py, bytes)?;
Ok(Py::new(py, obj)?.into_bound(py).into_any())
})
}
#[staticmethod]
fn to_pem<'py>(
py: Python<'py>,
obj_or_list: Bound<'_, PyAny>,
) -> PyResult<Bound<'py, PyBytes>> {
pyobject_to_pem::<Self, _>(py, "X509 CRL", &obj_or_list, |c| c.raw)
}
#[getter]
fn version(&self) -> PyResult<Option<i64>> {
Ok(self
.crl()?
.tbs_cert_list
.version
.as_ref()
.and_then(|v| v.as_i64().ok()))
}
#[getter]
fn issuer<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyString>> {
if let Some(cached) = self.issuer_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let s = name_to_dn_string(&self.crl()?.tbs_cert_list.issuer);
let py_str = PyString::new(py, &s).unbind();
let _ = self.issuer_cache.set(py_str.clone_ref(py));
Ok(py_str.into_bound(py))
}
#[getter]
fn issuer_raw_der<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
if let Some(cached) = self.issuer_raw_der_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let bytes = name_to_der_bytes(&self.crl()?.tbs_cert_list.issuer);
let py_bytes = PyBytes::new(py, &bytes).unbind();
let _ = self.issuer_raw_der_cache.set(py_bytes.clone_ref(py));
Ok(py_bytes.into_bound(py))
}
#[getter]
fn this_update<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyString>> {
if let Some(cached) = self.this_update_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let s = match &self.crl()?.tbs_cert_list.this_update {
Time::UtcTime(t) => t.to_string(),
Time::GeneralTime(t) => t.to_string(),
};
let py_str = PyString::new(py, &s).unbind();
let _ = self.this_update_cache.set(py_str.clone_ref(py));
Ok(py_str.into_bound(py))
}
#[getter]
fn next_update<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyString>>> {
if let Some(cached) = self.next_update_cache.get() {
return Ok(cached.as_ref().map(|s| s.clone_ref(py).into_bound(py)));
}
let computed: Option<Bound<'py, PyString>> =
self.crl()?.tbs_cert_list.next_update.as_ref().map(|t| {
let s = match t {
Time::UtcTime(t) => t.to_string(),
Time::GeneralTime(t) => t.to_string(),
};
PyString::new(py, &s)
});
let to_store = computed.as_ref().map(|s| s.as_unbound().clone_ref(py));
let _ = self.next_update_cache.set(to_store);
Ok(computed)
}
#[getter]
fn signature_algorithm<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyString>> {
if let Some(cached) = self.signature_algorithm_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let oid = &self.crl()?.signature_algorithm.algorithm;
let name = synta_certificate::identify_signature_algorithm(oid);
let s = if name != "Other" {
name.to_string()
} else {
oid.to_string()
};
let py_str = PyString::new(py, &s).unbind();
let _ = self.signature_algorithm_cache.set(py_str.clone_ref(py));
Ok(py_str.into_bound(py))
}
#[getter]
fn signature_algorithm_oid<'py>(
&self,
py: Python<'py>,
) -> PyResult<Bound<'py, PyObjectIdentifier>> {
if let Some(cached) = self.signature_algorithm_oid_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let obj = Py::new(
py,
PyObjectIdentifier::from_oid(self.crl()?.signature_algorithm.algorithm.clone()),
)?;
let _ = self.signature_algorithm_oid_cache.set(obj.clone_ref(py));
Ok(obj.into_bound(py))
}
#[getter]
fn signature_value<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
if let Some(cached) = self.signature_value_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let py_bytes = PyBytes::new(py, self.crl()?.signature_value.as_bytes()).unbind();
let _ = self.signature_value_cache.set(py_bytes.clone_ref(py));
Ok(py_bytes.into_bound(py))
}
#[getter]
fn crl_number<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyAny>>> {
let Some(n) = self.crl()?.crl_number() else {
return Ok(None);
};
let py_int = if let Ok(v) = n.as_i64() {
v.into_pyobject(py)?.into_any()
} else if let Ok(v) = n.as_i128() {
v.into_pyobject(py)?.into_any()
} else {
let bytes_obj = PyBytes::new(py, n.as_bytes());
let kwargs = pyo3::types::PyDict::new(py);
kwargs.set_item(pyo3::intern!(py, "signed"), false)?;
py.get_type::<pyo3::types::PyInt>().call_method(
pyo3::intern!(py, "from_bytes"),
(bytes_obj, pyo3::intern!(py, "big")),
Some(&kwargs),
)?
};
Ok(Some(py_int))
}
#[getter]
fn revoked_count(&self) -> PyResult<usize> {
Ok(self
.crl()?
.tbs_cert_list
.revoked_certificates
.as_ref()
.map(|v| v.len())
.unwrap_or(0))
}
fn get_extension_value_der<'py>(
&self,
py: Python<'py>,
oid: &Bound<'_, PyAny>,
) -> PyResult<Option<Bound<'py, PyBytes>>> {
let target = super::oid_from_pyany(oid)?;
let exts = match self.crl()?.tbs_cert_list.crl_extensions.as_ref() {
Some(e) => e,
None => return Ok(None),
};
for ext in exts {
if ext.extn_id == target {
return Ok(Some(PyBytes::new(py, ext.extn_value.as_bytes())));
}
}
Ok(None)
}
fn to_der<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
self._data.clone_ref(py).into_bound(py)
}
fn verify_issued_by(&self, issuer: &super::cert::PyCertificate) -> PyResult<()> {
use synta::traits::Encode;
use synta_certificate::{default_signature_verifier, SignatureVerifier};
let crl = self.crl()?;
let issuer_cert = issuer.cert()?;
let crl_issuer_der = name_to_der_bytes(&crl.tbs_cert_list.issuer);
if crl_issuer_der.as_slice() != issuer_cert.tbs_certificate.subject.as_bytes() {
return Err(pyo3::exceptions::PyValueError::new_err(
"CRL issuer name does not match subject of provided certificate",
));
}
let mut tbs_enc = synta::Encoder::new(synta::Encoding::Der);
crl.tbs_cert_list
.encode(&mut tbs_enc)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("CRL TBS encode: {e}")))?;
let tbs_der = tbs_enc
.finish()
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("CRL TBS finish: {e}")))?;
let mut alg_enc = synta::Encoder::new(synta::Encoding::Der);
crl.signature_algorithm
.encode(&mut alg_enc)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("CRL alg encode: {e}")))?;
let sig_alg_der = alg_enc
.finish()
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("CRL alg finish: {e}")))?;
let mut spki_enc = synta::Encoder::new(synta::Encoding::Der);
issuer_cert
.tbs_certificate
.subject_public_key_info
.encode(&mut spki_enc)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("SPKI encode: {e}")))?;
let spki_der = spki_enc
.finish()
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("SPKI finish: {e}")))?;
default_signature_verifier()
.verify_certificate_signature(
&tbs_der,
&sig_alg_der,
crl.signature_value.as_bytes(),
&spki_der,
)
.map_err(|e| {
pyo3::exceptions::PyValueError::new_err(format!("CRL signature invalid: {e}"))
})
}
fn __repr__(&self) -> PyResult<String> {
let crl = self.crl()?;
Ok(format!(
"CertificateList(issuer={:?}, revoked_count={})",
name_to_dn_string(&crl.tbs_cert_list.issuer),
crl.tbs_cert_list
.revoked_certificates
.as_ref()
.map(|v| v.len())
.unwrap_or(0),
))
}
}
#[pyclass(frozen, name = "OCSPResponse")]
pub struct PyOcspResponse {
pub(super) _data: Py<PyBytes>,
pub(super) raw: &'static [u8],
inner: OnceLock<Box<synta_certificate::ocsp::OCSPResponse<'static>>>,
status_cache: OnceLock<Py<PyString>>,
response_type_oid_cache: OnceLock<Option<Py<PyObjectIdentifier>>>,
response_bytes_cache: OnceLock<Option<Py<PyBytes>>>,
}
impl PyOcspResponse {
fn ocsp(&self) -> PyResult<&synta_certificate::ocsp::OCSPResponse<'static>> {
if let Some(v) = self.inner.get() {
return Ok(v.as_ref());
}
let mut decoder = Decoder::new(self.raw, Encoding::Der);
let decoded = decoder.decode().map_err(|e| {
pyo3::exceptions::PyValueError::new_err(format!("OCSPResponse DER decode failed: {e}"))
})?;
let _ = self.inner.set(Box::new(decoded));
Ok(self.inner.get().unwrap().as_ref())
}
}
#[pymethods]
impl PyOcspResponse {
#[staticmethod]
fn from_der(py: Python<'_>, data: Bound<'_, PyBytes>) -> PyResult<Self> {
let py_bytes = data.unbind();
let raw: &'static [u8] = unsafe {
let s = py_bytes.bind(py).as_bytes();
std::slice::from_raw_parts(s.as_ptr(), s.len())
};
{
let mut d = Decoder::new(raw, Encoding::Der);
d.read_tag()
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
d.read_length()
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
}
Ok(Self {
_data: py_bytes,
raw,
inner: OnceLock::new(),
status_cache: OnceLock::new(),
response_type_oid_cache: OnceLock::new(),
response_bytes_cache: OnceLock::new(),
})
}
#[staticmethod]
fn from_pem<'py>(py: Python<'py>, data: Bound<'_, PyBytes>) -> PyResult<Bound<'py, PyAny>> {
pem_blocks_to_pyobject(py, data.as_bytes(), |py, bytes| {
let obj = Self::from_der(py, bytes)?;
Ok(Py::new(py, obj)?.into_bound(py).into_any())
})
}
#[staticmethod]
fn to_pem<'py>(
py: Python<'py>,
obj_or_list: Bound<'_, PyAny>,
) -> PyResult<Bound<'py, PyBytes>> {
pyobject_to_pem::<Self, _>(py, "OCSP RESPONSE", &obj_or_list, |c| c.raw)
}
#[getter]
fn status<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyString>> {
if let Some(cached) = self.status_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let s = ocsp_status_str(self.ocsp()?.response_status);
let py_str = PyString::new(py, s).unbind();
let _ = self.status_cache.set(py_str.clone_ref(py));
Ok(py_str.into_bound(py))
}
#[getter]
fn response_type_oid<'py>(
&self,
py: Python<'py>,
) -> PyResult<Option<Bound<'py, PyObjectIdentifier>>> {
if let Some(cached) = self.response_type_oid_cache.get() {
return Ok(cached.as_ref().map(|o| o.clone_ref(py).into_bound(py)));
}
let computed: Option<Py<PyObjectIdentifier>> = self
.ocsp()?
.response_bytes
.as_ref()
.map(|rb| Py::new(py, PyObjectIdentifier::from_oid(rb.response_type.clone())))
.transpose()?;
let _ = self
.response_type_oid_cache
.set(computed.as_ref().map(|o| o.clone_ref(py)));
Ok(computed.map(|o| o.into_bound(py)))
}
#[getter]
fn response_bytes<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.response_bytes_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed: Option<Bound<'py, PyBytes>> = self
.ocsp()?
.response_bytes
.as_ref()
.map(|rb| PyBytes::new(py, rb.response.as_bytes()));
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.response_bytes_cache.set(to_store);
Ok(computed)
}
fn to_der<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
self._data.clone_ref(py).into_bound(py)
}
fn verify_signature(&self, responder: &super::cert::PyCertificate) -> PyResult<()> {
use synta_certificate::{default_signature_verifier, SignatureVerifier};
let ocsp = self.ocsp()?;
let rb = ocsp.response_bytes.as_ref().ok_or_else(|| {
pyo3::exceptions::PyValueError::new_err(
"OCSP response carries no responseBytes (error status response)",
)
})?;
use synta_certificate::oids::ID_PKIX_OCSP_BASIC;
if rb.response_type.components() != ID_PKIX_OCSP_BASIC {
return Err(pyo3::exceptions::PyValueError::new_err(format!(
"unsupported OCSP responseType: {}",
rb.response_type,
)));
}
let basic_der = rb.response.as_bytes();
let basic: synta_certificate::ocsp::BasicOCSPResponse<'_> = {
let mut dec = Decoder::new(basic_der, Encoding::Der);
dec.decode().map_err(|e| {
pyo3::exceptions::PyValueError::new_err(format!(
"BasicOCSPResponse decode failed: {e}"
))
})?
};
let tbs_der = basic.tbs_response_data.to_der().map_err(|e| {
pyo3::exceptions::PyValueError::new_err(format!("ResponseData encode: {e}"))
})?;
let sig_alg_der = basic.signature_algorithm.to_der().map_err(|e| {
pyo3::exceptions::PyValueError::new_err(format!("OCSP alg encode: {e}"))
})?;
let issuer_cert = responder.cert()?;
let spki_der = issuer_cert
.tbs_certificate
.subject_public_key_info
.to_der()
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("SPKI encode: {e}")))?;
default_signature_verifier()
.verify_certificate_signature(
&tbs_der,
&sig_alg_der,
basic.signature.as_bytes(),
&spki_der,
)
.map_err(|e| {
pyo3::exceptions::PyValueError::new_err(format!("OCSP signature invalid: {e}"))
})
}
fn __repr__(&self) -> PyResult<String> {
Ok(format!(
"OCSPResponse(status={})",
ocsp_status_str(self.ocsp()?.response_status),
))
}
}
type OcspRequest2024 = synta_certificate::ocsp_2024_88_types::OCSPRequest<'static>;
#[pyclass(frozen, name = "CertID")]
pub struct PyCertID {
hash_alg_oid: synta::ObjectIdentifier,
issuer_name_hash: Vec<u8>,
issuer_key_hash: Vec<u8>,
serial: synta::Integer,
hash_algorithm_oid_cache: OnceLock<Py<PyObjectIdentifier>>,
serial_number_cache: OnceLock<Py<PyAny>>,
}
impl PyCertID {
fn from_cert_id(c: &synta_certificate::ocsp_2024_88_types::CertID<'_>) -> Self {
Self {
hash_alg_oid: c.hash_algorithm.algorithm.clone(),
issuer_name_hash: c.issuer_name_hash.as_bytes().to_vec(),
issuer_key_hash: c.issuer_key_hash.as_bytes().to_vec(),
serial: c.serial_number.clone(),
hash_algorithm_oid_cache: OnceLock::new(),
serial_number_cache: OnceLock::new(),
}
}
}
#[pymethods]
impl PyCertID {
#[getter]
fn hash_algorithm_oid<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyObjectIdentifier>> {
if let Some(cached) = self.hash_algorithm_oid_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let oid = Py::new(py, PyObjectIdentifier::from_oid(self.hash_alg_oid.clone()))?;
let _ = self.hash_algorithm_oid_cache.set(oid.clone_ref(py));
Ok(oid.into_bound(py))
}
#[getter]
fn issuer_name_hash<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
PyBytes::new(py, &self.issuer_name_hash)
}
#[getter]
fn issuer_key_hash<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
PyBytes::new(py, &self.issuer_key_hash)
}
#[getter]
fn serial_number<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
if let Some(cached) = self.serial_number_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let py_int: Py<PyAny> = if let Ok(v) = self.serial.as_i64() {
v.into_pyobject(py)?.into_any().unbind()
} else if let Ok(v) = self.serial.as_i128() {
v.into_pyobject(py)?.into_any().unbind()
} else {
let bytes_obj = PyBytes::new(py, self.serial.as_bytes());
let kwargs = pyo3::types::PyDict::new(py);
kwargs.set_item(pyo3::intern!(py, "signed"), true)?;
py.get_type::<pyo3::types::PyInt>()
.call_method(
pyo3::intern!(py, "from_bytes"),
(bytes_obj, pyo3::intern!(py, "big")),
Some(&kwargs),
)
.map(|r| r.unbind())?
};
let _ = self.serial_number_cache.set(py_int.clone_ref(py));
Ok(py_int.into_bound(py))
}
fn __repr__(&self) -> String {
format!(
"CertID(hash_alg={}, serial=<{} bytes>)",
self.hash_alg_oid,
self.serial.as_bytes().len(),
)
}
}
#[pyclass(frozen, name = "OCSPRequest")]
pub struct PyOcspRequest {
pub(super) _data: Py<PyBytes>,
pub(super) raw: &'static [u8],
inner: OnceLock<Box<OcspRequest2024>>,
request_list_cache: OnceLock<Py<PyList>>,
requestor_name_cache: OnceLock<Option<Py<PyBytes>>>,
request_extensions_cache: OnceLock<Option<Py<PyBytes>>>,
}
impl PyOcspRequest {
fn req(&self) -> PyResult<&OcspRequest2024> {
if let Some(v) = self.inner.get() {
return Ok(v.as_ref());
}
let mut decoder = Decoder::new(self.raw, Encoding::Der);
let decoded = decoder.decode::<OcspRequest2024>().map_err(|e| {
pyo3::exceptions::PyValueError::new_err(format!("OCSPRequest DER decode failed: {e}"))
})?;
let _ = self.inner.set(Box::new(decoded));
Ok(self.inner.get().unwrap().as_ref())
}
}
#[pymethods]
impl PyOcspRequest {
#[staticmethod]
fn from_der(py: Python<'_>, data: Bound<'_, PyBytes>) -> PyResult<Self> {
let py_bytes = data.unbind();
let raw: &'static [u8] = unsafe {
let s = py_bytes.bind(py).as_bytes();
std::slice::from_raw_parts(s.as_ptr(), s.len())
};
{
let mut d = Decoder::new(raw, Encoding::Der);
d.read_tag()
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
d.read_length()
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
}
Ok(Self {
_data: py_bytes,
raw,
inner: OnceLock::new(),
request_list_cache: OnceLock::new(),
requestor_name_cache: OnceLock::new(),
request_extensions_cache: OnceLock::new(),
})
}
#[staticmethod]
fn from_pem<'py>(py: Python<'py>, data: Bound<'_, PyBytes>) -> PyResult<Bound<'py, PyAny>> {
pem_blocks_to_pyobject(py, data.as_bytes(), |py, bytes| {
let obj = Self::from_der(py, bytes)?;
Ok(Py::new(py, obj)?.into_bound(py).into_any())
})
}
#[staticmethod]
fn to_pem<'py>(
py: Python<'py>,
obj_or_list: Bound<'_, PyAny>,
) -> PyResult<Bound<'py, PyBytes>> {
pyobject_to_pem::<Self, _>(py, "OCSP REQUEST", &obj_or_list, |c| c.raw)
}
fn to_der<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
PyBytes::new(py, self.raw)
}
#[getter]
fn request_list<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyList>> {
if let Some(cached) = self.request_list_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let req = self.req()?;
let list = PyList::empty(py);
for request in &req.tbs_request.request_list {
let cert_id = PyCertID::from_cert_id(&request.req_cert);
list.append(Py::new(py, cert_id)?)?;
}
let py_list = list.unbind();
let _ = self.request_list_cache.set(py_list.clone_ref(py));
Ok(py_list.into_bound(py))
}
#[getter]
fn requestor_name<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.requestor_name_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed: Option<Bound<'py, PyBytes>> = self
.req()?
.tbs_request
.requestor_name
.as_ref()
.map(|gn| -> PyResult<Bound<'py, PyBytes>> {
let mut enc = synta::Encoder::new(synta::Encoding::Der);
gn.encode(&mut enc)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
let der = enc
.finish()
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
Ok(PyBytes::new(py, &der))
})
.transpose()?;
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.requestor_name_cache.set(to_store);
Ok(computed)
}
#[getter]
fn request_extensions<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.request_extensions_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed: Option<Bound<'py, PyBytes>> = self
.req()?
.tbs_request
.request_extensions
.as_ref()
.map(|exts| -> PyResult<Bound<'py, PyBytes>> {
let mut enc = synta::Encoder::new(synta::Encoding::Der);
exts.encode(&mut enc)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
let der = enc
.finish()
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
Ok(PyBytes::new(py, &der))
})
.transpose()?;
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.request_extensions_cache.set(to_store);
Ok(computed)
}
fn __repr__(&self) -> PyResult<String> {
Ok(format!(
"OCSPRequest(requests={})",
self.req()?.tbs_request.request_list.len(),
))
}
}
fn der_certs_to_pylist<'py>(
py: Python<'py>,
der_certs: Vec<Vec<u8>>,
) -> PyResult<Bound<'py, PyList>> {
let list = PyList::empty(py);
for der in der_certs {
let py_bytes = PyBytes::new(py, &der);
let cert = PyCertificate::new_from_der(py, py_bytes)?;
list.append(Py::new(py, cert)?.into_bound(py))?;
}
Ok(list)
}
#[pyfunction]
pub fn load_der_pkcs7_certificates<'py>(
py: Python<'py>,
data: &[u8],
) -> PyResult<Bound<'py, PyList>> {
let der_certs = synta_certificate::certs_from_pkcs7(data)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
der_certs_to_pylist(py, der_certs)
}
#[pyfunction]
pub fn load_pem_pkcs7_certificates<'py>(
py: Python<'py>,
data: &[u8],
) -> PyResult<Bound<'py, PyList>> {
let blocks = synta_certificate::pem_blocks(data);
if blocks.is_empty() {
return Err(pyo3::exceptions::PyValueError::new_err(
"no PEM block found in input",
));
}
let mut all_certs: Vec<Vec<u8>> = Vec::new();
for (_, der) in &blocks {
let certs = synta_certificate::certs_from_pkcs7(der)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
all_certs.extend(certs);
}
der_certs_to_pylist(py, all_certs)
}
#[pyfunction]
#[pyo3(signature = (data, password = None))]
pub fn load_pkcs12_certificates<'py>(
py: Python<'py>,
data: &[u8],
password: Option<&[u8]>,
) -> PyResult<Bound<'py, PyList>> {
let pw = password.unwrap_or(b"");
let der_certs =
synta_certificate::certs_from_pkcs12(data, pw, &synta_certificate::DefaultCrypto)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
der_certs_to_pylist(py, der_certs)
}
#[pyfunction]
#[pyo3(signature = (data, password = None))]
pub fn load_pkcs12_keys<'py>(
py: Python<'py>,
data: &[u8],
password: Option<&[u8]>,
) -> PyResult<Bound<'py, PyList>> {
let pw = password.unwrap_or(b"");
let der_keys = synta_certificate::keys_from_pkcs12(data, pw, &synta_certificate::DefaultCrypto)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
let list = PyList::empty(py);
for der in der_keys {
list.append(PyBytes::new(py, &der))?;
}
Ok(list)
}
#[pyfunction]
#[pyo3(signature = (data, password = None))]
pub fn load_pkcs12<'py>(
py: Python<'py>,
data: &[u8],
password: Option<&[u8]>,
) -> PyResult<Bound<'py, pyo3::types::PyTuple>> {
let pw = password.unwrap_or(b"");
let pki = synta_certificate::pki_from_pkcs12(data, pw, &synta_certificate::DefaultCrypto)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
let certs = der_certs_to_pylist(py, pki.certs)?;
let keys = PyList::empty(py);
for der in pki.keys {
keys.append(PyBytes::new(py, &der))?;
}
pyo3::types::PyTuple::new(py, [certs.into_any(), keys.into_any()])
}
#[pyfunction]
#[pyo3(signature = (data, password = None))]
pub fn read_pki_blocks<'py>(
py: Python<'py>,
data: &[u8],
password: Option<&[u8]>,
) -> PyResult<Bound<'py, PyList>> {
let pw = password.unwrap_or(b"");
let blocks = if password.is_some() {
synta_certificate::read_pki_blocks(
data,
pw,
Some(&synta_certificate::DefaultCrypto as &dyn synta_certificate::PkiDecryptor),
)
} else {
synta_certificate::read_pki_blocks(data, pw, None::<&dyn synta_certificate::PkiDecryptor>)
};
let blocks = blocks.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
let list = PyList::empty(py);
for (label, der) in blocks {
let tuple = pyo3::types::PyTuple::new(
py,
[
PyString::new(py, &label).into_any(),
PyBytes::new(py, &der).into_any(),
],
)?;
list.append(tuple)?;
}
Ok(list)
}
#[pyfunction]
pub fn encode_extended_key_usage<'py>(
py: Python<'py>,
oids: &Bound<'_, PyList>,
) -> PyResult<Bound<'py, PyBytes>> {
use synta::traits::Encode;
let mut eku: Vec<synta::ObjectIdentifier> = Vec::with_capacity(oids.len());
for item in oids.iter() {
eku.push(super::oid_from_pyany(&item)?);
}
let mut enc = synta::Encoder::new(synta::Encoding::Der);
eku.encode(&mut enc)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
let der = enc
.finish()
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
Ok(PyBytes::new(py, &der))
}
#[pyfunction]
pub fn encode_subject_alt_names<'py>(
py: Python<'py>,
names: &Bound<'_, PyList>,
) -> PyResult<Bound<'py, PyBytes>> {
let mut owned: Vec<(u32, Vec<u8>)> = Vec::with_capacity(names.len());
for item in names.iter() {
let tuple = item.cast::<pyo3::types::PyTuple>().map_err(|_| {
pyo3::exceptions::PyTypeError::new_err(
"each element must be a (tag_number, bytes) 2-tuple",
)
})?;
if tuple.len() != 2 {
return Err(pyo3::exceptions::PyTypeError::new_err(
"each element must be a (tag_number, bytes) 2-tuple",
));
}
let tag_num: u32 = tuple.get_item(0)?.extract()?;
let content: Vec<u8> = tuple.get_item(1)?.extract()?;
owned.push((tag_num, content));
}
let entries: Vec<(u32, &[u8])> = owned.iter().map(|(t, v)| (*t, v.as_slice())).collect();
let der = synta_certificate::encode_general_names(&entries).ok_or_else(|| {
pyo3::exceptions::PyValueError::new_err(
"failed to encode GeneralName entries (invalid content bytes or unsupported tag)",
)
})?;
Ok(PyBytes::new(py, &der))
}
#[pyfunction]
pub fn name_der_equal(a: &[u8], b: &[u8]) -> bool {
a == b
}
#[pyfunction]
#[pyo3(signature = (certificates, private_key = None, password = None, cipher = None, mac_algorithm = None))]
pub fn create_pkcs12<'py>(
py: Python<'py>,
certificates: &Bound<'_, PyList>,
private_key: Option<Bound<'_, PyAny>>,
password: Option<&[u8]>,
cipher: Option<&str>,
mac_algorithm: Option<&str>,
) -> PyResult<Bound<'py, PyBytes>> {
use pyo3::exceptions::PyValueError;
use synta_certificate::{
OpensslPkcs12Encryptor, Pkcs12Builder, Pkcs12Cipher, Pkcs12Config, Pkcs12HmacAlgorithm,
};
let pkcs12_cipher = match cipher.unwrap_or("aes256") {
"aes128" => Pkcs12Cipher::Aes128Cbc,
"aes256" => Pkcs12Cipher::Aes256Cbc,
"3des" => {
return Err(PyValueError::new_err(
"cipher '3des' is not supported; use 'aes128' or 'aes256'",
))
}
other => {
return Err(PyValueError::new_err(format!(
"unknown cipher: {other}; use 'aes128' or 'aes256'"
)))
}
};
let pkcs12_mac = match mac_algorithm.unwrap_or("sha256") {
"sha256" => Pkcs12HmacAlgorithm::Sha256,
"sha384" => Pkcs12HmacAlgorithm::Sha384,
"sha512" => Pkcs12HmacAlgorithm::Sha512,
"sha1" => {
return Err(PyValueError::new_err(
"mac_algorithm 'sha1' is not supported; use 'sha256', 'sha384', or 'sha512'",
))
}
other => {
return Err(PyValueError::new_err(format!(
"unknown mac_algorithm: {other}; use 'sha256', 'sha384', or 'sha512'"
)))
}
};
let config = Pkcs12Config {
cipher: pkcs12_cipher,
encryption_prf: pkcs12_mac,
mac_algorithm: pkcs12_mac,
..Pkcs12Config::default()
};
let encryptor = OpensslPkcs12Encryptor::with_config(config);
let password = password.unwrap_or(b"");
let mut builder = Pkcs12Builder::new();
for item in certificates.iter() {
if let Ok(py_cert) = item.cast::<PyCertificate>() {
builder = builder.certificate(py_cert.get().raw);
} else if let Ok(py_bytes) = item.cast::<PyBytes>() {
builder = builder.certificate(py_bytes.as_bytes());
} else {
return Err(pyo3::exceptions::PyTypeError::new_err(
"certificates must be synta.Certificate or bytes",
));
}
}
if let Some(key_arg) = private_key {
if let Ok(py_key) = key_arg.cast::<PyPrivateKey>() {
let key_der = py_key
.get()
.inner
.to_der()
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
builder = builder.private_key(&key_der);
} else if let Ok(py_bytes) = key_arg.cast::<PyBytes>() {
builder = builder.private_key(py_bytes.as_bytes());
} else {
return Err(pyo3::exceptions::PyTypeError::new_err(
"private_key must be synta.PrivateKey or bytes",
));
}
}
let pfx_der = builder
.build(password, &encryptor)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
Ok(PyBytes::new(py, &pfx_der))
}
fn py_dt_to_gen_time_str(dt: &Bound<'_, PyAny>) -> PyResult<String> {
if dt.getattr("tzinfo")?.is_none() {
return Err(pyo3::exceptions::PyValueError::new_err(
"datetime must be timezone-aware (e.g. datetime.timezone.utc)",
));
}
let ts: f64 = dt.call_method0("timestamp")?.extract()?;
let secs = ts.floor() as i64;
let gt = synta::GeneralizedTime::from_unix(secs).ok_or_else(|| {
pyo3::exceptions::PyValueError::new_err("datetime out of valid ASN.1 year range 1–9999")
})?;
Ok(gt.to_string())
}
#[pyclass(name = "CertificateListBuilder")]
pub struct PyCertificateListBuilder {
inner: synta_certificate::CertificateListBuilder,
}
#[pymethods]
impl PyCertificateListBuilder {
#[new]
fn new() -> Self {
Self {
inner: synta_certificate::CertificateListBuilder::new(),
}
}
fn issuer<'py>(slf: Bound<'py, Self>, name_der: &[u8]) -> Bound<'py, Self> {
let mut guard = slf.borrow_mut();
let old = std::mem::replace(
&mut guard.inner,
synta_certificate::CertificateListBuilder::new(),
);
guard.inner = old.issuer(name_der);
drop(guard);
slf
}
fn this_update<'py>(slf: Bound<'py, Self>, time: &str) -> Bound<'py, Self> {
let mut guard = slf.borrow_mut();
let old = std::mem::replace(
&mut guard.inner,
synta_certificate::CertificateListBuilder::new(),
);
guard.inner = old.this_update(time);
drop(guard);
slf
}
fn next_update<'py>(slf: Bound<'py, Self>, time: &str) -> Bound<'py, Self> {
let mut guard = slf.borrow_mut();
let old = std::mem::replace(
&mut guard.inner,
synta_certificate::CertificateListBuilder::new(),
);
guard.inner = old.next_update(time);
drop(guard);
slf
}
fn revoke<'py>(
slf: Bound<'py, Self>,
serial: &[u8],
revocation_date: &str,
reason: Option<u8>,
) -> Bound<'py, Self> {
let mut guard = slf.borrow_mut();
let old = std::mem::replace(
&mut guard.inner,
synta_certificate::CertificateListBuilder::new(),
);
guard.inner = old.revoke(serial, revocation_date, reason);
drop(guard);
slf
}
fn this_update_utc<'py>(
slf: Bound<'py, Self>,
dt: &Bound<'_, PyAny>,
) -> PyResult<Bound<'py, Self>> {
let time_str = py_dt_to_gen_time_str(dt)?;
let mut guard = slf.borrow_mut();
let old = std::mem::replace(
&mut guard.inner,
synta_certificate::CertificateListBuilder::new(),
);
guard.inner = old.this_update(&time_str);
drop(guard);
Ok(slf)
}
fn next_update_utc<'py>(
slf: Bound<'py, Self>,
dt: &Bound<'_, PyAny>,
) -> PyResult<Bound<'py, Self>> {
let time_str = py_dt_to_gen_time_str(dt)?;
let mut guard = slf.borrow_mut();
let old = std::mem::replace(
&mut guard.inner,
synta_certificate::CertificateListBuilder::new(),
);
guard.inner = old.next_update(&time_str);
drop(guard);
Ok(slf)
}
fn revoke_utc<'py>(
slf: Bound<'py, Self>,
serial: &[u8],
revocation_dt: &Bound<'_, PyAny>,
reason: Option<u8>,
) -> PyResult<Bound<'py, Self>> {
let time_str = py_dt_to_gen_time_str(revocation_dt)?;
let mut guard = slf.borrow_mut();
let old = std::mem::replace(
&mut guard.inner,
synta_certificate::CertificateListBuilder::new(),
);
guard.inner = old.revoke(serial, &time_str, reason);
drop(guard);
Ok(slf)
}
fn signature_algorithm<'py>(slf: Bound<'py, Self>, alg_der: &[u8]) -> Bound<'py, Self> {
let mut guard = slf.borrow_mut();
let old = std::mem::replace(
&mut guard.inner,
synta_certificate::CertificateListBuilder::new(),
);
guard.inner = old.signature_algorithm(alg_der);
drop(guard);
slf
}
fn add_extension<'py>(
slf: Bound<'py, Self>,
oid: Bound<'_, pyo3::PyAny>,
critical: bool,
value_der: &[u8],
) -> PyResult<Bound<'py, Self>> {
use std::str::FromStr;
let parsed_oid: synta::ObjectIdentifier = if let Ok(s) = oid.extract::<String>() {
synta::ObjectIdentifier::from_str(&s)
.map_err(|_| pyo3::exceptions::PyValueError::new_err(format!("invalid OID: {s}")))?
} else if let Ok(o) = oid.extract::<pyo3::PyRef<'_, PyObjectIdentifier>>() {
o.inner.clone()
} else {
return Err(pyo3::exceptions::PyTypeError::new_err(
"oid must be a str or ObjectIdentifier",
));
};
let mut guard = slf.borrow_mut();
let old = std::mem::replace(
&mut guard.inner,
synta_certificate::CertificateListBuilder::new(),
);
guard.inner = old.add_crl_extension(parsed_oid.components(), critical, value_der);
drop(guard);
Ok(slf)
}
fn build<'py>(&mut self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
let inner = std::mem::replace(
&mut self.inner,
synta_certificate::CertificateListBuilder::new(),
);
let der = inner
.build()
.map_err(pyo3::exceptions::PyValueError::new_err)?;
Ok(PyBytes::new(py, &der))
}
#[staticmethod]
fn assemble<'py>(
py: Python<'py>,
tbs_der: &[u8],
sig_alg_der: &[u8],
signature: &[u8],
) -> PyResult<Bound<'py, PyBytes>> {
let der =
synta_certificate::CertificateListBuilder::assemble(tbs_der, sig_alg_der, signature)
.map_err(pyo3::exceptions::PyValueError::new_err)?;
Ok(PyBytes::new(py, &der))
}
fn __repr__(&self) -> String {
"CertificateListBuilder()".to_string()
}
}
#[pyclass(frozen, name = "OCSPSingleResponse")]
pub struct PyOCSPSingleResponse {
pub(crate) hash_algorithm_der: Vec<u8>,
pub(crate) issuer_name_hash: Vec<u8>,
pub(crate) issuer_key_hash: Vec<u8>,
pub(crate) serial: Vec<u8>,
pub(crate) status: u8,
pub(crate) this_update: String,
pub(crate) next_update: Option<String>,
}
#[pymethods]
impl PyOCSPSingleResponse {
#[new]
#[pyo3(signature = (hash_algorithm_der, issuer_name_hash, issuer_key_hash, serial, status, this_update, next_update=None))]
fn new(
hash_algorithm_der: &[u8],
issuer_name_hash: &[u8],
issuer_key_hash: &[u8],
serial: &[u8],
status: u8,
this_update: &str,
next_update: Option<&str>,
) -> Self {
Self {
hash_algorithm_der: hash_algorithm_der.to_vec(),
issuer_name_hash: issuer_name_hash.to_vec(),
issuer_key_hash: issuer_key_hash.to_vec(),
serial: serial.to_vec(),
status,
this_update: this_update.to_owned(),
next_update: next_update.map(str::to_owned),
}
}
}
#[pyclass(name = "OCSPResponseBuilder")]
pub struct PyOCSPResponseBuilder {
inner: synta_certificate::OCSPResponseBuilder,
}
#[pymethods]
impl PyOCSPResponseBuilder {
#[new]
fn new() -> Self {
Self {
inner: synta_certificate::OCSPResponseBuilder::new(),
}
}
fn responder_name<'py>(slf: Bound<'py, Self>, name_der: &[u8]) -> Bound<'py, Self> {
let mut guard = slf.borrow_mut();
let old = std::mem::replace(
&mut guard.inner,
synta_certificate::OCSPResponseBuilder::new(),
);
guard.inner = old.responder_name(name_der);
drop(guard);
slf
}
fn responder_key_hash<'py>(slf: Bound<'py, Self>, key_hash: &[u8]) -> Bound<'py, Self> {
let mut guard = slf.borrow_mut();
let old = std::mem::replace(
&mut guard.inner,
synta_certificate::OCSPResponseBuilder::new(),
);
guard.inner = old.responder_key_hash(key_hash);
drop(guard);
slf
}
fn produced_at<'py>(slf: Bound<'py, Self>, time: &str) -> Bound<'py, Self> {
let mut guard = slf.borrow_mut();
let old = std::mem::replace(
&mut guard.inner,
synta_certificate::OCSPResponseBuilder::new(),
);
guard.inner = old.produced_at(time);
drop(guard);
slf
}
fn add_response<'py>(
slf: Bound<'py, Self>,
response: &PyOCSPSingleResponse,
) -> Bound<'py, Self> {
let mut guard = slf.borrow_mut();
let old = std::mem::replace(
&mut guard.inner,
synta_certificate::OCSPResponseBuilder::new(),
);
guard.inner = old.add_response(synta_certificate::SingleResponseSpec {
hash_algorithm_der: &response.hash_algorithm_der,
issuer_name_hash: &response.issuer_name_hash,
issuer_key_hash: &response.issuer_key_hash,
serial: &response.serial,
status: response.status,
this_update: &response.this_update,
next_update: response.next_update.as_deref(),
});
drop(guard);
slf
}
fn build_tbs<'py>(&mut self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
let inner = std::mem::replace(
&mut self.inner,
synta_certificate::OCSPResponseBuilder::new(),
);
let der = inner
.build_tbs()
.map_err(pyo3::exceptions::PyValueError::new_err)?;
Ok(PyBytes::new(py, &der))
}
#[staticmethod]
fn assemble<'py>(
py: Python<'py>,
tbs_der: &[u8],
sig_alg_der: &[u8],
signature: &[u8],
) -> PyResult<Bound<'py, PyBytes>> {
let der = synta_certificate::OCSPResponseBuilder::assemble(tbs_der, sig_alg_der, signature)
.map_err(pyo3::exceptions::PyValueError::new_err)?;
Ok(PyBytes::new(py, &der))
}
fn __repr__(&self) -> String {
"OCSPResponseBuilder()".to_string()
}
}
#[pyclass(frozen, name = "OCSPCertIDSpec")]
pub struct PyOCSPCertIDSpec {
pub(crate) hash_algorithm_der: Vec<u8>,
pub(crate) issuer_name_hash: Vec<u8>,
pub(crate) issuer_key_hash: Vec<u8>,
pub(crate) serial: Vec<u8>,
}
#[pymethods]
impl PyOCSPCertIDSpec {
#[new]
fn new(
hash_algorithm_der: &[u8],
issuer_name_hash: &[u8],
issuer_key_hash: &[u8],
serial: &[u8],
) -> Self {
Self {
hash_algorithm_der: hash_algorithm_der.to_vec(),
issuer_name_hash: issuer_name_hash.to_vec(),
issuer_key_hash: issuer_key_hash.to_vec(),
serial: serial.to_vec(),
}
}
}
#[pyclass(name = "OCSPRequestBuilder")]
pub struct PyOCSPRequestBuilder {
inner: synta_certificate::OCSPRequestBuilder,
built: bool,
}
#[pymethods]
impl PyOCSPRequestBuilder {
#[new]
fn new() -> Self {
Self {
inner: synta_certificate::OCSPRequestBuilder::new(),
built: false,
}
}
fn requestor_name<'py>(slf: Bound<'py, Self>, name_der: &[u8]) -> Bound<'py, Self> {
let mut guard = slf.borrow_mut();
let old = std::mem::replace(
&mut guard.inner,
synta_certificate::OCSPRequestBuilder::new(),
);
guard.inner = old.requestor_name(name_der);
drop(guard);
slf
}
fn add_request<'py>(slf: Bound<'py, Self>, spec: &PyOCSPCertIDSpec) -> Bound<'py, Self> {
let mut guard = slf.borrow_mut();
let old = std::mem::replace(
&mut guard.inner,
synta_certificate::OCSPRequestBuilder::new(),
);
guard.inner = old.add_request(synta_certificate::CertIDSpec {
hash_algorithm_der: &spec.hash_algorithm_der,
issuer_name_hash: &spec.issuer_name_hash,
issuer_key_hash: &spec.issuer_key_hash,
serial: &spec.serial,
});
drop(guard);
slf
}
fn build_tbs<'py>(&mut self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
if self.built {
return Err(pyo3::exceptions::PyValueError::new_err(
"build_tbs already called on this OCSPRequestBuilder",
));
}
self.built = true;
let inner = std::mem::replace(
&mut self.inner,
synta_certificate::OCSPRequestBuilder::new(),
);
let der = inner
.build_tbs()
.map_err(pyo3::exceptions::PyValueError::new_err)?;
Ok(PyBytes::new(py, &der))
}
fn build_tbs_inner<'py>(&mut self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
if self.built {
return Err(pyo3::exceptions::PyValueError::new_err(
"build_tbs_inner already called on this OCSPRequestBuilder",
));
}
self.built = true;
let inner = std::mem::replace(
&mut self.inner,
synta_certificate::OCSPRequestBuilder::new(),
);
let der = inner
.build_tbs_inner()
.map_err(pyo3::exceptions::PyValueError::new_err)?;
Ok(PyBytes::new(py, &der))
}
#[staticmethod]
fn assemble<'py>(
py: Python<'py>,
tbs_der: &[u8],
sig_alg_der: &[u8],
signature: &[u8],
) -> PyResult<Bound<'py, PyBytes>> {
let der = synta_certificate::OCSPRequestBuilder::assemble(tbs_der, sig_alg_der, signature)
.map_err(pyo3::exceptions::PyValueError::new_err)?;
Ok(PyBytes::new(py, &der))
}
fn __repr__(&self) -> String {
"OCSPRequestBuilder()".to_string()
}
}