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>>,
}
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(),
})
}
#[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))
}
fn to_der<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
self._data.clone_ref(py).into_bound(py)
}
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 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 revoked_count(&self) -> PyResult<usize> {
Ok(self
.crl()?
.tbs_cert_list
.revoked_certificates
.as_ref()
.map(|v| v.len())
.unwrap_or(0))
}
fn to_der<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
self._data.clone_ref(py).into_bound(py)
}
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 __repr__(&self) -> PyResult<String> {
Ok(format!(
"OCSPResponse(status={})",
ocsp_status_str(self.ocsp()?.response_status),
))
}
}
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"");
#[cfg(feature = "openssl")]
let der_certs =
synta_certificate::certs_from_pkcs12(data, pw, &synta_certificate::OpensslDecryptor)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
#[cfg(not(feature = "openssl"))]
let der_certs = synta_certificate::certs_from_pkcs12(data, pw, &synta_certificate::NoCrypto)
.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"");
#[cfg(feature = "openssl")]
let der_keys =
synta_certificate::keys_from_pkcs12(data, pw, &synta_certificate::OpensslDecryptor)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
#[cfg(not(feature = "openssl"))]
let der_keys = synta_certificate::keys_from_pkcs12(data, pw, &synta_certificate::NoCrypto)
.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"");
#[cfg(feature = "openssl")]
let pki = synta_certificate::pki_from_pkcs12(data, pw, &synta_certificate::OpensslDecryptor)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
#[cfg(not(feature = "openssl"))]
let pki = synta_certificate::pki_from_pkcs12(data, pw, &synta_certificate::NoCrypto)
.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"");
#[cfg(feature = "openssl")]
let blocks = if password.is_some() {
synta_certificate::read_pki_blocks(
data,
pw,
Some(&synta_certificate::OpensslDecryptor as &dyn synta_certificate::PkiDecryptor),
)
} else {
synta_certificate::read_pki_blocks(data, pw, None::<&dyn synta_certificate::PkiDecryptor>)
};
#[cfg(not(feature = "openssl"))]
let blocks =
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))]
pub fn create_pkcs12<'py>(
py: Python<'py>,
certificates: &Bound<'_, PyList>,
private_key: Option<Bound<'_, PyAny>>,
password: Option<&[u8]>,
) -> PyResult<Bound<'py, PyBytes>> {
use synta_certificate::Pkcs12Builder;
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",
));
}
}
#[cfg(feature = "openssl")]
let pfx_der = builder
.build(password, &synta_certificate::OpensslPkcs12Encryptor::new())
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
#[cfg(not(feature = "openssl"))]
let pfx_der = {
use synta_certificate::NoPkcs12Encryptor;
builder
.build(password, &NoPkcs12Encryptor)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?
};
Ok(PyBytes::new(py, &pfx_der))
}