use std::sync::OnceLock;
use pyo3::prelude::*;
use pyo3::types::PyBytes;
use synta::{Decoder, Encoding};
use crate::error::SyntaErr;
use crate::types::PyObjectIdentifier;
#[pyclass(frozen, name = "OneAsymmetricKey")]
pub struct PyOneAsymmetricKey {
_data: Py<PyBytes>,
raw: &'static [u8],
inner: OnceLock<Box<synta_certificate::pkcs8_types::OneAsymmetricKey<'static>>>,
alg_oid_cache: OnceLock<Py<PyObjectIdentifier>>,
}
impl PyOneAsymmetricKey {
fn key(&self) -> PyResult<&synta_certificate::pkcs8_types::OneAsymmetricKey<'static>> {
if let Some(v) = self.inner.get() {
return Ok(v.as_ref());
}
let mut dec = Decoder::new(self.raw, Encoding::Der);
let decoded = dec
.decode::<synta_certificate::pkcs8_types::OneAsymmetricKey<'_>>()
.map_err(SyntaErr)?;
let decoded: synta_certificate::pkcs8_types::OneAsymmetricKey<'static> =
unsafe { std::mem::transmute(decoded) };
let _ = self.inner.set(Box::new(decoded));
Ok(self.inner.get().unwrap().as_ref())
}
}
#[pymethods]
impl PyOneAsymmetricKey {
#[staticmethod]
fn from_der(py: Python<'_>, data: Bound<'_, PyBytes>) -> PyResult<Self> {
let py_bytes = data.unbind();
{
let raw = py_bytes.as_bytes(py);
Decoder::new(raw, Encoding::Der)
.decode::<synta_certificate::pkcs8_types::OneAsymmetricKey<'_>>()
.map_err(SyntaErr)?;
}
let raw: &'static [u8] = unsafe { std::mem::transmute(py_bytes.as_bytes(py)) };
Ok(Self {
_data: py_bytes,
raw,
inner: OnceLock::new(),
alg_oid_cache: OnceLock::new(),
})
}
fn to_der<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
Ok(PyBytes::new(py, &self.key()?.to_der().map_err(SyntaErr)?))
}
#[getter]
fn version(&self) -> PyResult<i64> {
Ok(self.key()?.version.as_i64().map_err(SyntaErr)?)
}
#[getter]
fn private_key_algorithm<'py>(
&self,
py: Python<'py>,
) -> PyResult<Bound<'py, PyObjectIdentifier>> {
if let Some(c) = self.alg_oid_cache.get() {
return Ok(c.clone_ref(py).into_bound(py));
}
let k = self.key()?;
let oid_obj = Py::new(
py,
PyObjectIdentifier::from_oid(k.private_key_algorithm.algorithm.clone()),
)?;
let _ = self.alg_oid_cache.set(oid_obj.clone_ref(py));
Ok(oid_obj.into_bound(py))
}
#[getter]
fn private_key<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
Ok(PyBytes::new(py, self.key()?.private_key.as_bytes()))
}
#[getter]
fn attributes_der<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
Ok(self
.key()?
.attributes
.as_ref()
.map(|a| PyBytes::new(py, a.as_bytes())))
}
#[getter]
fn public_key_der<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
Ok(self
.key()?
.public_key
.as_ref()
.map(|pk| PyBytes::new(py, pk.as_bytes())))
}
#[getter]
fn alg_parameters_der<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
let k = self.key()?;
Ok(k.private_key_algorithm.parameters.as_ref().map(|p| {
let mut enc = synta::Encoder::new(Encoding::Der);
if enc.encode(p).is_err() {
return PyBytes::new(py, &[]);
}
PyBytes::new(py, &enc.finish().unwrap_or_default())
}))
}
fn __repr__(&self, py: Python<'_>) -> PyResult<String> {
let alg = self.private_key_algorithm(py)?;
Ok(format!(
"OneAsymmetricKey(version={}, algorithm={})",
self.version()?,
alg.borrow().inner,
))
}
}
pub(super) fn register_pkcs8_submodule(parent: &Bound<'_, PyModule>) -> PyResult<()> {
let py = parent.py();
let m = PyModule::new(py, "pkcs8")?;
m.add_class::<PyOneAsymmetricKey>()?;
m.add("PrivateKeyInfo", m.getattr("OneAsymmetricKey")?)?;
crate::install_submodule(
parent,
&m,
"synta.pkcs8",
Some(concat!(
"synta.pkcs8 — PKCS #8 / RFC 5958 private key structures.\n\n",
"Provides OneAsymmetricKey (PrivateKeyInfo) for parsing DER-encoded\n",
"private key envelopes produced by OpenSSL and other PKI tools.",
)),
)
}