1use std::sync::OnceLock;
8
9use pyo3::prelude::*;
10use pyo3::types::{PyBytes, PyList, PyString};
11
12use synta::traits::Encode;
13use synta::{Decoder, Encoding};
14
15use crate::error::SyntaErr;
16
17fn encode_to_der<T: Encode>(v: &T) -> Vec<u8> {
20 let mut enc = synta::Encoder::new(Encoding::Der);
21 if v.encode(&mut enc).is_err() {
22 return Vec::new();
23 }
24 enc.finish().unwrap_or_default()
25}
26
27#[pyclass(frozen, name = "CertReqMsg")]
40pub struct PyCertReqMsg {
41 cert_req_id: i64,
42 cert_template_der: Vec<u8>,
43 popo_type: Option<String>,
44 popo_der: Option<Vec<u8>>,
45 subject_der: Option<Vec<u8>>,
46 issuer_der: Option<Vec<u8>>,
47}
48
49impl PyCertReqMsg {
50 fn from_rust(msg: &synta_certificate::crmf_types::CertReqMsg<'_>) -> Self {
51 let cert_req_id = msg.cert_req.cert_req_id.as_i64().unwrap_or(-1);
52
53 let cert_template_der = encode_to_der(&msg.cert_req.cert_template);
54
55 let (popo_type, popo_der) = match &msg.popo {
56 None => (None, None),
57 Some(pop) => {
58 use synta_certificate::crmf_types::ProofOfPossession::*;
59 let arm = match pop {
60 RaVerified(_) => "raVerified",
61 Signature(_) => "signature",
62 KeyEncipherment(_) => "keyEncipherment",
63 KeyAgreement(_) => "keyAgreement",
64 };
65 let der = encode_to_der(pop);
66 (Some(arm.to_string()), Some(der))
67 }
68 };
69
70 let subject_der = msg
71 .cert_req
72 .cert_template
73 .subject
74 .as_ref()
75 .map(encode_to_der);
76 let issuer_der = msg
77 .cert_req
78 .cert_template
79 .issuer
80 .as_ref()
81 .map(encode_to_der);
82
83 Self {
84 cert_req_id,
85 cert_template_der,
86 popo_type,
87 popo_der,
88 subject_der,
89 issuer_der,
90 }
91 }
92}
93
94#[pymethods]
95impl PyCertReqMsg {
96 #[getter]
98 fn cert_req_id(&self) -> i64 {
99 self.cert_req_id
100 }
101
102 #[getter]
107 fn cert_template_der<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
108 PyBytes::new(py, &self.cert_template_der)
109 }
110
111 #[getter]
116 fn popo_type<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyString>> {
117 self.popo_type.as_deref().map(|s| PyString::new(py, s))
118 }
119
120 #[getter]
122 fn popo_der<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyBytes>> {
123 self.popo_der.as_deref().map(|b| PyBytes::new(py, b))
124 }
125
126 #[getter]
129 fn subject_der<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyBytes>> {
130 self.subject_der.as_deref().map(|b| PyBytes::new(py, b))
131 }
132
133 #[getter]
136 fn issuer_der<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyBytes>> {
137 self.issuer_der.as_deref().map(|b| PyBytes::new(py, b))
138 }
139
140 fn __repr__(&self) -> String {
141 format!(
142 "CertReqMsg(cert_req_id={}, popo_type={})",
143 self.cert_req_id,
144 self.popo_type.as_deref().unwrap_or("None"),
145 )
146 }
147}
148
149#[pyclass(frozen, name = "CertReqMessages")]
165pub struct PyCertReqMessages {
166 _data: Py<PyBytes>,
167 raw: &'static [u8],
168 inner: OnceLock<Vec<synta_certificate::crmf_types::CertReqMsg<'static>>>,
169 requests_cache: OnceLock<Py<PyList>>,
170}
171
172impl PyCertReqMessages {
173 fn msgs(&self) -> PyResult<&Vec<synta_certificate::crmf_types::CertReqMsg<'static>>> {
174 if let Some(v) = self.inner.get() {
175 return Ok(v);
176 }
177 let mut dec = Decoder::new(self.raw, Encoding::Der);
178 let decoded = dec
179 .decode::<synta_certificate::crmf_types::CertReqMessages<'_>>()
180 .map_err(SyntaErr)?;
181 let decoded: Vec<synta_certificate::crmf_types::CertReqMsg<'static>> =
183 unsafe { std::mem::transmute(decoded) };
184 let _ = self.inner.set(decoded);
185 Ok(self.inner.get().unwrap())
186 }
187
188 fn build_requests_list<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyList>> {
189 let msgs = self.msgs()?;
190 let list = PyList::empty(py);
191 for msg in msgs {
192 let obj = Py::new(py, PyCertReqMsg::from_rust(msg))?;
193 list.append(obj)?;
194 }
195 Ok(list)
196 }
197}
198
199#[pymethods]
200impl PyCertReqMessages {
201 #[staticmethod]
206 fn from_der(py: Python<'_>, data: Bound<'_, PyBytes>) -> PyResult<Self> {
207 let py_bytes = data.unbind();
208 {
209 let raw = py_bytes.as_bytes(py);
210 Decoder::new(raw, Encoding::Der)
211 .decode::<synta_certificate::crmf_types::CertReqMessages<'_>>()
212 .map_err(SyntaErr)?;
213 }
214 let raw: &'static [u8] = unsafe { std::mem::transmute(py_bytes.as_bytes(py)) };
215 Ok(Self {
216 _data: py_bytes,
217 raw,
218 inner: OnceLock::new(),
219 requests_cache: OnceLock::new(),
220 })
221 }
222
223 fn to_der<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
225 let msgs = self.msgs()?;
226 let mut enc = synta::Encoder::new(Encoding::Der);
227 msgs.encode(&mut enc).map_err(SyntaErr)?;
228 Ok(PyBytes::new(py, &enc.finish().map_err(SyntaErr)?))
229 }
230
231 #[getter]
233 fn requests<'py>(&'py self, py: Python<'py>) -> PyResult<Bound<'py, PyList>> {
234 if let Some(c) = self.requests_cache.get() {
235 return Ok(c.clone_ref(py).into_bound(py));
236 }
237 let list = self.build_requests_list(py)?;
238 let _ = self.requests_cache.set(list.as_unbound().clone_ref(py));
239 Ok(list)
240 }
241
242 fn __len__(&self) -> PyResult<usize> {
243 Ok(self.msgs()?.len())
244 }
245
246 fn __iter__<'py>(&'py self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
247 self.requests(py)?.call_method0("__iter__")
248 }
249
250 fn __getitem__(&self, py: Python<'_>, index: usize) -> PyResult<Py<PyCertReqMsg>> {
251 let msgs = self.msgs()?;
252 if index >= msgs.len() {
253 return Err(pyo3::exceptions::PyIndexError::new_err(format!(
254 "index {index} out of range for CertReqMessages with {} entries",
255 msgs.len()
256 )));
257 }
258 Py::new(py, PyCertReqMsg::from_rust(&msgs[index]))
259 }
260
261 fn __repr__(&self) -> PyResult<String> {
262 Ok(format!("CertReqMessages({} requests)", self.msgs()?.len()))
263 }
264}
265
266pub(super) fn register_crmf_submodule(parent: &Bound<'_, PyModule>) -> PyResult<()> {
270 let py = parent.py();
271 let m = PyModule::new(py, "crmf")?;
272
273 m.add_class::<PyCertReqMessages>()?;
274 m.add_class::<PyCertReqMsg>()?;
275
276 m.add(
278 "ID_REG_CTRL_REG_TOKEN",
279 super::oid_const(py, synta_certificate::crmf_types::ID_REG_CTRL_REG_TOKEN),
280 )?;
281 m.add(
282 "ID_REG_CTRL_AUTHENTICATOR",
283 super::oid_const(py, synta_certificate::crmf_types::ID_REG_CTRL_AUTHENTICATOR),
284 )?;
285 m.add(
286 "ID_REG_CTRL_PKI_PUBLICATION_INFO",
287 super::oid_const(
288 py,
289 synta_certificate::crmf_types::ID_REG_CTRL_PKI_PUBLICATION_INFO,
290 ),
291 )?;
292 m.add(
293 "ID_REG_CTRL_PKI_ARCHIVE_OPTIONS",
294 super::oid_const(
295 py,
296 synta_certificate::crmf_types::ID_REG_CTRL_PKI_ARCHIVE_OPTIONS,
297 ),
298 )?;
299 m.add(
300 "ID_REG_CTRL_OLD_CERT_ID",
301 super::oid_const(py, synta_certificate::crmf_types::ID_REG_CTRL_OLD_CERT_ID),
302 )?;
303 m.add(
304 "ID_REG_CTRL_PROTOCOL_ENCR_KEY",
305 super::oid_const(
306 py,
307 synta_certificate::crmf_types::ID_REG_CTRL_PROTOCOL_ENCR_KEY,
308 ),
309 )?;
310 m.add(
311 "ID_REG_INFO_UTF8_PAIRS",
312 super::oid_const(py, synta_certificate::crmf_types::ID_REG_INFO_UTF8_PAIRS),
313 )?;
314 m.add(
315 "ID_REG_INFO_CERT_REQ",
316 super::oid_const(py, synta_certificate::crmf_types::ID_REG_INFO_CERT_REQ),
317 )?;
318
319 crate::install_submodule(
320 parent,
321 &m,
322 "synta.crmf",
323 Some(concat!(
324 "synta.crmf — RFC 4211 Certificate Request Message Format types.\n\n",
325 "Provides CertReqMessages (SEQUENCE OF CertReqMsg) and CertReqMsg\n",
326 "for decoding CRMF batches used in CMP certificate management,\n",
327 "along with registration-control OID constants.",
328 )),
329 )
330}