use std::str::FromStr;
use std::sync::OnceLock;
use pyo3::prelude::*;
use pyo3::types::{PyBytes, PyList, PyString};
use pyo3::PyClass;
use synta::traits::Encode;
use synta::{Decoder, Encoding, ObjectIdentifier};
use synta_certificate::{Certificate, Time};
use crate::types::PyObjectIdentifier;
fn encode_element_opt<'py>(
py: Python<'py>,
elem: Option<&synta::Element<'_>>,
) -> PyResult<Option<Bound<'py, PyBytes>>> {
match elem {
None => Ok(None),
Some(e) => {
let mut encoder = synta::Encoder::new(Encoding::Der);
e.encode(&mut encoder)
.map_err(|err| pyo3::exceptions::PyValueError::new_err(format!("{err}")))?;
let bytes = encoder
.finish()
.map_err(|err| pyo3::exceptions::PyValueError::new_err(format!("{err}")))?;
Ok(Some(PyBytes::new(py, &bytes)))
}
}
}
fn decode_name_debug(raw: &[u8]) -> String {
synta_certificate::name::format_dn(raw)
}
fn oid_from_pyany(obj: &Bound<'_, PyAny>) -> PyResult<ObjectIdentifier> {
if let Ok(oid_ref) = obj.extract::<PyRef<PyObjectIdentifier>>() {
return Ok(oid_ref.inner.clone());
}
if let Ok(s) = obj.extract::<String>() {
return ObjectIdentifier::from_str(&s)
.map_err(|_| pyo3::exceptions::PyValueError::new_err(format!("invalid OID: {s}")));
}
Err(pyo3::exceptions::PyTypeError::new_err(
"oid must be a str or ObjectIdentifier",
))
}
fn pem_blocks_to_pyobject<'py, F>(
py: Python<'py>,
data: &[u8],
make_obj: F,
) -> PyResult<Bound<'py, PyAny>>
where
F: for<'a> Fn(Python<'py>, Bound<'a, PyBytes>) -> PyResult<Bound<'py, PyAny>>,
{
let blocks = synta_certificate::pem_blocks(data);
match blocks.len() {
0 => Err(pyo3::exceptions::PyValueError::new_err(
"no PEM block found in input",
)),
1 => make_obj(py, PyBytes::new(py, &blocks[0].1)),
_ => {
let list = PyList::empty(py);
for (_, der) in &blocks {
list.append(make_obj(py, PyBytes::new(py, der))?)?;
}
Ok(list.into_any())
}
}
}
fn pyobject_to_pem<'py, T, D>(
py: Python<'py>,
label: &str,
obj_or_list: &Bound<'_, PyAny>,
get_der: D,
) -> PyResult<Bound<'py, PyBytes>>
where
T: PyClass,
D: for<'r> Fn(&'r T) -> &'r [u8],
{
let mut pem: Vec<u8> = Vec::new();
if let Ok(list) = obj_or_list.cast::<PyList>() {
for item in list.iter() {
let bound_t = item.cast::<T>().map_err(|_| {
pyo3::exceptions::PyTypeError::new_err(format!(
"list items must be {label} objects"
))
})?;
let borrow = bound_t.borrow();
pem.extend_from_slice(&synta_certificate::der_to_pem(label, get_der(&borrow)));
}
} else {
let bound_t = obj_or_list.cast::<T>().map_err(|_| {
pyo3::exceptions::PyTypeError::new_err(format!(
"expected a {label} object or list[{label}]"
))
})?;
let borrow = bound_t.borrow();
pem.extend_from_slice(&synta_certificate::der_to_pem(label, get_der(&borrow)));
}
Ok(PyBytes::new(py, &pem))
}
#[pyclass(frozen, name = "Certificate")]
pub struct PyCertificate {
_data: Py<PyBytes>,
raw: &'static [u8],
inner: OnceLock<Box<Certificate<'static>>>,
tbs_range: std::ops::Range<usize>,
issuer_cache: OnceLock<Py<PyString>>,
subject_cache: OnceLock<Py<PyString>>,
signature_algorithm_cache: OnceLock<Py<PyString>>,
not_before_cache: OnceLock<Py<PyString>>,
not_after_cache: OnceLock<Py<PyString>>,
public_key_algorithm_cache: OnceLock<Py<PyString>>,
serial_number_cache: OnceLock<Py<PyAny>>,
signature_value_cache: OnceLock<Py<PyBytes>>,
public_key_cache: OnceLock<Py<PyBytes>>,
tbs_bytes_cache: OnceLock<Py<PyBytes>>,
signature_algorithm_oid_cache: OnceLock<Py<PyObjectIdentifier>>,
public_key_algorithm_oid_cache: OnceLock<Py<PyObjectIdentifier>>,
signature_algorithm_params_cache: OnceLock<Option<Py<PyBytes>>>,
public_key_algorithm_params_cache: OnceLock<Option<Py<PyBytes>>>,
extensions_der_cache: OnceLock<Option<Py<PyBytes>>>,
issuer_raw_der_cache: OnceLock<Py<PyBytes>>,
subject_raw_der_cache: OnceLock<Py<PyBytes>>,
}
impl PyCertificate {
fn cert(&self) -> PyResult<&Certificate<'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!("Certificate DER decode failed: {e}"))
})?;
let _ = self.inner.set(Box::new(decoded));
Ok(self.inner.get().unwrap().as_ref())
}
}
#[pymethods]
impl PyCertificate {
#[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 tbs_range = {
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}")))?;
let tbs_start = d.position();
d.read_tag()
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
let tbs_len = d
.read_length()
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
let tbs_content_len = tbs_len
.definite()
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
tbs_start..(d.position() + tbs_content_len)
};
Ok(Self {
_data: py_bytes,
raw,
inner: OnceLock::new(),
tbs_range,
issuer_cache: OnceLock::new(),
subject_cache: OnceLock::new(),
signature_algorithm_cache: OnceLock::new(),
not_before_cache: OnceLock::new(),
not_after_cache: OnceLock::new(),
public_key_algorithm_cache: OnceLock::new(),
serial_number_cache: OnceLock::new(),
signature_value_cache: OnceLock::new(),
public_key_cache: OnceLock::new(),
tbs_bytes_cache: OnceLock::new(),
signature_algorithm_oid_cache: OnceLock::new(),
public_key_algorithm_oid_cache: OnceLock::new(),
signature_algorithm_params_cache: OnceLock::new(),
public_key_algorithm_params_cache: OnceLock::new(),
extensions_der_cache: OnceLock::new(),
issuer_raw_der_cache: OnceLock::new(),
subject_raw_der_cache: OnceLock::new(),
})
}
#[staticmethod]
fn full_from_der(py: Python<'_>, data: Bound<'_, PyBytes>) -> PyResult<Self> {
let cert = Self::from_der(py, data)?;
cert.cert()?;
Ok(cert)
}
#[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())
})
}
fn to_pyca<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
let m = py.import("cryptography.x509").map_err(|_| {
pyo3::exceptions::PyImportError::new_err(
"the 'cryptography' package is required; install it with: pip install cryptography",
)
})?;
m.call_method1(
"load_der_x509_certificate",
(self._data.clone_ref(py).into_bound(py),),
)
}
#[staticmethod]
fn from_pyca(py: Python<'_>, pyca_cert: Bound<'_, PyAny>) -> PyResult<Self> {
if let Ok(attr) = pyca_cert.getattr("_synta_der_bytes") {
if let Ok(der_bytes) = attr.cast_into::<PyBytes>() {
return Self::from_der(py, der_bytes);
}
}
let ser = py
.import("cryptography.hazmat.primitives.serialization")
.map_err(|_| {
pyo3::exceptions::PyImportError::new_err(
"the 'cryptography' package is required; install it with: pip install cryptography",
)
})?;
let der_bytes: Bound<'_, PyBytes> = pyca_cert
.call_method1("public_bytes", (ser.getattr("Encoding")?.getattr("DER")?,))?
.cast_into()?;
Self::from_der(py, der_bytes)
}
#[staticmethod]
fn to_pem<'py>(
py: Python<'py>,
obj_or_list: Bound<'_, PyAny>,
) -> PyResult<Bound<'py, PyBytes>> {
pyobject_to_pem::<Self, _>(py, "CERTIFICATE", &obj_or_list, |c| c.raw)
}
#[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 serial = &self.cert()?.tbs_certificate.serial_number;
let py_int: Py<PyAny> = if let Ok(v) = serial.as_i64() {
v.into_pyobject(py)?.into_any().unbind()
} else if let Ok(v) = serial.as_i128() {
v.into_pyobject(py)?.into_any().unbind()
} else {
let bytes_obj = PyBytes::new(py, 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))
}
#[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 = decode_name_debug(self.cert()?.tbs_certificate.issuer.as_bytes());
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 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 = decode_name_debug(self.cert()?.tbs_certificate.subject.as_bytes());
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 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.cert()?.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_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.cert()?.signature_value.as_bytes()).unbind();
let _ = self.signature_value_cache.set(py_bytes.clone_ref(py));
Ok(py_bytes.into_bound(py))
}
#[getter]
fn not_before<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyString>> {
if let Some(cached) = self.not_before_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let s = match &self.cert()?.tbs_certificate.validity.not_before {
Time::UtcTime(t) => t.to_string(),
Time::GeneralTime(t) => t.to_string(),
};
let py_str = PyString::new(py, &s).unbind();
let _ = self.not_before_cache.set(py_str.clone_ref(py));
Ok(py_str.into_bound(py))
}
#[getter]
fn not_after<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyString>> {
if let Some(cached) = self.not_after_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let s = match &self.cert()?.tbs_certificate.validity.not_after {
Time::UtcTime(t) => t.to_string(),
Time::GeneralTime(t) => t.to_string(),
};
let py_str = PyString::new(py, &s).unbind();
let _ = self.not_after_cache.set(py_str.clone_ref(py));
Ok(py_str.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
.cert()?
.tbs_certificate
.subject_public_key_info
.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<'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.cert()?
.tbs_certificate
.subject_public_key_info
.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 version(&self) -> PyResult<Option<i64>> {
Ok(self
.cert()?
.tbs_certificate
.version
.as_ref()
.and_then(|v| v.as_i64().ok()))
}
fn to_der<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
self._data.clone_ref(py).into_bound(py)
}
#[getter]
fn tbs_bytes<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
self.tbs_bytes_cache
.get_or_init(|| {
let data = self._data.bind(py).as_bytes();
PyBytes::new(py, &data[self.tbs_range.clone()]).unbind()
})
.clone_ref(py)
.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.cert()?.signature_algorithm.algorithm.clone()),
)?;
let _ = self.signature_algorithm_oid_cache.set(obj.clone_ref(py));
Ok(obj.into_bound(py))
}
#[getter]
fn signature_algorithm_params<'py>(
&self,
py: Python<'py>,
) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.signature_algorithm_params_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed =
encode_element_opt(py, self.cert()?.signature_algorithm.parameters.as_ref())?;
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.signature_algorithm_params_cache.set(to_store);
Ok(computed)
}
#[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.cert()?
.tbs_certificate
.subject_public_key_info
.algorithm
.algorithm
.clone(),
),
)?;
let _ = self.public_key_algorithm_oid_cache.set(obj.clone_ref(py));
Ok(obj.into_bound(py))
}
#[getter]
fn public_key_algorithm_params<'py>(
&self,
py: Python<'py>,
) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.public_key_algorithm_params_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed = encode_element_opt(
py,
self.cert()?
.tbs_certificate
.subject_public_key_info
.algorithm
.parameters
.as_ref(),
)?;
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.public_key_algorithm_params_cache.set(to_store);
Ok(computed)
}
#[getter]
fn extensions_der<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.extensions_der_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed = self
.cert()?
.tbs_certificate
.extensions
.as_ref()
.map(|raw: &synta::RawDer<'_>| PyBytes::new(py, raw.as_bytes()));
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.extensions_der_cache.set(to_store);
Ok(computed)
}
fn get_extension_value_der<'py>(
&self,
py: Python<'py>,
oid: &Bound<'_, PyAny>,
) -> PyResult<Option<Bound<'py, PyBytes>>> {
let target = oid_from_pyany(oid)?;
let raw = match self.cert()?.tbs_certificate.extensions.as_ref() {
Some(r) => r,
None => return Ok(None),
};
for ext in &synta_certificate::decode_extensions(raw.as_bytes()) {
if ext.extn_id == target {
return Ok(Some(PyBytes::new(py, ext.extn_value.as_bytes())));
}
}
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);
for (tag_num, content) in self.cert()?.subject_alt_names() {
let tuple = PyTuple::new(
py,
[
tag_num.into_pyobject(py)?.into_any(),
PyBytes::new(py, &content).into_any(),
],
)?;
list.append(tuple)?;
}
Ok(list)
}
#[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 py_bytes = PyBytes::new(py, self.cert()?.tbs_certificate.issuer.as_bytes()).unbind();
let _ = self.issuer_raw_der_cache.set(py_bytes.clone_ref(py));
Ok(py_bytes.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 py_bytes = PyBytes::new(py, self.cert()?.tbs_certificate.subject.as_bytes()).unbind();
let _ = self.subject_raw_der_cache.set(py_bytes.clone_ref(py));
Ok(py_bytes.into_bound(py))
}
fn __repr__(&self) -> PyResult<String> {
let cert = self.cert()?;
let serial = &cert.tbs_certificate.serial_number;
let serial_str = if let Ok(v) = serial.as_i64() {
v.to_string()
} else {
format!("<{} bytes>", serial.as_bytes().len())
};
Ok(format!(
"Certificate(subject={:?}, serial={})",
decode_name_debug(cert.tbs_certificate.subject.as_bytes()),
serial_str,
))
}
}
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 {
_data: Py<PyBytes>,
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),
))
}
}
#[pyclass(frozen, name = "CertificateList")]
pub struct PyCrl {
_data: Py<PyBytes>,
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 {
_data: Py<PyBytes>,
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::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)
}
#[pyclass(frozen, name = "KEMRecipientInfo")]
pub struct PyKEMRecipientInfo {
_data: Py<PyBytes>,
raw: &'static [u8],
inner: OnceLock<Box<synta_certificate::cms_kem_types::KEMRecipientInfo<'static>>>,
kem_algorithm_oid_cache: OnceLock<Py<PyObjectIdentifier>>,
kdf_algorithm_oid_cache: OnceLock<Py<PyObjectIdentifier>>,
key_encryption_algorithm_oid_cache: OnceLock<Py<PyObjectIdentifier>>,
kem_algorithm_params_cache: OnceLock<Option<Py<PyBytes>>>,
kdf_algorithm_params_cache: OnceLock<Option<Py<PyBytes>>>,
key_encryption_algorithm_params_cache: OnceLock<Option<Py<PyBytes>>>,
recipient_id_cache: OnceLock<Py<PyBytes>>,
kem_ciphertext_cache: OnceLock<Py<PyBytes>>,
encrypted_key_cache: OnceLock<Py<PyBytes>>,
ukm_cache: OnceLock<Option<Py<PyBytes>>>,
}
impl PyKEMRecipientInfo {
fn kemri(&self) -> PyResult<&synta_certificate::cms_kem_types::KEMRecipientInfo<'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!(
"KEMRecipientInfo DER decode failed: {e}"
))
})?;
let _ = self.inner.set(Box::new(decoded));
Ok(self.inner.get().unwrap().as_ref())
}
}
#[pymethods]
impl PyKEMRecipientInfo {
#[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(),
kem_algorithm_oid_cache: OnceLock::new(),
kdf_algorithm_oid_cache: OnceLock::new(),
key_encryption_algorithm_oid_cache: OnceLock::new(),
kem_algorithm_params_cache: OnceLock::new(),
kdf_algorithm_params_cache: OnceLock::new(),
key_encryption_algorithm_params_cache: OnceLock::new(),
recipient_id_cache: OnceLock::new(),
kem_ciphertext_cache: OnceLock::new(),
encrypted_key_cache: OnceLock::new(),
ukm_cache: OnceLock::new(),
})
}
fn to_der<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
self._data.clone_ref(py).into_bound(py)
}
#[getter]
fn version(&self) -> PyResult<i64> {
Ok(self.kemri()?.version.as_i64().unwrap_or(0))
}
#[getter]
fn recipient_id<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
if let Some(cached) = self.recipient_id_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let py_bytes = PyBytes::new(py, self.kemri()?.rid.as_bytes()).unbind();
let _ = self.recipient_id_cache.set(py_bytes.clone_ref(py));
Ok(py_bytes.into_bound(py))
}
#[getter]
fn kem_algorithm_oid<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyObjectIdentifier>> {
if let Some(cached) = self.kem_algorithm_oid_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let obj = Py::new(
py,
PyObjectIdentifier::from_oid(self.kemri()?.kem.algorithm.clone()),
)?;
let _ = self.kem_algorithm_oid_cache.set(obj.clone_ref(py));
Ok(obj.into_bound(py))
}
#[getter]
fn kem_algorithm_params<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.kem_algorithm_params_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed = encode_element_opt(py, self.kemri()?.kem.parameters.as_ref())?;
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.kem_algorithm_params_cache.set(to_store);
Ok(computed)
}
#[getter]
fn kem_ciphertext<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
if let Some(cached) = self.kem_ciphertext_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let py_bytes = PyBytes::new(py, self.kemri()?.kemct.as_bytes()).unbind();
let _ = self.kem_ciphertext_cache.set(py_bytes.clone_ref(py));
Ok(py_bytes.into_bound(py))
}
#[getter]
fn kdf_algorithm_oid<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyObjectIdentifier>> {
if let Some(cached) = self.kdf_algorithm_oid_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let obj = Py::new(
py,
PyObjectIdentifier::from_oid(self.kemri()?.kdf.algorithm.clone()),
)?;
let _ = self.kdf_algorithm_oid_cache.set(obj.clone_ref(py));
Ok(obj.into_bound(py))
}
#[getter]
fn kdf_algorithm_params<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.kdf_algorithm_params_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed = encode_element_opt(py, self.kemri()?.kdf.parameters.as_ref())?;
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.kdf_algorithm_params_cache.set(to_store);
Ok(computed)
}
#[getter]
fn kek_length(&self) -> PyResult<i64> {
Ok(self.kemri()?.kek_length.as_i64().unwrap_or(0))
}
#[getter]
fn ukm<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.ukm_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed = self
.kemri()?
.ukm
.as_ref()
.map(|u| PyBytes::new(py, u.as_bytes()));
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.ukm_cache.set(to_store);
Ok(computed)
}
#[getter]
fn key_encryption_algorithm_oid<'py>(
&self,
py: Python<'py>,
) -> PyResult<Bound<'py, PyObjectIdentifier>> {
if let Some(cached) = self.key_encryption_algorithm_oid_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let obj = Py::new(
py,
PyObjectIdentifier::from_oid(self.kemri()?.wrap.algorithm.clone()),
)?;
let _ = self
.key_encryption_algorithm_oid_cache
.set(obj.clone_ref(py));
Ok(obj.into_bound(py))
}
#[getter]
fn key_encryption_algorithm_params<'py>(
&self,
py: Python<'py>,
) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.key_encryption_algorithm_params_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed = encode_element_opt(py, self.kemri()?.wrap.parameters.as_ref())?;
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.key_encryption_algorithm_params_cache.set(to_store);
Ok(computed)
}
#[getter]
fn encrypted_key<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
if let Some(cached) = self.encrypted_key_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let py_bytes = PyBytes::new(py, self.kemri()?.encrypted_key.as_bytes()).unbind();
let _ = self.encrypted_key_cache.set(py_bytes.clone_ref(py));
Ok(py_bytes.into_bound(py))
}
fn __repr__(&self) -> PyResult<String> {
let kemri = self.kemri()?;
Ok(format!(
"KEMRecipientInfo(kem={}, kdf={}, kek_length={})",
kemri.kem.algorithm,
kemri.kdf.algorithm,
kemri.kek_length.as_i64().unwrap_or(0),
))
}
}
#[pyclass(frozen, name = "CMSORIforKEMOtherInfo")]
pub struct PyCMSORIforKEMOtherInfo {
_data: Py<PyBytes>,
raw: &'static [u8],
inner: OnceLock<Box<synta_certificate::cms_kem_types::CMSORIforKEMOtherInfo<'static>>>,
key_encryption_algorithm_oid_cache: OnceLock<Py<PyObjectIdentifier>>,
key_encryption_algorithm_params_cache: OnceLock<Option<Py<PyBytes>>>,
ukm_cache: OnceLock<Option<Py<PyBytes>>>,
}
impl PyCMSORIforKEMOtherInfo {
fn info(&self) -> PyResult<&synta_certificate::cms_kem_types::CMSORIforKEMOtherInfo<'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!(
"CMSORIforKEMOtherInfo DER decode failed: {e}"
))
})?;
let _ = self.inner.set(Box::new(decoded));
Ok(self.inner.get().unwrap().as_ref())
}
}
#[pymethods]
impl PyCMSORIforKEMOtherInfo {
#[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(),
key_encryption_algorithm_oid_cache: OnceLock::new(),
key_encryption_algorithm_params_cache: OnceLock::new(),
ukm_cache: OnceLock::new(),
})
}
fn to_der<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
self._data.clone_ref(py).into_bound(py)
}
#[getter]
fn key_encryption_algorithm_oid<'py>(
&self,
py: Python<'py>,
) -> PyResult<Bound<'py, PyObjectIdentifier>> {
if let Some(cached) = self.key_encryption_algorithm_oid_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let obj = Py::new(
py,
PyObjectIdentifier::from_oid(self.info()?.wrap.algorithm.clone()),
)?;
let _ = self
.key_encryption_algorithm_oid_cache
.set(obj.clone_ref(py));
Ok(obj.into_bound(py))
}
#[getter]
fn key_encryption_algorithm_params<'py>(
&self,
py: Python<'py>,
) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.key_encryption_algorithm_params_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed = encode_element_opt(py, self.info()?.wrap.parameters.as_ref())?;
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.key_encryption_algorithm_params_cache.set(to_store);
Ok(computed)
}
#[getter]
fn kek_length(&self) -> PyResult<i64> {
Ok(self.info()?.kek_length.as_i64().unwrap_or(0))
}
#[getter]
fn ukm<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.ukm_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed = self
.info()?
.ukm
.as_ref()
.map(|u| PyBytes::new(py, u.as_bytes()));
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.ukm_cache.set(to_store);
Ok(computed)
}
fn __repr__(&self) -> PyResult<String> {
let info = self.info()?;
Ok(format!(
"CMSORIforKEMOtherInfo(wrap={}, kek_length={})",
info.wrap.algorithm,
info.kek_length.as_i64().unwrap_or(0),
))
}
}
#[pyclass(frozen, name = "ContentInfo")]
pub struct PyContentInfo {
_data: Py<PyBytes>,
raw: &'static [u8],
inner: OnceLock<Box<synta_certificate::pkcs7_types::ContentInfo<'static>>>,
content_type_oid_cache: OnceLock<Py<PyObjectIdentifier>>,
content_cache: OnceLock<Py<PyBytes>>,
}
impl PyContentInfo {
fn content_info(&self) -> PyResult<&synta_certificate::pkcs7_types::ContentInfo<'static>> {
if let Some(v) = self.inner.get() {
return Ok(v.as_ref());
}
let mut decoder = Decoder::new(self.raw, Encoding::Ber);
let decoded = decoder.decode().map_err(|e| {
pyo3::exceptions::PyValueError::new_err(format!("ContentInfo BER decode failed: {e}"))
})?;
let _ = self.inner.set(Box::new(decoded));
Ok(self.inner.get().unwrap().as_ref())
}
}
#[pymethods]
impl PyContentInfo {
#[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::Ber);
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(),
content_type_oid_cache: OnceLock::new(),
content_cache: OnceLock::new(),
})
}
fn to_der<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
self._data.clone_ref(py).into_bound(py)
}
#[getter]
fn content_type_oid<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyObjectIdentifier>> {
if let Some(cached) = self.content_type_oid_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let obj = Py::new(
py,
PyObjectIdentifier::from_oid(self.content_info()?.content_type.clone()),
)?;
let _ = self.content_type_oid_cache.set(obj.clone_ref(py));
Ok(obj.into_bound(py))
}
#[getter]
fn content<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
if let Some(cached) = self.content_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let py_bytes = PyBytes::new(py, self.content_info()?.content.as_bytes()).unbind();
let _ = self.content_cache.set(py_bytes.clone_ref(py));
Ok(py_bytes.into_bound(py))
}
fn __repr__(&self) -> PyResult<String> {
Ok(format!(
"ContentInfo(content_type={})",
self.content_info()?.content_type,
))
}
}
#[pyclass(frozen, name = "IssuerAndSerialNumber")]
pub struct PyIssuerAndSerialNumber {
_data: Py<PyBytes>,
raw: &'static [u8],
inner: OnceLock<Box<synta_certificate::cms_2010_types::IssuerAndSerialNumber<'static>>>,
issuer_cache: OnceLock<Py<PyString>>,
issuer_raw_der_cache: OnceLock<Py<PyBytes>>,
serial_number_cache: OnceLock<Py<PyAny>>,
}
impl PyIssuerAndSerialNumber {
fn ias(&self) -> PyResult<&synta_certificate::cms_2010_types::IssuerAndSerialNumber<'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!(
"IssuerAndSerialNumber DER decode failed: {e}"
))
})?;
let _ = self.inner.set(Box::new(decoded));
Ok(self.inner.get().unwrap().as_ref())
}
}
#[pymethods]
impl PyIssuerAndSerialNumber {
#[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(),
serial_number_cache: OnceLock::new(),
})
}
fn to_der<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
self._data.clone_ref(py).into_bound(py)
}
#[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.ias()?.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.ias()?.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 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 serial = &self.ias()?.serial_number;
let py_int: Py<PyAny> = if let Ok(v) = serial.as_i64() {
v.into_pyobject(py)?.into_any().unbind()
} else if let Ok(v) = serial.as_i128() {
v.into_pyobject(py)?.into_any().unbind()
} else {
let bytes_obj = PyBytes::new(py, 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) -> PyResult<String> {
let ias = self.ias()?;
let serial = &ias.serial_number;
Ok(format!(
"IssuerAndSerialNumber(issuer={:?}, serial={})",
name_to_dn_string(&ias.issuer),
serial
.as_i64()
.map(|v| v.to_string())
.unwrap_or_else(|_| format!("<{} bytes>", serial.as_bytes().len())),
))
}
}
#[pyclass(frozen, name = "SignedData")]
pub struct PySignedData {
_data: Py<PyBytes>,
raw: &'static [u8],
inner: OnceLock<Box<synta_certificate::cms_rfc5652_types::SignedData<'static>>>,
encap_content_type_cache: OnceLock<Py<PyObjectIdentifier>>,
encap_content_cache: OnceLock<Option<Py<PyBytes>>>,
certificates_cache: OnceLock<Option<Py<PyBytes>>>,
crls_cache: OnceLock<Option<Py<PyBytes>>>,
signer_infos_cache: OnceLock<Py<PyList>>,
}
impl PySignedData {
fn signed_data(&self) -> PyResult<&synta_certificate::cms_rfc5652_types::SignedData<'static>> {
if let Some(v) = self.inner.get() {
return Ok(v.as_ref());
}
let mut decoder = Decoder::new(self.raw, Encoding::Ber);
let decoded = decoder.decode().map_err(|e| {
pyo3::exceptions::PyValueError::new_err(format!("SignedData BER decode failed: {e}"))
})?;
let _ = self.inner.set(Box::new(decoded));
Ok(self.inner.get().unwrap().as_ref())
}
}
#[pymethods]
impl PySignedData {
#[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::Ber);
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(),
encap_content_type_cache: OnceLock::new(),
encap_content_cache: OnceLock::new(),
certificates_cache: OnceLock::new(),
crls_cache: OnceLock::new(),
signer_infos_cache: OnceLock::new(),
})
}
fn to_der<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
self._data.clone_ref(py).into_bound(py)
}
#[getter]
fn version(&self) -> PyResult<i64> {
Ok(self.signed_data()?.version.as_i64().unwrap_or(0))
}
#[getter]
fn encap_content_type<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyObjectIdentifier>> {
if let Some(cached) = self.encap_content_type_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let obj = Py::new(
py,
PyObjectIdentifier::from_oid(
self.signed_data()?
.encap_content_info
.e_content_type
.clone(),
),
)?;
let _ = self.encap_content_type_cache.set(obj.clone_ref(py));
Ok(obj.into_bound(py))
}
#[getter]
fn encap_content<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.encap_content_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed = self
.signed_data()?
.encap_content_info
.e_content
.as_ref()
.map(|c| PyBytes::new(py, c.as_bytes()));
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.encap_content_cache.set(to_store);
Ok(computed)
}
#[getter]
fn certificates<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.certificates_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed = self
.signed_data()?
.certificates
.as_ref()
.map(|c| PyBytes::new(py, c.as_bytes()));
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.certificates_cache.set(to_store);
Ok(computed)
}
#[getter]
fn crls<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.crls_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed = self
.signed_data()?
.crls
.as_ref()
.map(|c| PyBytes::new(py, c.as_bytes()));
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.crls_cache.set(to_store);
Ok(computed)
}
#[getter]
fn signer_infos<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyList>> {
if let Some(cached) = self.signer_infos_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let list = PyList::empty(py);
for si in self.signed_data()?.signer_infos.elements() {
let mut enc = synta::Encoder::new(Encoding::Der);
si.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}")))?;
let pybytes = PyBytes::new(py, &der).unbind();
let raw_static: &'static [u8] = unsafe {
let s = pybytes.bind(py).as_bytes();
std::slice::from_raw_parts(s.as_ptr(), s.len())
};
let si_obj = Py::new(
py,
PySignerInfo {
_data: pybytes,
raw: raw_static,
inner: OnceLock::new(),
sid_cache: OnceLock::new(),
digest_algorithm_oid_cache: OnceLock::new(),
digest_algorithm_params_cache: OnceLock::new(),
signature_algorithm_oid_cache: OnceLock::new(),
signature_algorithm_params_cache: OnceLock::new(),
signature_cache: OnceLock::new(),
signed_attrs_cache: OnceLock::new(),
unsigned_attrs_cache: OnceLock::new(),
},
)?;
list.append(si_obj.into_bound(py))?;
}
let list_unbound = list.unbind();
let _ = self.signer_infos_cache.set(list_unbound.clone_ref(py));
Ok(list_unbound.into_bound(py))
}
fn __repr__(&self) -> PyResult<String> {
let sd = self.signed_data()?;
Ok(format!(
"SignedData(version={}, signer_count={})",
sd.version.as_i64().unwrap_or(0),
sd.signer_infos.len(),
))
}
}
#[pyclass(frozen, name = "SignerInfo")]
pub struct PySignerInfo {
_data: Py<PyBytes>,
raw: &'static [u8],
inner: OnceLock<Box<synta_certificate::cms_rfc5652_types::SignerInfo<'static>>>,
sid_cache: OnceLock<Py<PyBytes>>,
digest_algorithm_oid_cache: OnceLock<Py<PyObjectIdentifier>>,
digest_algorithm_params_cache: OnceLock<Option<Py<PyBytes>>>,
signature_algorithm_oid_cache: OnceLock<Py<PyObjectIdentifier>>,
signature_algorithm_params_cache: OnceLock<Option<Py<PyBytes>>>,
signature_cache: OnceLock<Py<PyBytes>>,
signed_attrs_cache: OnceLock<Option<Py<PyBytes>>>,
unsigned_attrs_cache: OnceLock<Option<Py<PyBytes>>>,
}
impl PySignerInfo {
fn signer_info(&self) -> PyResult<&synta_certificate::cms_rfc5652_types::SignerInfo<'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!("SignerInfo DER decode failed: {e}"))
})?;
let _ = self.inner.set(Box::new(decoded));
Ok(self.inner.get().unwrap().as_ref())
}
}
#[pymethods]
impl PySignerInfo {
#[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(),
sid_cache: OnceLock::new(),
digest_algorithm_oid_cache: OnceLock::new(),
digest_algorithm_params_cache: OnceLock::new(),
signature_algorithm_oid_cache: OnceLock::new(),
signature_algorithm_params_cache: OnceLock::new(),
signature_cache: OnceLock::new(),
signed_attrs_cache: OnceLock::new(),
unsigned_attrs_cache: OnceLock::new(),
})
}
fn to_der<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
self._data.clone_ref(py).into_bound(py)
}
#[getter]
fn version(&self) -> PyResult<i64> {
Ok(self.signer_info()?.version.as_i64().unwrap_or(0))
}
#[getter]
fn sid<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
if let Some(cached) = self.sid_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let py_bytes = PyBytes::new(py, self.signer_info()?.sid.as_bytes()).unbind();
let _ = self.sid_cache.set(py_bytes.clone_ref(py));
Ok(py_bytes.into_bound(py))
}
#[getter]
fn digest_algorithm_oid<'py>(
&self,
py: Python<'py>,
) -> PyResult<Bound<'py, PyObjectIdentifier>> {
if let Some(cached) = self.digest_algorithm_oid_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let obj = Py::new(
py,
PyObjectIdentifier::from_oid(self.signer_info()?.digest_algorithm.algorithm.clone()),
)?;
let _ = self.digest_algorithm_oid_cache.set(obj.clone_ref(py));
Ok(obj.into_bound(py))
}
#[getter]
fn digest_algorithm_params<'py>(
&self,
py: Python<'py>,
) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.digest_algorithm_params_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed =
encode_element_opt(py, self.signer_info()?.digest_algorithm.parameters.as_ref())?;
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.digest_algorithm_params_cache.set(to_store);
Ok(computed)
}
#[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.signer_info()?.signature_algorithm.algorithm.clone()),
)?;
let _ = self.signature_algorithm_oid_cache.set(obj.clone_ref(py));
Ok(obj.into_bound(py))
}
#[getter]
fn signature_algorithm_params<'py>(
&self,
py: Python<'py>,
) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.signature_algorithm_params_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed = encode_element_opt(
py,
self.signer_info()?.signature_algorithm.parameters.as_ref(),
)?;
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.signature_algorithm_params_cache.set(to_store);
Ok(computed)
}
#[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.signer_info()?.signature.as_bytes()).unbind();
let _ = self.signature_cache.set(py_bytes.clone_ref(py));
Ok(py_bytes.into_bound(py))
}
#[getter]
fn signed_attrs<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.signed_attrs_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed = self
.signer_info()?
.signed_attrs
.as_ref()
.map(|a| PyBytes::new(py, a.as_bytes()));
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.signed_attrs_cache.set(to_store);
Ok(computed)
}
#[getter]
fn unsigned_attrs<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.unsigned_attrs_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed = self
.signer_info()?
.unsigned_attrs
.as_ref()
.map(|a| PyBytes::new(py, a.as_bytes()));
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.unsigned_attrs_cache.set(to_store);
Ok(computed)
}
fn __repr__(&self) -> PyResult<String> {
let si = self.signer_info()?;
Ok(format!(
"SignerInfo(version={}, digest_algorithm={})",
si.version.as_i64().unwrap_or(0),
si.digest_algorithm.algorithm,
))
}
}
#[pyclass(frozen, name = "EnvelopedData")]
pub struct PyEnvelopedData {
_data: Py<PyBytes>,
raw: &'static [u8],
inner: OnceLock<Box<synta_certificate::cms_rfc5652_types::EnvelopedData<'static>>>,
originator_info_cache: OnceLock<Option<Py<PyBytes>>>,
recipient_infos_cache: OnceLock<Py<PyBytes>>,
content_type_cache: OnceLock<Py<PyObjectIdentifier>>,
content_encryption_algorithm_oid_cache: OnceLock<Py<PyObjectIdentifier>>,
content_encryption_algorithm_params_cache: OnceLock<Option<Py<PyBytes>>>,
encrypted_content_cache: OnceLock<Option<Py<PyBytes>>>,
unprotected_attrs_cache: OnceLock<Option<Py<PyBytes>>>,
}
impl PyEnvelopedData {
fn enveloped_data(
&self,
) -> PyResult<&synta_certificate::cms_rfc5652_types::EnvelopedData<'static>> {
if let Some(v) = self.inner.get() {
return Ok(v.as_ref());
}
let mut decoder = Decoder::new(self.raw, Encoding::Ber);
let decoded = decoder.decode().map_err(|e| {
pyo3::exceptions::PyValueError::new_err(format!("EnvelopedData BER decode failed: {e}"))
})?;
let _ = self.inner.set(Box::new(decoded));
Ok(self.inner.get().unwrap().as_ref())
}
}
#[pymethods]
impl PyEnvelopedData {
#[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::Ber);
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(),
originator_info_cache: OnceLock::new(),
recipient_infos_cache: OnceLock::new(),
content_type_cache: OnceLock::new(),
content_encryption_algorithm_oid_cache: OnceLock::new(),
content_encryption_algorithm_params_cache: OnceLock::new(),
encrypted_content_cache: OnceLock::new(),
unprotected_attrs_cache: OnceLock::new(),
})
}
fn to_der<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
self._data.clone_ref(py).into_bound(py)
}
#[getter]
fn version(&self) -> PyResult<i64> {
Ok(self.enveloped_data()?.version.as_i64().unwrap_or(0))
}
#[getter]
fn originator_info<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.originator_info_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed = match &self.enveloped_data()?.originator_info {
None => None,
Some(oi) => {
let mut enc = synta::Encoder::new(Encoding::Der);
oi.encode(&mut enc)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
let bytes = enc
.finish()
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
Some(PyBytes::new(py, &bytes))
}
};
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.originator_info_cache.set(to_store);
Ok(computed)
}
#[getter]
fn recipient_infos<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
if let Some(cached) = self.recipient_infos_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let py_bytes = PyBytes::new(py, self.enveloped_data()?.recipient_infos.as_bytes()).unbind();
let _ = self.recipient_infos_cache.set(py_bytes.clone_ref(py));
Ok(py_bytes.into_bound(py))
}
#[getter]
fn content_type<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyObjectIdentifier>> {
if let Some(cached) = self.content_type_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let obj = Py::new(
py,
PyObjectIdentifier::from_oid(
self.enveloped_data()?
.encrypted_content_info
.content_type
.clone(),
),
)?;
let _ = self.content_type_cache.set(obj.clone_ref(py));
Ok(obj.into_bound(py))
}
#[getter]
fn content_encryption_algorithm_oid<'py>(
&self,
py: Python<'py>,
) -> PyResult<Bound<'py, PyObjectIdentifier>> {
if let Some(cached) = self.content_encryption_algorithm_oid_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let obj = Py::new(
py,
PyObjectIdentifier::from_oid(
self.enveloped_data()?
.encrypted_content_info
.content_encryption_algorithm
.algorithm
.clone(),
),
)?;
let _ = self
.content_encryption_algorithm_oid_cache
.set(obj.clone_ref(py));
Ok(obj.into_bound(py))
}
#[getter]
fn content_encryption_algorithm_params<'py>(
&self,
py: Python<'py>,
) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.content_encryption_algorithm_params_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed = encode_element_opt(
py,
self.enveloped_data()?
.encrypted_content_info
.content_encryption_algorithm
.parameters
.as_ref(),
)?;
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.content_encryption_algorithm_params_cache.set(to_store);
Ok(computed)
}
#[getter]
fn encrypted_content<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.encrypted_content_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed = self
.enveloped_data()?
.encrypted_content_info
.encrypted_content
.as_ref()
.map(|c| PyBytes::new(py, c.as_bytes()));
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.encrypted_content_cache.set(to_store);
Ok(computed)
}
#[getter]
fn unprotected_attrs<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.unprotected_attrs_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed = self
.enveloped_data()?
.unprotected_attrs
.as_ref()
.map(|a| PyBytes::new(py, a.as_bytes()));
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.unprotected_attrs_cache.set(to_store);
Ok(computed)
}
fn __repr__(&self) -> PyResult<String> {
Ok(format!(
"EnvelopedData(version={})",
self.enveloped_data()?.version.as_i64().unwrap_or(0),
))
}
}
#[pyclass(frozen, name = "EncryptedData")]
pub struct PyEncryptedData {
_data: Py<PyBytes>,
raw: &'static [u8],
inner: OnceLock<Box<synta_certificate::cms_rfc5652_types::EncryptedData<'static>>>,
content_type_cache: OnceLock<Py<PyObjectIdentifier>>,
content_encryption_algorithm_oid_cache: OnceLock<Py<PyObjectIdentifier>>,
content_encryption_algorithm_params_cache: OnceLock<Option<Py<PyBytes>>>,
encrypted_content_cache: OnceLock<Option<Py<PyBytes>>>,
unprotected_attrs_cache: OnceLock<Option<Py<PyBytes>>>,
}
impl PyEncryptedData {
fn encrypted_data(
&self,
) -> PyResult<&synta_certificate::cms_rfc5652_types::EncryptedData<'static>> {
if let Some(v) = self.inner.get() {
return Ok(v.as_ref());
}
let mut decoder = Decoder::new(self.raw, Encoding::Ber);
let decoded = decoder.decode().map_err(|e| {
pyo3::exceptions::PyValueError::new_err(format!("EncryptedData BER decode failed: {e}"))
})?;
let _ = self.inner.set(Box::new(decoded));
Ok(self.inner.get().unwrap().as_ref())
}
}
#[pymethods]
impl PyEncryptedData {
#[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::Ber);
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(),
content_type_cache: OnceLock::new(),
content_encryption_algorithm_oid_cache: OnceLock::new(),
content_encryption_algorithm_params_cache: OnceLock::new(),
encrypted_content_cache: OnceLock::new(),
unprotected_attrs_cache: OnceLock::new(),
})
}
fn to_der<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
self._data.clone_ref(py).into_bound(py)
}
#[getter]
fn version(&self) -> PyResult<i64> {
Ok(self.encrypted_data()?.version.as_i64().unwrap_or(0))
}
#[getter]
fn content_type<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyObjectIdentifier>> {
if let Some(cached) = self.content_type_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let obj = Py::new(
py,
PyObjectIdentifier::from_oid(
self.encrypted_data()?
.encrypted_content_info
.content_type
.clone(),
),
)?;
let _ = self.content_type_cache.set(obj.clone_ref(py));
Ok(obj.into_bound(py))
}
#[getter]
fn content_encryption_algorithm_oid<'py>(
&self,
py: Python<'py>,
) -> PyResult<Bound<'py, PyObjectIdentifier>> {
if let Some(cached) = self.content_encryption_algorithm_oid_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let obj = Py::new(
py,
PyObjectIdentifier::from_oid(
self.encrypted_data()?
.encrypted_content_info
.content_encryption_algorithm
.algorithm
.clone(),
),
)?;
let _ = self
.content_encryption_algorithm_oid_cache
.set(obj.clone_ref(py));
Ok(obj.into_bound(py))
}
#[getter]
fn content_encryption_algorithm_params<'py>(
&self,
py: Python<'py>,
) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.content_encryption_algorithm_params_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed = encode_element_opt(
py,
self.encrypted_data()?
.encrypted_content_info
.content_encryption_algorithm
.parameters
.as_ref(),
)?;
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.content_encryption_algorithm_params_cache.set(to_store);
Ok(computed)
}
#[getter]
fn encrypted_content<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.encrypted_content_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed = self
.encrypted_data()?
.encrypted_content_info
.encrypted_content
.as_ref()
.map(|c| PyBytes::new(py, c.as_bytes()));
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.encrypted_content_cache.set(to_store);
Ok(computed)
}
#[getter]
fn unprotected_attrs<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.unprotected_attrs_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed = self
.encrypted_data()?
.unprotected_attrs
.as_ref()
.map(|a| PyBytes::new(py, a.as_bytes()));
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.unprotected_attrs_cache.set(to_store);
Ok(computed)
}
#[staticmethod]
#[pyo3(signature = (plaintext, key, algorithm_oid, content_type_oid = None))]
fn create(
py: Python<'_>,
plaintext: &[u8],
key: &[u8],
algorithm_oid: &Bound<'_, PyAny>,
content_type_oid: Option<&Bound<'_, PyAny>>,
) -> PyResult<Self> {
let enc_alg_oid = oid_from_pyany(algorithm_oid)?;
let ct_oid = match content_type_oid {
Some(obj) => oid_from_pyany(obj)?,
None => synta::ObjectIdentifier::new(synta_certificate::pkcs7_types::ID_DATA)
.expect("id-data is a valid OID"),
};
#[cfg(feature = "openssl")]
{
use synta_certificate::CmsEncryptor as _;
let der = synta_certificate::OpensslEncryptor
.create_encrypted_data(
ct_oid.components(),
enc_alg_oid.components(),
plaintext,
key,
)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
let py_bytes = PyBytes::new(py, &der).unbind();
Self::from_der(py, py_bytes.into_bound(py))
}
#[cfg(not(feature = "openssl"))]
{
let _ = (py, plaintext, key, enc_alg_oid, ct_oid);
Err(pyo3::exceptions::PyNotImplementedError::new_err(
"CMS encryption requires the 'openssl' feature; \
rebuild synta-python with --features openssl",
))
}
}
fn decrypt<'py>(&self, py: Python<'py>, key: &[u8]) -> PyResult<Bound<'py, PyBytes>> {
#[cfg(feature = "openssl")]
{
use synta_certificate::CmsDecryptor as _;
let ed = self.encrypted_data()?;
let mut enc = synta::Encoder::new(synta::Encoding::Der);
ed.encrypted_content_info
.content_encryption_algorithm
.encode(&mut enc)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
let algorithm_der = enc
.finish()
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
let ciphertext = ed
.encrypted_content_info
.encrypted_content
.as_ref()
.ok_or_else(|| {
pyo3::exceptions::PyValueError::new_err(
"EncryptedData has no encryptedContent field",
)
})?
.as_bytes();
let plaintext = synta_certificate::OpensslDecryptor
.decrypt(&algorithm_der, ciphertext, key)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
Ok(PyBytes::new(py, &plaintext))
}
#[cfg(not(feature = "openssl"))]
{
let _ = (py, key);
Err(pyo3::exceptions::PyNotImplementedError::new_err(
"CMS decryption requires the 'openssl' feature; \
rebuild synta-python with --features openssl",
))
}
}
fn __repr__(&self) -> PyResult<String> {
Ok(format!(
"EncryptedData(version={})",
self.encrypted_data()?.version.as_i64().unwrap_or(0),
))
}
}
#[pyclass(frozen, name = "DigestedData")]
pub struct PyDigestedData {
_data: Py<PyBytes>,
raw: &'static [u8],
inner: OnceLock<Box<synta_certificate::cms_rfc5652_types::DigestedData<'static>>>,
digest_algorithm_oid_cache: OnceLock<Py<PyObjectIdentifier>>,
digest_algorithm_params_cache: OnceLock<Option<Py<PyBytes>>>,
encap_content_type_cache: OnceLock<Py<PyObjectIdentifier>>,
encap_content_cache: OnceLock<Option<Py<PyBytes>>>,
digest_cache: OnceLock<Py<PyBytes>>,
}
impl PyDigestedData {
fn digested_data(
&self,
) -> PyResult<&synta_certificate::cms_rfc5652_types::DigestedData<'static>> {
if let Some(v) = self.inner.get() {
return Ok(v.as_ref());
}
let mut decoder = Decoder::new(self.raw, Encoding::Ber);
let decoded = decoder.decode().map_err(|e| {
pyo3::exceptions::PyValueError::new_err(format!("DigestedData BER decode failed: {e}"))
})?;
let _ = self.inner.set(Box::new(decoded));
Ok(self.inner.get().unwrap().as_ref())
}
}
#[pymethods]
impl PyDigestedData {
#[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::Ber);
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(),
digest_algorithm_oid_cache: OnceLock::new(),
digest_algorithm_params_cache: OnceLock::new(),
encap_content_type_cache: OnceLock::new(),
encap_content_cache: OnceLock::new(),
digest_cache: OnceLock::new(),
})
}
fn to_der<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
self._data.clone_ref(py).into_bound(py)
}
#[getter]
fn version(&self) -> PyResult<i64> {
Ok(self.digested_data()?.version.as_i64().unwrap_or(0))
}
#[getter]
fn digest_algorithm_oid<'py>(
&self,
py: Python<'py>,
) -> PyResult<Bound<'py, PyObjectIdentifier>> {
if let Some(cached) = self.digest_algorithm_oid_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let obj = Py::new(
py,
PyObjectIdentifier::from_oid(self.digested_data()?.digest_algorithm.algorithm.clone()),
)?;
let _ = self.digest_algorithm_oid_cache.set(obj.clone_ref(py));
Ok(obj.into_bound(py))
}
#[getter]
fn digest_algorithm_params<'py>(
&self,
py: Python<'py>,
) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.digest_algorithm_params_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed = encode_element_opt(
py,
self.digested_data()?.digest_algorithm.parameters.as_ref(),
)?;
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.digest_algorithm_params_cache.set(to_store);
Ok(computed)
}
#[getter]
fn encap_content_type<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyObjectIdentifier>> {
if let Some(cached) = self.encap_content_type_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let obj = Py::new(
py,
PyObjectIdentifier::from_oid(
self.digested_data()?
.encap_content_info
.e_content_type
.clone(),
),
)?;
let _ = self.encap_content_type_cache.set(obj.clone_ref(py));
Ok(obj.into_bound(py))
}
#[getter]
fn encap_content<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.encap_content_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed = self
.digested_data()?
.encap_content_info
.e_content
.as_ref()
.map(|c| PyBytes::new(py, c.as_bytes()));
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.encap_content_cache.set(to_store);
Ok(computed)
}
#[getter]
fn digest<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
if let Some(cached) = self.digest_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let py_bytes = PyBytes::new(py, self.digested_data()?.digest.as_bytes()).unbind();
let _ = self.digest_cache.set(py_bytes.clone_ref(py));
Ok(py_bytes.into_bound(py))
}
fn __repr__(&self) -> PyResult<String> {
let dd = self.digested_data()?;
Ok(format!(
"DigestedData(version={}, digest_algorithm={})",
dd.version.as_i64().unwrap_or(0),
dd.digest_algorithm.algorithm,
))
}
}
#[pyclass(frozen, name = "AuthenticatedData")]
pub struct PyAuthenticatedData {
_data: Py<PyBytes>,
raw: &'static [u8],
inner: OnceLock<Box<synta_certificate::cms_rfc5652_types::AuthenticatedData<'static>>>,
originator_info_cache: OnceLock<Option<Py<PyBytes>>>,
recipient_infos_cache: OnceLock<Py<PyBytes>>,
mac_algorithm_oid_cache: OnceLock<Py<PyObjectIdentifier>>,
mac_algorithm_params_cache: OnceLock<Option<Py<PyBytes>>>,
digest_algorithm_oid_cache: OnceLock<Option<Py<PyObjectIdentifier>>>,
digest_algorithm_params_cache: OnceLock<Option<Py<PyBytes>>>,
encap_content_type_cache: OnceLock<Py<PyObjectIdentifier>>,
encap_content_cache: OnceLock<Option<Py<PyBytes>>>,
mac_cache: OnceLock<Py<PyBytes>>,
auth_attrs_cache: OnceLock<Option<Py<PyBytes>>>,
unauth_attrs_cache: OnceLock<Option<Py<PyBytes>>>,
}
impl PyAuthenticatedData {
fn authenticated_data(
&self,
) -> PyResult<&synta_certificate::cms_rfc5652_types::AuthenticatedData<'static>> {
if let Some(v) = self.inner.get() {
return Ok(v.as_ref());
}
let mut decoder = Decoder::new(self.raw, Encoding::Ber);
let decoded = decoder.decode().map_err(|e| {
pyo3::exceptions::PyValueError::new_err(format!(
"AuthenticatedData BER decode failed: {e}"
))
})?;
let _ = self.inner.set(Box::new(decoded));
Ok(self.inner.get().unwrap().as_ref())
}
}
#[pymethods]
impl PyAuthenticatedData {
#[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::Ber);
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(),
originator_info_cache: OnceLock::new(),
recipient_infos_cache: OnceLock::new(),
mac_algorithm_oid_cache: OnceLock::new(),
mac_algorithm_params_cache: OnceLock::new(),
digest_algorithm_oid_cache: OnceLock::new(),
digest_algorithm_params_cache: OnceLock::new(),
encap_content_type_cache: OnceLock::new(),
encap_content_cache: OnceLock::new(),
mac_cache: OnceLock::new(),
auth_attrs_cache: OnceLock::new(),
unauth_attrs_cache: OnceLock::new(),
})
}
fn to_der<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
self._data.clone_ref(py).into_bound(py)
}
#[getter]
fn version(&self) -> PyResult<i64> {
Ok(self.authenticated_data()?.version.as_i64().unwrap_or(0))
}
#[getter]
fn originator_info<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.originator_info_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed = match &self.authenticated_data()?.originator_info {
None => None,
Some(oi) => {
let mut enc = synta::Encoder::new(Encoding::Der);
oi.encode(&mut enc)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
let bytes = enc
.finish()
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("{e}")))?;
Some(PyBytes::new(py, &bytes))
}
};
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.originator_info_cache.set(to_store);
Ok(computed)
}
#[getter]
fn recipient_infos<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
if let Some(cached) = self.recipient_infos_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let bytes =
PyBytes::new(py, self.authenticated_data()?.recipient_infos.as_bytes()).unbind();
let _ = self.recipient_infos_cache.set(bytes.clone_ref(py));
Ok(bytes.into_bound(py))
}
#[getter]
fn mac_algorithm_oid<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyObjectIdentifier>> {
if let Some(cached) = self.mac_algorithm_oid_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let obj = Py::new(
py,
PyObjectIdentifier::from_oid(
self.authenticated_data()?.mac_algorithm.algorithm.clone(),
),
)?;
let _ = self.mac_algorithm_oid_cache.set(obj.clone_ref(py));
Ok(obj.into_bound(py))
}
#[getter]
fn mac_algorithm_params<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.mac_algorithm_params_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed = encode_element_opt(
py,
self.authenticated_data()?.mac_algorithm.parameters.as_ref(),
)?;
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.mac_algorithm_params_cache.set(to_store);
Ok(computed)
}
#[getter]
fn digest_algorithm_oid<'py>(
&self,
py: Python<'py>,
) -> PyResult<Option<Bound<'py, PyObjectIdentifier>>> {
if let Some(cached) = self.digest_algorithm_oid_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed: Option<Py<PyObjectIdentifier>> =
match &self.authenticated_data()?.digest_algorithm {
None => None,
Some(da) => Some(Py::new(
py,
PyObjectIdentifier::from_oid(da.algorithm.clone()),
)?),
};
let to_store = computed.as_ref().map(|b| b.clone_ref(py));
let _ = self.digest_algorithm_oid_cache.set(to_store);
Ok(computed.map(|b| b.into_bound(py)))
}
#[getter]
fn digest_algorithm_params<'py>(
&self,
py: Python<'py>,
) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.digest_algorithm_params_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed = encode_element_opt(
py,
self.authenticated_data()?
.digest_algorithm
.as_ref()
.and_then(|da| da.parameters.as_ref()),
)?;
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.digest_algorithm_params_cache.set(to_store);
Ok(computed)
}
#[getter]
fn encap_content_type<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyObjectIdentifier>> {
if let Some(cached) = self.encap_content_type_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let obj = Py::new(
py,
PyObjectIdentifier::from_oid(
self.authenticated_data()?
.encap_content_info
.e_content_type
.clone(),
),
)?;
let _ = self.encap_content_type_cache.set(obj.clone_ref(py));
Ok(obj.into_bound(py))
}
#[getter]
fn encap_content<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.encap_content_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed = self
.authenticated_data()?
.encap_content_info
.e_content
.as_ref()
.map(|c| PyBytes::new(py, c.as_bytes()));
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.encap_content_cache.set(to_store);
Ok(computed)
}
#[getter]
fn mac<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
if let Some(cached) = self.mac_cache.get() {
return Ok(cached.clone_ref(py).into_bound(py));
}
let bytes = PyBytes::new(py, self.authenticated_data()?.mac.as_bytes()).unbind();
let _ = self.mac_cache.set(bytes.clone_ref(py));
Ok(bytes.into_bound(py))
}
#[getter]
fn auth_attrs<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.auth_attrs_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed = self
.authenticated_data()?
.auth_attrs
.as_ref()
.map(|a| PyBytes::new(py, a.as_bytes()));
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.auth_attrs_cache.set(to_store);
Ok(computed)
}
#[getter]
fn unauth_attrs<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.unauth_attrs_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let computed = self
.authenticated_data()?
.unauth_attrs
.as_ref()
.map(|a| PyBytes::new(py, a.as_bytes()));
let to_store = computed.as_ref().map(|b| b.as_unbound().clone_ref(py));
let _ = self.unauth_attrs_cache.set(to_store);
Ok(computed)
}
fn __repr__(&self) -> PyResult<String> {
let ad = self.authenticated_data()?;
Ok(format!(
"AuthenticatedData(version={})",
ad.version.as_i64().unwrap_or(0),
))
}
}
fn register_cms_submodule(parent: &Bound<'_, PyModule>) -> PyResult<()> {
let py = parent.py();
let m = PyModule::new(py, "cms")?;
m.add_class::<PyContentInfo>()?;
m.add_class::<PySignedData>()?;
m.add_class::<PySignerInfo>()?;
m.add_class::<PyEnvelopedData>()?;
m.add_class::<PyEncryptedData>()?;
m.add_class::<PyDigestedData>()?;
m.add_class::<PyAuthenticatedData>()?;
m.add_class::<PyIssuerAndSerialNumber>()?;
m.add_class::<PyKEMRecipientInfo>()?;
m.add_class::<PyCMSORIforKEMOtherInfo>()?;
m.add(
"ID_DATA",
oid_const(py, synta_certificate::pkcs7_types::ID_DATA),
)?;
m.add(
"ID_SIGNED_DATA",
oid_const(py, synta_certificate::pkcs7_types::ID_SIGNED_DATA),
)?;
m.add(
"ID_ENVELOPED_DATA",
oid_const(py, synta_certificate::pkcs7_types::ID_ENVELOPED_DATA),
)?;
m.add(
"ID_ENCRYPTED_DATA",
oid_const(py, synta_certificate::pkcs7_types::ID_ENCRYPTED_DATA),
)?;
m.add(
"ID_DIGESTED_DATA",
oid_const(py, synta_certificate::oids::CMS_DIGESTED_DATA),
)?;
m.add(
"ID_CT_AUTH_DATA",
oid_const(py, synta_certificate::oids::CMS_AUTH_DATA),
)?;
m.add(
"ID_ORI",
oid_const(py, synta_certificate::cms_kem_types::ID_ORI),
)?;
m.add(
"ID_ORI_KEM",
oid_const(py, synta_certificate::cms_kem_types::ID_ORI_KEM),
)?;
m.add(
"ID_AES128_CBC",
oid_const(py, synta_certificate::pkcs12_types::ID_AES128_CBC),
)?;
m.add(
"ID_AES192_CBC",
oid_const(py, synta_certificate::pkcs12_types::ID_AES192_CBC),
)?;
m.add(
"ID_AES256_CBC",
oid_const(py, synta_certificate::pkcs12_types::ID_AES256_CBC),
)?;
crate::install_submodule(
parent,
&m,
"synta.cms",
Some(
"synta.cms — CMS (RFC 5652) and CMS-KEM (RFC 9629) types.\n\
\n\
Provides ContentInfo, SignedData, SignerInfo, EnvelopedData,\n\
EncryptedData, DigestedData, AuthenticatedData,\n\
IssuerAndSerialNumber, KEMRecipientInfo, CMSORIforKEMOtherInfo,\n\
along with content-type and OtherRecipientInfo OID constants.",
),
)
}
pub fn register_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<PyObjectIdentifier>()?;
m.add_class::<PyCertificate>()?;
m.add_class::<PyCsr>()?;
m.add_class::<PyCrl>()?;
m.add_class::<PyOcspResponse>()?;
m.add_function(wrap_pyfunction!(load_der_pkcs7_certificates, m)?)?;
m.add_function(wrap_pyfunction!(load_pem_pkcs7_certificates, m)?)?;
m.add_function(wrap_pyfunction!(load_pkcs12_certificates, m)?)?;
m.add_function(wrap_pyfunction!(load_pkcs12_keys, m)?)?;
m.add_function(wrap_pyfunction!(load_pkcs12, m)?)?;
m.add_function(wrap_pyfunction!(read_pki_blocks, m)?)?;
register_oids_submodule(m)?;
register_cms_submodule(m)?;
register_general_name_submodule(m)?;
Ok(())
}
#[pyfunction]
fn identify_signature_algorithm(oid: &Bound<'_, PyAny>) -> PyResult<&'static str> {
let inner = oid_from_pyany(oid)?;
Ok(synta_certificate::identify_signature_algorithm(&inner))
}
#[pyfunction]
fn identify_public_key_algorithm(oid: &Bound<'_, PyAny>) -> PyResult<Option<&'static str>> {
let inner = oid_from_pyany(oid)?;
Ok(synta_certificate::identify_public_key_algorithm(&inner))
}
#[pyfunction]
fn ec_curve_short_name(oid: &Bound<'_, PyAny>) -> PyResult<Option<&'static str>> {
let inner = oid_from_pyany(oid)?;
Ok(synta_certificate::ec_curve_short_name(inner.components()))
}
#[pyfunction]
fn ec_curve_nist_name(oid: &Bound<'_, PyAny>) -> PyResult<Option<&'static str>> {
let inner = oid_from_pyany(oid)?;
Ok(synta_certificate::ec_curve_nist_name(inner.components()))
}
#[pyfunction]
fn ec_curve_key_bits(oid: &Bound<'_, PyAny>) -> PyResult<Option<usize>> {
let inner = oid_from_pyany(oid)?;
Ok(synta_certificate::ec_curve_key_bits(inner.components()))
}
#[pyfunction]
fn extension_oid_name(oid: &Bound<'_, PyAny>) -> PyResult<String> {
let inner = oid_from_pyany(oid)?;
Ok(synta_certificate::extension_oid_name(&inner))
}
fn oid_const(py: Python<'_>, components: &[u32]) -> Py<PyAny> {
let inner = ObjectIdentifier::new(components)
.expect("oid constant has invalid components — bug in synta-certificate");
Py::new(py, PyObjectIdentifier::from_oid(inner))
.expect("PyObjectIdentifier allocation failed")
.into_any()
}
fn register_oids_submodule(parent: &Bound<'_, PyModule>) -> PyResult<()> {
let py = parent.py();
let m = PyModule::new(py, "oids")?;
m.add(
"ML_DSA_44",
oid_const(py, synta_certificate::oids::ML_DSA_44),
)?;
m.add(
"ML_DSA_65",
oid_const(py, synta_certificate::oids::ML_DSA_65),
)?;
m.add(
"ML_DSA_87",
oid_const(py, synta_certificate::oids::ML_DSA_87),
)?;
m.add(
"ML_KEM_512",
oid_const(py, synta_certificate::oids::ML_KEM_512),
)?;
m.add(
"ML_KEM_768",
oid_const(py, synta_certificate::oids::ML_KEM_768),
)?;
m.add(
"ML_KEM_1024",
oid_const(py, synta_certificate::oids::ML_KEM_1024),
)?;
m.add(
"SLH_DSA_SHA2_128S",
oid_const(py, synta_certificate::oids::ID_SLH_DSA_SHA2_128S),
)?;
m.add(
"SLH_DSA_SHA2_128F",
oid_const(py, synta_certificate::oids::ID_SLH_DSA_SHA2_128F),
)?;
m.add(
"SLH_DSA_SHA2_192S",
oid_const(py, synta_certificate::oids::ID_SLH_DSA_SHA2_192S),
)?;
m.add(
"SLH_DSA_SHA2_192F",
oid_const(py, synta_certificate::oids::ID_SLH_DSA_SHA2_192F),
)?;
m.add(
"SLH_DSA_SHA2_256S",
oid_const(py, synta_certificate::oids::ID_SLH_DSA_SHA2_256S),
)?;
m.add(
"SLH_DSA_SHA2_256F",
oid_const(py, synta_certificate::oids::ID_SLH_DSA_SHA2_256F),
)?;
m.add(
"SLH_DSA_SHAKE_128S",
oid_const(py, synta_certificate::oids::ID_SLH_DSA_SHAKE_128S),
)?;
m.add(
"SLH_DSA_SHAKE_128F",
oid_const(py, synta_certificate::oids::ID_SLH_DSA_SHAKE_128F),
)?;
m.add(
"SLH_DSA_SHAKE_192S",
oid_const(py, synta_certificate::oids::ID_SLH_DSA_SHAKE_192S),
)?;
m.add(
"SLH_DSA_SHAKE_192F",
oid_const(py, synta_certificate::oids::ID_SLH_DSA_SHAKE_192F),
)?;
m.add(
"SLH_DSA_SHAKE_256S",
oid_const(py, synta_certificate::oids::ID_SLH_DSA_SHAKE_256S),
)?;
m.add(
"SLH_DSA_SHAKE_256F",
oid_const(py, synta_certificate::oids::ID_SLH_DSA_SHAKE_256F),
)?;
m.add("ED25519", oid_const(py, synta_certificate::oids::ED25519))?;
m.add("ED448", oid_const(py, synta_certificate::oids::ED448))?;
m.add(
"RSA_ENCRYPTION",
oid_const(py, synta_certificate::oids::RSA_ENCRYPTION),
)?;
m.add(
"MD5_WITH_RSA",
oid_const(py, synta_certificate::oids::MD5_WITH_RSA),
)?;
m.add(
"SHA1_WITH_RSA",
oid_const(py, synta_certificate::oids::SHA1_WITH_RSA),
)?;
m.add(
"SHA256_WITH_RSA",
oid_const(py, synta_certificate::oids::SHA256_WITH_RSA),
)?;
m.add(
"SHA384_WITH_RSA",
oid_const(py, synta_certificate::oids::SHA384_WITH_RSA),
)?;
m.add(
"SHA512_WITH_RSA",
oid_const(py, synta_certificate::oids::SHA512_WITH_RSA),
)?;
m.add("RSA", oid_const(py, synta_certificate::oids::RSA))?;
m.add(
"EC_PUBLIC_KEY",
oid_const(py, synta_certificate::oids::EC_PUBLIC_KEY),
)?;
m.add(
"ECDSA_WITH_SHA1",
oid_const(py, synta_certificate::oids::ECDSA_WITH_SHA1),
)?;
m.add(
"ECDSA_WITH_SHA256",
oid_const(py, synta_certificate::oids::ECDSA_WITH_SHA256),
)?;
m.add(
"ECDSA_WITH_SHA384",
oid_const(py, synta_certificate::oids::ECDSA_WITH_SHA384),
)?;
m.add(
"ECDSA_WITH_SHA512",
oid_const(py, synta_certificate::oids::ECDSA_WITH_SHA512),
)?;
m.add("ECDSA", oid_const(py, synta_certificate::oids::ECDSA_SIG))?;
m.add(
"EC_CURVE_P256",
oid_const(py, synta_certificate::oids::EC_CURVE_P256),
)?;
m.add(
"EC_CURVE_P384",
oid_const(py, synta_certificate::oids::EC_CURVE_P384),
)?;
m.add(
"EC_CURVE_P521",
oid_const(py, synta_certificate::oids::EC_CURVE_P521),
)?;
m.add(
"EC_CURVE_SECP256K1",
oid_const(py, synta_certificate::oids::EC_CURVE_SECP256K1),
)?;
m.add("SHA224", oid_const(py, synta_certificate::oids::ID_SHA224))?;
m.add("SHA256", oid_const(py, synta_certificate::oids::ID_SHA256))?;
m.add("SHA384", oid_const(py, synta_certificate::oids::ID_SHA384))?;
m.add("SHA512", oid_const(py, synta_certificate::oids::ID_SHA512))?;
m.add(
"SHA512_224",
oid_const(py, synta_certificate::oids::ID_SHA512_224),
)?;
m.add(
"SHA512_256",
oid_const(py, synta_certificate::oids::ID_SHA512_256),
)?;
m.add(
"SHA3_224",
oid_const(py, synta_certificate::oids::ID_SHA3_224),
)?;
m.add(
"SHA3_256",
oid_const(py, synta_certificate::oids::ID_SHA3_256),
)?;
m.add(
"SHA3_384",
oid_const(py, synta_certificate::oids::ID_SHA3_384),
)?;
m.add(
"SHA3_512",
oid_const(py, synta_certificate::oids::ID_SHA3_512),
)?;
m.add(
"SHAKE128",
oid_const(py, synta_certificate::oids::ID_SHAKE128),
)?;
m.add(
"SHAKE256",
oid_const(py, synta_certificate::oids::ID_SHAKE256),
)?;
m.add(
"SUBJECT_KEY_IDENTIFIER",
oid_const(py, synta_certificate::oids::SUBJECT_KEY_IDENTIFIER),
)?;
m.add(
"KEY_USAGE",
oid_const(py, synta_certificate::oids::KEY_USAGE),
)?;
m.add(
"SUBJECT_ALT_NAME",
oid_const(py, synta_certificate::oids::SUBJECT_ALT_NAME),
)?;
m.add(
"ISSUER_ALT_NAME",
oid_const(py, synta_certificate::oids::ISSUER_ALT_NAME),
)?;
m.add(
"BASIC_CONSTRAINTS",
oid_const(py, synta_certificate::oids::BASIC_CONSTRAINTS),
)?;
m.add(
"CRL_DISTRIBUTION_POINTS",
oid_const(py, synta_certificate::oids::CRL_DISTRIBUTION_POINTS),
)?;
m.add(
"CERTIFICATE_POLICIES",
oid_const(py, synta_certificate::oids::CERTIFICATE_POLICIES),
)?;
m.add(
"AUTHORITY_KEY_IDENTIFIER",
oid_const(py, synta_certificate::oids::AUTHORITY_KEY_IDENTIFIER),
)?;
m.add(
"EXTENDED_KEY_USAGE",
oid_const(py, synta_certificate::oids::EXTENDED_KEY_USAGE),
)?;
m.add(
"AUTHORITY_INFO_ACCESS",
oid_const(py, synta_certificate::oids::AUTHORITY_INFO_ACCESS),
)?;
m.add(
"CT_PRECERT_SCTS",
oid_const(py, synta_certificate::oids::CT_PRECERT_SCTS),
)?;
m.add(
"KP_SERVER_AUTH",
oid_const(py, synta_certificate::oids::KP_SERVER_AUTH),
)?;
m.add(
"KP_CLIENT_AUTH",
oid_const(py, synta_certificate::oids::KP_CLIENT_AUTH),
)?;
m.add(
"KP_CODE_SIGNING",
oid_const(py, synta_certificate::oids::KP_CODE_SIGNING),
)?;
m.add(
"KP_EMAIL_PROTECTION",
oid_const(py, synta_certificate::oids::KP_EMAIL_PROTECTION),
)?;
m.add(
"KP_TIME_STAMPING",
oid_const(py, synta_certificate::oids::KP_TIME_STAMPING),
)?;
m.add(
"KP_OCSP_SIGNING",
oid_const(py, synta_certificate::oids::KP_OCSP_SIGNING),
)?;
m.add(
"ANY_EXTENDED_KEY_USAGE",
oid_const(py, synta_certificate::oids::ANY_EXTENDED_KEY_USAGE),
)?;
m.add(
"PKINIT_SAN",
oid_const(py, synta_certificate::oids::ID_PKINIT_SAN),
)?;
m.add(
"PKINIT_KP_CLIENT_AUTH",
oid_const(py, synta_certificate::oids::ID_PKINIT_KPCLIENT_AUTH),
)?;
m.add(
"PKINIT_KP_KDC",
oid_const(py, synta_certificate::oids::ID_PKINIT_KPKDC),
)?;
m.add(
"PKINIT_AUTH_DATA",
oid_const(py, synta_certificate::oids::ID_PKINIT_AUTH_DATA),
)?;
m.add(
"PKINIT_DHKEY_DATA",
oid_const(py, synta_certificate::oids::ID_PKINIT_DHKEY_DATA),
)?;
m.add(
"PKINIT_RKEY_DATA",
oid_const(py, synta_certificate::oids::ID_PKINIT_RKEY_DATA),
)?;
m.add(
"PKINIT_KDF",
oid_const(py, synta_certificate::oids::ID_PKINIT_KDF),
)?;
m.add(
"PKINIT_KDF_SHA1",
oid_const(py, synta_certificate::oids::ID_PKINIT_KDF_AH_SHA1),
)?;
m.add(
"PKINIT_KDF_SHA256",
oid_const(py, synta_certificate::oids::ID_PKINIT_KDF_AH_SHA256),
)?;
m.add(
"PKINIT_KDF_SHA384",
oid_const(py, synta_certificate::oids::ID_PKINIT_KDF_AH_SHA384),
)?;
m.add(
"PKINIT_KDF_SHA512",
oid_const(py, synta_certificate::oids::ID_PKINIT_KDF_AH_SHA512),
)?;
m.add(
"MS_SAN_UPN",
oid_const(py, synta_certificate::oids::ID_MS_SAN_UPN),
)?;
m.add(
"MS_CERTIFICATE_TEMPLATE_NAME",
oid_const(py, synta_certificate::oids::ID_MS_CERTIFICATE_TEMPLATE_NAME),
)?;
m.add(
"MS_CERTIFICATE_TEMPLATE",
oid_const(py, synta_certificate::oids::ID_MS_CERTIFICATE_TEMPLATE),
)?;
m.add(
"MS_KP_SMARTCARD_LOGON",
oid_const(py, synta_certificate::oids::ID_MS_KP_SMARTCARD_LOGON),
)?;
m.add(
"MS_NTDS_REPLICATION",
oid_const(py, synta_certificate::oids::ID_MS_NTDS_REPLICATION),
)?;
m.add("CMS_DATA", oid_const(py, synta_certificate::oids::CMS_DATA))?;
m.add(
"CMS_SIGNED_DATA",
oid_const(py, synta_certificate::oids::CMS_SIGNED_DATA),
)?;
m.add(
"CMS_ENVELOPED_DATA",
oid_const(py, synta_certificate::oids::CMS_ENVELOPED_DATA),
)?;
m.add(
"CMS_DIGESTED_DATA",
oid_const(py, synta_certificate::oids::CMS_DIGESTED_DATA),
)?;
m.add(
"CMS_ENCRYPTED_DATA",
oid_const(py, synta_certificate::oids::CMS_ENCRYPTED_DATA),
)?;
m.add(
"CMS_AUTH_DATA",
oid_const(py, synta_certificate::oids::CMS_AUTH_DATA),
)?;
m.add("CMS_ORI", oid_const(py, synta_certificate::oids::CMS_ORI))?;
m.add(
"CMS_ORI_KEM",
oid_const(py, synta_certificate::oids::CMS_ORI_KEM),
)?;
m.add(
"PKCS9_EMAIL_ADDRESS",
oid_const(py, synta_certificate::oids::PKCS9_EMAIL_ADDRESS),
)?;
m.add(
"PKCS9_CONTENT_TYPE",
oid_const(py, synta_certificate::oids::PKCS9_CONTENT_TYPE),
)?;
m.add(
"PKCS9_MESSAGE_DIGEST",
oid_const(py, synta_certificate::oids::PKCS9_MESSAGE_DIGEST),
)?;
m.add(
"PKCS9_SIGNING_TIME",
oid_const(py, synta_certificate::oids::PKCS9_SIGNING_TIME),
)?;
m.add(
"PKCS9_COUNTERSIGNATURE",
oid_const(py, synta_certificate::oids::PKCS9_COUNTERSIGNATURE),
)?;
m.add(
"PKCS9_CHALLENGE_PASSWORD",
oid_const(py, synta_certificate::oids::PKCS9_CHALLENGE_PASSWORD),
)?;
m.add(
"PKCS9_EXTENSION_REQUEST",
oid_const(py, synta_certificate::oids::PKCS9_EXTENSION_REQUEST),
)?;
m.add(
"PKCS9_FRIENDLY_NAME",
oid_const(py, synta_certificate::oids::PKCS9_FRIENDLY_NAME),
)?;
m.add(
"PKCS9_LOCAL_KEY_ID",
oid_const(py, synta_certificate::oids::PKCS9_LOCAL_KEY_ID),
)?;
m.add_function(wrap_pyfunction!(identify_signature_algorithm, &m)?)?;
m.add_function(wrap_pyfunction!(identify_public_key_algorithm, &m)?)?;
m.add_function(wrap_pyfunction!(ec_curve_short_name, &m)?)?;
m.add_function(wrap_pyfunction!(ec_curve_nist_name, &m)?)?;
m.add_function(wrap_pyfunction!(ec_curve_key_bits, &m)?)?;
m.add_function(wrap_pyfunction!(extension_oid_name, &m)?)?;
let attr = PyModule::new(py, "attr")?;
attr.add(
"__doc__",
"OIDs for X.500 Distinguished Name attribute types (RFC 4519).",
)?;
attr.add(
"COMMON_NAME",
oid_const(py, synta_certificate::oids::attr::COMMON_NAME),
)?;
attr.add(
"COUNTRY",
oid_const(py, synta_certificate::oids::attr::COUNTRY),
)?;
attr.add("STATE", oid_const(py, synta_certificate::oids::attr::STATE))?;
attr.add(
"LOCALITY",
oid_const(py, synta_certificate::oids::attr::LOCALITY),
)?;
attr.add(
"ORGANIZATION",
oid_const(py, synta_certificate::oids::attr::ORGANIZATION),
)?;
attr.add(
"ORG_UNIT",
oid_const(py, synta_certificate::oids::attr::ORG_UNIT),
)?;
attr.add(
"ORG_IDENTIFIER",
oid_const(py, synta_certificate::oids::attr::ORG_IDENTIFIER),
)?;
attr.add(
"STREET",
oid_const(py, synta_certificate::oids::attr::STREET),
)?;
attr.add(
"SURNAME",
oid_const(py, synta_certificate::oids::attr::SURNAME),
)?;
attr.add(
"GIVEN_NAME",
oid_const(py, synta_certificate::oids::attr::GIVEN_NAME),
)?;
attr.add(
"INITIALS",
oid_const(py, synta_certificate::oids::attr::INITIALS),
)?;
attr.add("TITLE", oid_const(py, synta_certificate::oids::attr::TITLE))?;
attr.add(
"SERIAL_NUMBER",
oid_const(py, synta_certificate::oids::attr::SERIAL_NUMBER),
)?;
attr.add(
"EMAIL_ADDRESS",
oid_const(py, synta_certificate::oids::attr::EMAIL_ADDRESS),
)?;
attr.add(
"USER_ID",
oid_const(py, synta_certificate::oids::attr::USER_ID),
)?;
attr.add(
"DOMAIN_COMPONENT",
oid_const(py, synta_certificate::oids::attr::DOMAIN_COMPONENT),
)?;
crate::install_submodule(&m, &attr, "synta.oids.attr", None)?;
crate::install_submodule(
parent,
&m,
"synta.oids",
Some("OID constants for X.509 signature, public-key, and extension algorithms."),
)
}
fn register_general_name_submodule(parent: &Bound<'_, PyModule>) -> PyResult<()> {
let py = parent.py();
let m = PyModule::new(py, "general_name")?;
m.add("OTHER_NAME", synta_certificate::general_name::OTHER_NAME)?;
m.add("RFC822_NAME", synta_certificate::general_name::RFC822_NAME)?;
m.add("DNS_NAME", synta_certificate::general_name::DNS_NAME)?;
m.add(
"X400_ADDRESS",
synta_certificate::general_name::X400_ADDRESS,
)?;
m.add(
"DIRECTORY_NAME",
synta_certificate::general_name::DIRECTORY_NAME,
)?;
m.add(
"EDI_PARTY_NAME",
synta_certificate::general_name::EDI_PARTY_NAME,
)?;
m.add("URI", synta_certificate::general_name::URI)?;
m.add("IP_ADDRESS", synta_certificate::general_name::IP_ADDRESS)?;
m.add(
"REGISTERED_ID",
synta_certificate::general_name::REGISTERED_ID,
)?;
crate::install_submodule(
parent,
&m,
"synta.general_name",
Some(concat!(
"Context-specific tag numbers for the ``GeneralName`` CHOICE type ",
"(RFC 5280 \u{a7}4.2.1.6).\n\n",
"These integer constants correspond to the first element of tuples returned by\n",
":func:`~synta.parse_general_names` and ",
":meth:`~synta.Certificate.subject_alt_names`.\n\n",
"Example usage::\n\n",
" import synta.general_name as gn\n",
" for tag, content in cert.subject_alt_names():\n",
" if tag == gn.DNS_NAME:\n",
" print('DNS:', content.decode())\n",
" elif tag == gn.IP_ADDRESS:\n",
" print('IP:', content.hex())",
)),
)
}