use std::sync::OnceLock;
use pyo3::prelude::*;
use pyo3::types::{PyBytes, PyString};
use synta::{Decoder, Encoding};
use crate::error::SyntaErr;
use crate::types::PyObjectIdentifier;
fn body_type_name(body: &synta_certificate::cmp_types::PKIBody<'_>) -> &'static str {
use synta_certificate::cmp_types::PKIBody::*;
match body {
Ir(_) => "ir",
Ip(_) => "ip",
Cr(_) => "cr",
Cp(_) => "cp",
P10cr(_) => "p10cr",
Popdecc(_) => "popdecc",
Popdecr(_) => "popdecr",
Kur(_) => "kur",
Kup(_) => "kup",
Krr(_) => "krr",
Krp(_) => "krp",
Rr(_) => "rr",
Rp(_) => "rp",
Ccr(_) => "ccr",
Ccp(_) => "ccp",
Ckuann(_) => "ckuann",
Cann(_) => "cann",
Rann(_) => "rann",
Crlann(_) => "crlann",
Pkiconf(_) => "pkiconf",
Nested(_) => "nested",
Genm(_) => "genm",
Genp(_) => "genp",
Error(_) => "error",
CertConf(_) => "certConf",
PollReq(_) => "pollReq",
PollRep(_) => "pollRep",
}
}
fn body_raw_der(body: &synta_certificate::cmp_types::PKIBody<'_>) -> Option<Vec<u8>> {
use synta_certificate::cmp_types::PKIBody::*;
match body {
Pkiconf(_) => None,
Ir(r) | Ip(r) | Cr(r) | Cp(r) | P10cr(r) | Popdecc(r) | Popdecr(r) | Kur(r) | Kup(r)
| Krr(r) | Krp(r) | Rr(r) | Rp(r) | Ccr(r) | Ccp(r) | Ckuann(r) | Cann(r) | Rann(r)
| Crlann(r) | Nested(r) | Genm(r) | Genp(r) | Error(r) | CertConf(r) | PollReq(r)
| PollRep(r) => Some(r.as_bytes().to_vec()),
}
}
#[pyclass(frozen, name = "CMPMessage")]
pub struct PyCMPMessage {
_data: Py<PyBytes>,
raw: &'static [u8],
inner: OnceLock<Box<synta_certificate::cmp_types::PKIMessage<'static>>>,
pvno_cache: OnceLock<i64>,
body_type_cache: OnceLock<&'static str>,
body_der_cache: OnceLock<Option<Py<PyBytes>>>,
sender_der_cache: OnceLock<Py<PyBytes>>,
recipient_der_cache: OnceLock<Py<PyBytes>>,
transaction_id_cache: OnceLock<Option<Py<PyBytes>>>,
sender_nonce_cache: OnceLock<Option<Py<PyBytes>>>,
recip_nonce_cache: OnceLock<Option<Py<PyBytes>>>,
protection_alg_oid_cache: OnceLock<Option<Py<PyObjectIdentifier>>>,
message_time_cache: OnceLock<Option<Py<PyString>>>,
}
impl PyCMPMessage {
fn msg(&self) -> PyResult<&synta_certificate::cmp_types::PKIMessage<'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::cmp_types::PKIMessage<'_>>()
.map_err(SyntaErr)?;
let decoded: synta_certificate::cmp_types::PKIMessage<'static> =
unsafe { std::mem::transmute(decoded) };
let _ = self.inner.set(Box::new(decoded));
Ok(self.inner.get().unwrap().as_ref())
}
}
#[pymethods]
impl PyCMPMessage {
#[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::cmp_types::PKIMessage<'_>>()
.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(),
pvno_cache: OnceLock::new(),
body_type_cache: OnceLock::new(),
body_der_cache: OnceLock::new(),
sender_der_cache: OnceLock::new(),
recipient_der_cache: OnceLock::new(),
transaction_id_cache: OnceLock::new(),
sender_nonce_cache: OnceLock::new(),
recip_nonce_cache: OnceLock::new(),
protection_alg_oid_cache: OnceLock::new(),
message_time_cache: OnceLock::new(),
})
}
fn to_der<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
Ok(PyBytes::new(py, &self.msg()?.to_der().map_err(SyntaErr)?))
}
#[getter]
fn pvno(&self) -> PyResult<i64> {
if let Some(v) = self.pvno_cache.get() {
return Ok(*v);
}
let v = self.msg()?.header.pvno.as_i64().unwrap_or(2);
let _ = self.pvno_cache.set(v);
Ok(v)
}
#[getter]
fn body_type(&self) -> PyResult<&'static str> {
if let Some(v) = self.body_type_cache.get() {
return Ok(v);
}
let name = body_type_name(&self.msg()?.body);
let _ = self.body_type_cache.set(name);
Ok(name)
}
#[getter]
fn body_der<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.body_der_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let der = body_raw_der(&self.msg()?.body);
let py_opt = der
.as_deref()
.map(|b| PyBytes::new(py, b).as_unbound().clone_ref(py));
let _ = self.body_der_cache.set(py_opt);
Ok(self
.body_der_cache
.get()
.unwrap()
.as_ref()
.map(|b| b.clone_ref(py).into_bound(py)))
}
#[getter]
fn sender_der<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
if let Some(c) = self.sender_der_cache.get() {
return Ok(c.clone_ref(py).into_bound(py));
}
let der = self.msg()?.header.sender.to_der().map_err(SyntaErr)?;
let b = PyBytes::new(py, &der);
let _ = self.sender_der_cache.set(b.as_unbound().clone_ref(py));
Ok(b)
}
#[getter]
fn recipient_der<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
if let Some(c) = self.recipient_der_cache.get() {
return Ok(c.clone_ref(py).into_bound(py));
}
let der = self.msg()?.header.recipient.to_der().map_err(SyntaErr)?;
let b = PyBytes::new(py, &der);
let _ = self.recipient_der_cache.set(b.as_unbound().clone_ref(py));
Ok(b)
}
#[getter]
fn transaction_id<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.transaction_id_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let opt = self
.msg()?
.header
.transaction_id
.as_ref()
.map(|o| PyBytes::new(py, o.as_bytes()).as_unbound().clone_ref(py));
let _ = self.transaction_id_cache.set(opt);
Ok(self
.transaction_id_cache
.get()
.unwrap()
.as_ref()
.map(|b| b.clone_ref(py).into_bound(py)))
}
#[getter]
fn sender_nonce<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.sender_nonce_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let opt = self
.msg()?
.header
.sender_nonce
.as_ref()
.map(|o| PyBytes::new(py, o.as_bytes()).as_unbound().clone_ref(py));
let _ = self.sender_nonce_cache.set(opt);
Ok(self
.sender_nonce_cache
.get()
.unwrap()
.as_ref()
.map(|b| b.clone_ref(py).into_bound(py)))
}
#[getter]
fn recip_nonce<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
if let Some(cached) = self.recip_nonce_cache.get() {
return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
}
let opt = self
.msg()?
.header
.recip_nonce
.as_ref()
.map(|o| PyBytes::new(py, o.as_bytes()).as_unbound().clone_ref(py));
let _ = self.recip_nonce_cache.set(opt);
Ok(self
.recip_nonce_cache
.get()
.unwrap()
.as_ref()
.map(|b| b.clone_ref(py).into_bound(py)))
}
#[getter]
fn protection_alg_oid(&self, py: Python<'_>) -> PyResult<Option<Py<PyObjectIdentifier>>> {
if let Some(cached) = self.protection_alg_oid_cache.get() {
return Ok(cached.as_ref().map(|o| o.clone_ref(py)));
}
let opt = self
.msg()?
.header
.protection_alg
.as_ref()
.map(|alg| Py::new(py, PyObjectIdentifier::from_oid(alg.algorithm.clone())))
.transpose()?;
let _ = self
.protection_alg_oid_cache
.set(opt.as_ref().map(|o| o.clone_ref(py)));
Ok(opt)
}
#[getter]
fn message_time<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyString>>> {
if let Some(cached) = self.message_time_cache.get() {
return Ok(cached.as_ref().map(|s| s.clone_ref(py).into_bound(py)));
}
let opt = self
.msg()?
.header
.message_time
.as_ref()
.map(|t| PyString::new(py, &t.to_string()).as_unbound().clone_ref(py));
let _ = self.message_time_cache.set(opt);
Ok(self
.message_time_cache
.get()
.unwrap()
.as_ref()
.map(|s| s.clone_ref(py).into_bound(py)))
}
fn __repr__(&self) -> PyResult<String> {
Ok(format!(
"CMPMessage(pvno={}, body_type={})",
self.pvno()?,
self.body_type()?,
))
}
}
#[pyclass(name = "CMPMessageBuilder")]
pub struct PyCMPMessageBuilder {
pvno: i64,
sender: Option<synta_certificate::GeneralNameSpec>,
recipient: Option<synta_certificate::GeneralNameSpec>,
transaction_id: Option<Vec<u8>>,
sender_nonce: Option<Vec<u8>>,
recip_nonce: Option<Vec<u8>>,
body_tag: u8,
body_der: Option<Vec<u8>>,
}
#[pymethods]
impl PyCMPMessageBuilder {
#[new]
fn new() -> Self {
Self {
pvno: 2,
sender: None,
recipient: None,
transaction_id: None,
sender_nonce: None,
recip_nonce: None,
body_tag: 0,
body_der: None,
}
}
fn pvno<'py>(slf: Bound<'py, Self>, pvno: i64) -> Bound<'py, Self> {
slf.borrow_mut().pvno = pvno;
slf
}
fn sender_rfc822<'py>(slf: Bound<'py, Self>, email: &str) -> Bound<'py, Self> {
slf.borrow_mut().sender = Some(synta_certificate::GeneralNameSpec::rfc822(email));
slf
}
fn sender_dns<'py>(slf: Bound<'py, Self>, host: &str) -> Bound<'py, Self> {
slf.borrow_mut().sender = Some(synta_certificate::GeneralNameSpec::dns(host));
slf
}
fn sender_directory_name<'py>(slf: Bound<'py, Self>, name_der: &[u8]) -> Bound<'py, Self> {
slf.borrow_mut().sender =
Some(synta_certificate::GeneralNameSpec::directory_name(name_der));
slf
}
fn sender_uri<'py>(slf: Bound<'py, Self>, uri: &str) -> Bound<'py, Self> {
slf.borrow_mut().sender = Some(synta_certificate::GeneralNameSpec::uri(uri));
slf
}
fn recipient_rfc822<'py>(slf: Bound<'py, Self>, email: &str) -> Bound<'py, Self> {
slf.borrow_mut().recipient = Some(synta_certificate::GeneralNameSpec::rfc822(email));
slf
}
fn recipient_dns<'py>(slf: Bound<'py, Self>, host: &str) -> Bound<'py, Self> {
slf.borrow_mut().recipient = Some(synta_certificate::GeneralNameSpec::dns(host));
slf
}
fn recipient_uri<'py>(slf: Bound<'py, Self>, uri: &str) -> Bound<'py, Self> {
slf.borrow_mut().recipient = Some(synta_certificate::GeneralNameSpec::uri(uri));
slf
}
fn recipient_directory_name<'py>(slf: Bound<'py, Self>, name_der: &[u8]) -> Bound<'py, Self> {
slf.borrow_mut().recipient =
Some(synta_certificate::GeneralNameSpec::directory_name(name_der));
slf
}
fn transaction_id<'py>(slf: Bound<'py, Self>, bytes: &[u8]) -> Bound<'py, Self> {
slf.borrow_mut().transaction_id = Some(bytes.to_vec());
slf
}
fn sender_nonce<'py>(slf: Bound<'py, Self>, bytes: &[u8]) -> Bound<'py, Self> {
slf.borrow_mut().sender_nonce = Some(bytes.to_vec());
slf
}
fn recip_nonce<'py>(slf: Bound<'py, Self>, bytes: &[u8]) -> Bound<'py, Self> {
slf.borrow_mut().recip_nonce = Some(bytes.to_vec());
slf
}
fn body_pkiconf<'py>(slf: Bound<'py, Self>) -> Bound<'py, Self> {
let mut b = slf.borrow_mut();
b.body_tag = 0;
b.body_der = None;
drop(b);
slf
}
fn body_ir<'py>(slf: Bound<'py, Self>, cert_req_messages_der: &[u8]) -> Bound<'py, Self> {
let mut b = slf.borrow_mut();
b.body_tag = 1;
b.body_der = Some(cert_req_messages_der.to_vec());
drop(b);
slf
}
fn body_cr<'py>(slf: Bound<'py, Self>, cert_req_messages_der: &[u8]) -> Bound<'py, Self> {
let mut b = slf.borrow_mut();
b.body_tag = 2;
b.body_der = Some(cert_req_messages_der.to_vec());
drop(b);
slf
}
fn body_kur<'py>(slf: Bound<'py, Self>, cert_req_messages_der: &[u8]) -> Bound<'py, Self> {
let mut b = slf.borrow_mut();
b.body_tag = 3;
b.body_der = Some(cert_req_messages_der.to_vec());
drop(b);
slf
}
fn body_p10cr<'py>(slf: Bound<'py, Self>, csr_der: &[u8]) -> Bound<'py, Self> {
let mut b = slf.borrow_mut();
b.body_tag = 4;
b.body_der = Some(csr_der.to_vec());
drop(b);
slf
}
fn body_genm<'py>(slf: Bound<'py, Self>, gen_msg_der: &[u8]) -> Bound<'py, Self> {
let mut b = slf.borrow_mut();
b.body_tag = 5;
b.body_der = Some(gen_msg_der.to_vec());
drop(b);
slf
}
fn build(&self, py: Python<'_>) -> PyResult<PyCMPMessage> {
use pyo3::exceptions::PyValueError;
use synta_certificate::CMPMessageBuilder;
let sender_spec = self
.sender
.clone()
.ok_or_else(|| PyValueError::new_err("sender is required"))?;
let recipient_spec = self
.recipient
.clone()
.ok_or_else(|| PyValueError::new_err("recipient is required"))?;
let mut rust_builder = CMPMessageBuilder::new()
.pvno(self.pvno)
.sender(sender_spec)
.recipient(recipient_spec);
if let Some(ref b) = self.transaction_id {
rust_builder = rust_builder.transaction_id(b);
}
if let Some(ref b) = self.sender_nonce {
rust_builder = rust_builder.sender_nonce(b);
}
if let Some(ref b) = self.recip_nonce {
rust_builder = rust_builder.recip_nonce(b);
}
match self.body_tag {
0 => {
rust_builder = rust_builder.body_pkiconf();
}
1 => {
rust_builder = rust_builder.body_ir(self.body_der.as_deref().unwrap_or(&[]));
}
2 => {
rust_builder = rust_builder.body_cr(self.body_der.as_deref().unwrap_or(&[]));
}
3 => {
rust_builder = rust_builder.body_kur(self.body_der.as_deref().unwrap_or(&[]));
}
4 => {
rust_builder = rust_builder.body_p10cr(self.body_der.as_deref().unwrap_or(&[]));
}
5 => {
rust_builder = rust_builder.body_genm(self.body_der.as_deref().unwrap_or(&[]));
}
_ => {}
}
let der = rust_builder
.build()
.map_err(|e| PyValueError::new_err(e.to_string()))?;
let py_bytes = PyBytes::new(py, &der).unbind();
let raw: &'static [u8] = unsafe { std::mem::transmute(py_bytes.as_bytes(py)) };
Ok(PyCMPMessage {
_data: py_bytes,
raw,
inner: OnceLock::new(),
pvno_cache: OnceLock::new(),
body_type_cache: OnceLock::new(),
body_der_cache: OnceLock::new(),
sender_der_cache: OnceLock::new(),
recipient_der_cache: OnceLock::new(),
transaction_id_cache: OnceLock::new(),
sender_nonce_cache: OnceLock::new(),
recip_nonce_cache: OnceLock::new(),
protection_alg_oid_cache: OnceLock::new(),
message_time_cache: OnceLock::new(),
})
}
fn __repr__(&self) -> String {
use synta_certificate::GeneralNameSpec;
let sender = match &self.sender {
None => "None".to_string(),
Some(GeneralNameSpec::Rfc822(s)) => format!("rfc822:{s}"),
Some(GeneralNameSpec::Dns(s)) => format!("dns:{s}"),
Some(GeneralNameSpec::Uri(s)) => format!("uri:{s}"),
Some(GeneralNameSpec::DirectoryName(_)) => "directoryName".to_string(),
Some(GeneralNameSpec::IpAddress(_)) => "iPAddress".to_string(),
Some(GeneralNameSpec::RegisteredId(oid)) => format!("registeredID:{oid}"),
};
format!("CMPMessageBuilder(pvno={}, sender={})", self.pvno, sender)
}
}
pub(super) fn register_cmp_submodule(parent: &Bound<'_, PyModule>) -> PyResult<()> {
let py = parent.py();
let m = PyModule::new(py, "cmp")?;
m.add_class::<PyCMPMessage>()?;
m.add_class::<PyCMPMessageBuilder>()?;
m.add(
"ID_PASSWORD_BASED_MAC",
super::oid_const(py, synta_certificate::cmp_types::ID_PASSWORD_BASED_MAC),
)?;
m.add(
"ID_DHBASED_MAC",
super::oid_const(py, synta_certificate::cmp_types::ID_DHBASED_MAC),
)?;
m.add(
"ID_KEM_BASED_MAC",
super::oid_const(py, synta_certificate::cmp_types::ID_KEM_BASED_MAC),
)?;
m.add(
"ID_KP_CM_KGA",
super::oid_const(py, synta_certificate::cmp_types::ID_KP_CM_KGA),
)?;
m.add(
"ID_REG_CTRL_ALT_CERT_TEMPLATE",
super::oid_const(
py,
synta_certificate::cmp_types::ID_REG_CTRL_ALT_CERT_TEMPLATE,
),
)?;
m.add(
"ID_REG_CTRL_ALG_ID",
super::oid_const(py, synta_certificate::cmp_types::ID_REG_CTRL_ALG_ID),
)?;
m.add(
"ID_REG_CTRL_RSA_KEY_LEN",
super::oid_const(py, synta_certificate::cmp_types::ID_REG_CTRL_RSA_KEY_LEN),
)?;
crate::install_submodule(
parent,
&m,
"synta.cmp",
Some(concat!(
"synta.cmp — RFC 9810 Certificate Management Protocol v3 types.\n\n",
"Provides CMPMessage (wrapping PKIMessage) for decoding CMP\n",
"envelopes. body_type names the active PKIBody arm; body_der\n",
"returns the raw bytes for further decoding with synta.crmf or\n",
"a synta.Decoder. Also exposes MAC algorithm and key-purpose\n",
"OID constants.",
)),
)
}