1use std::sync::OnceLock;
8
9use pyo3::prelude::*;
10use pyo3::types::{PyBytes, PyString};
11
12use synta::{Decoder, Encoding};
13
14use crate::error::SyntaErr;
15use crate::types::PyObjectIdentifier;
16
17fn body_type_name(body: &synta_certificate::cmp_types::PKIBody<'_>) -> &'static str {
21 use synta_certificate::cmp_types::PKIBody::*;
22 match body {
23 Ir(_) => "ir",
24 Ip(_) => "ip",
25 Cr(_) => "cr",
26 Cp(_) => "cp",
27 P10cr(_) => "p10cr",
28 Popdecc(_) => "popdecc",
29 Popdecr(_) => "popdecr",
30 Kur(_) => "kur",
31 Kup(_) => "kup",
32 Krr(_) => "krr",
33 Krp(_) => "krp",
34 Rr(_) => "rr",
35 Rp(_) => "rp",
36 Ccr(_) => "ccr",
37 Ccp(_) => "ccp",
38 Ckuann(_) => "ckuann",
39 Cann(_) => "cann",
40 Rann(_) => "rann",
41 Crlann(_) => "crlann",
42 Pkiconf(_) => "pkiconf",
43 Nested(_) => "nested",
44 Genm(_) => "genm",
45 Genp(_) => "genp",
46 Error(_) => "error",
47 CertConf(_) => "certConf",
48 PollReq(_) => "pollReq",
49 PollRep(_) => "pollRep",
50 }
51}
52
53fn body_raw_der(body: &synta_certificate::cmp_types::PKIBody<'_>) -> Option<Vec<u8>> {
56 use synta_certificate::cmp_types::PKIBody::*;
57 match body {
58 Pkiconf(_) => None,
59 Ir(r) | Ip(r) | Cr(r) | Cp(r) | P10cr(r) | Popdecc(r) | Popdecr(r) | Kur(r) | Kup(r)
60 | Krr(r) | Krp(r) | Rr(r) | Rp(r) | Ccr(r) | Ccp(r) | Ckuann(r) | Cann(r) | Rann(r)
61 | Crlann(r) | Nested(r) | Genm(r) | Genp(r) | Error(r) | CertConf(r) | PollReq(r)
62 | PollRep(r) => Some(r.as_bytes().to_vec()),
63 }
64}
65
66#[pyclass(frozen, name = "CMPMessage")]
92pub struct PyCMPMessage {
93 _data: Py<PyBytes>,
94 raw: &'static [u8],
95 inner: OnceLock<Box<synta_certificate::cmp_types::PKIMessage<'static>>>,
96 pvno_cache: OnceLock<i64>,
98 body_type_cache: OnceLock<&'static str>,
99 body_der_cache: OnceLock<Option<Py<PyBytes>>>,
100 sender_der_cache: OnceLock<Py<PyBytes>>,
101 recipient_der_cache: OnceLock<Py<PyBytes>>,
102 transaction_id_cache: OnceLock<Option<Py<PyBytes>>>,
103 sender_nonce_cache: OnceLock<Option<Py<PyBytes>>>,
104 recip_nonce_cache: OnceLock<Option<Py<PyBytes>>>,
105 protection_alg_oid_cache: OnceLock<Option<Py<PyObjectIdentifier>>>,
106 message_time_cache: OnceLock<Option<Py<PyString>>>,
107}
108
109impl PyCMPMessage {
110 fn msg(&self) -> PyResult<&synta_certificate::cmp_types::PKIMessage<'static>> {
111 if let Some(v) = self.inner.get() {
112 return Ok(v.as_ref());
113 }
114 let mut dec = Decoder::new(self.raw, Encoding::Der);
115 let decoded = dec
116 .decode::<synta_certificate::cmp_types::PKIMessage<'_>>()
117 .map_err(SyntaErr)?;
118 let decoded: synta_certificate::cmp_types::PKIMessage<'static> =
120 unsafe { std::mem::transmute(decoded) };
121 let _ = self.inner.set(Box::new(decoded));
122 Ok(self.inner.get().unwrap().as_ref())
123 }
124}
125
126#[pymethods]
127impl PyCMPMessage {
128 #[staticmethod]
133 fn from_der(py: Python<'_>, data: Bound<'_, PyBytes>) -> PyResult<Self> {
134 let py_bytes = data.unbind();
135 {
136 let raw = py_bytes.as_bytes(py);
137 Decoder::new(raw, Encoding::Der)
138 .decode::<synta_certificate::cmp_types::PKIMessage<'_>>()
139 .map_err(SyntaErr)?;
140 }
141 let raw: &'static [u8] = unsafe { std::mem::transmute(py_bytes.as_bytes(py)) };
142 Ok(Self {
143 _data: py_bytes,
144 raw,
145 inner: OnceLock::new(),
146 pvno_cache: OnceLock::new(),
147 body_type_cache: OnceLock::new(),
148 body_der_cache: OnceLock::new(),
149 sender_der_cache: OnceLock::new(),
150 recipient_der_cache: OnceLock::new(),
151 transaction_id_cache: OnceLock::new(),
152 sender_nonce_cache: OnceLock::new(),
153 recip_nonce_cache: OnceLock::new(),
154 protection_alg_oid_cache: OnceLock::new(),
155 message_time_cache: OnceLock::new(),
156 })
157 }
158
159 fn to_der<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
161 Ok(PyBytes::new(py, &self.msg()?.to_der().map_err(SyntaErr)?))
162 }
163
164 #[getter]
166 fn pvno(&self) -> PyResult<i64> {
167 if let Some(v) = self.pvno_cache.get() {
168 return Ok(*v);
169 }
170 let v = self.msg()?.header.pvno.as_i64().unwrap_or(2);
171 let _ = self.pvno_cache.set(v);
172 Ok(v)
173 }
174
175 #[getter]
178 fn body_type(&self) -> PyResult<&'static str> {
179 if let Some(v) = self.body_type_cache.get() {
180 return Ok(v);
181 }
182 let name = body_type_name(&self.msg()?.body);
183 let _ = self.body_type_cache.set(name);
184 Ok(name)
185 }
186
187 #[getter]
193 fn body_der<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
194 if let Some(cached) = self.body_der_cache.get() {
195 return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
196 }
197 let der = body_raw_der(&self.msg()?.body);
198 let py_opt = der
199 .as_deref()
200 .map(|b| PyBytes::new(py, b).as_unbound().clone_ref(py));
201 let _ = self.body_der_cache.set(py_opt);
202 Ok(self
203 .body_der_cache
204 .get()
205 .unwrap()
206 .as_ref()
207 .map(|b| b.clone_ref(py).into_bound(py)))
208 }
209
210 #[getter]
212 fn sender_der<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
213 if let Some(c) = self.sender_der_cache.get() {
214 return Ok(c.clone_ref(py).into_bound(py));
215 }
216 let der = self.msg()?.header.sender.to_der().map_err(SyntaErr)?;
217 let b = PyBytes::new(py, &der);
218 let _ = self.sender_der_cache.set(b.as_unbound().clone_ref(py));
219 Ok(b)
220 }
221
222 #[getter]
224 fn recipient_der<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
225 if let Some(c) = self.recipient_der_cache.get() {
226 return Ok(c.clone_ref(py).into_bound(py));
227 }
228 let der = self.msg()?.header.recipient.to_der().map_err(SyntaErr)?;
229 let b = PyBytes::new(py, &der);
230 let _ = self.recipient_der_cache.set(b.as_unbound().clone_ref(py));
231 Ok(b)
232 }
233
234 #[getter]
236 fn transaction_id<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
237 if let Some(cached) = self.transaction_id_cache.get() {
238 return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
239 }
240 let opt = self
241 .msg()?
242 .header
243 .transaction_id
244 .as_ref()
245 .map(|o| PyBytes::new(py, o.as_bytes()).as_unbound().clone_ref(py));
246 let _ = self.transaction_id_cache.set(opt);
247 Ok(self
248 .transaction_id_cache
249 .get()
250 .unwrap()
251 .as_ref()
252 .map(|b| b.clone_ref(py).into_bound(py)))
253 }
254
255 #[getter]
257 fn sender_nonce<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
258 if let Some(cached) = self.sender_nonce_cache.get() {
259 return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
260 }
261 let opt = self
262 .msg()?
263 .header
264 .sender_nonce
265 .as_ref()
266 .map(|o| PyBytes::new(py, o.as_bytes()).as_unbound().clone_ref(py));
267 let _ = self.sender_nonce_cache.set(opt);
268 Ok(self
269 .sender_nonce_cache
270 .get()
271 .unwrap()
272 .as_ref()
273 .map(|b| b.clone_ref(py).into_bound(py)))
274 }
275
276 #[getter]
278 fn recip_nonce<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyBytes>>> {
279 if let Some(cached) = self.recip_nonce_cache.get() {
280 return Ok(cached.as_ref().map(|b| b.clone_ref(py).into_bound(py)));
281 }
282 let opt = self
283 .msg()?
284 .header
285 .recip_nonce
286 .as_ref()
287 .map(|o| PyBytes::new(py, o.as_bytes()).as_unbound().clone_ref(py));
288 let _ = self.recip_nonce_cache.set(opt);
289 Ok(self
290 .recip_nonce_cache
291 .get()
292 .unwrap()
293 .as_ref()
294 .map(|b| b.clone_ref(py).into_bound(py)))
295 }
296
297 #[getter]
299 fn protection_alg_oid(&self, py: Python<'_>) -> PyResult<Option<Py<PyObjectIdentifier>>> {
300 if let Some(cached) = self.protection_alg_oid_cache.get() {
301 return Ok(cached.as_ref().map(|o| o.clone_ref(py)));
302 }
303 let opt = self
304 .msg()?
305 .header
306 .protection_alg
307 .as_ref()
308 .map(|alg| Py::new(py, PyObjectIdentifier::from_oid(alg.algorithm.clone())))
309 .transpose()?;
310 let _ = self
311 .protection_alg_oid_cache
312 .set(opt.as_ref().map(|o| o.clone_ref(py)));
313 Ok(opt)
314 }
315
316 #[getter]
318 fn message_time<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyString>>> {
319 if let Some(cached) = self.message_time_cache.get() {
320 return Ok(cached.as_ref().map(|s| s.clone_ref(py).into_bound(py)));
321 }
322 let opt = self
323 .msg()?
324 .header
325 .message_time
326 .as_ref()
327 .map(|t| PyString::new(py, &t.to_string()).as_unbound().clone_ref(py));
328 let _ = self.message_time_cache.set(opt);
329 Ok(self
330 .message_time_cache
331 .get()
332 .unwrap()
333 .as_ref()
334 .map(|s| s.clone_ref(py).into_bound(py)))
335 }
336
337 fn __repr__(&self) -> PyResult<String> {
338 Ok(format!(
339 "CMPMessage(pvno={}, body_type={})",
340 self.pvno()?,
341 self.body_type()?,
342 ))
343 }
344}
345
346#[pyclass(name = "CMPMessageBuilder")]
367pub struct PyCMPMessageBuilder {
368 pvno: i64,
369 sender: Option<synta_certificate::GeneralNameSpec>,
370 recipient: Option<synta_certificate::GeneralNameSpec>,
371 transaction_id: Option<Vec<u8>>,
372 sender_nonce: Option<Vec<u8>>,
373 recip_nonce: Option<Vec<u8>>,
374 body_tag: u8,
376 body_der: Option<Vec<u8>>,
377}
378
379#[pymethods]
380impl PyCMPMessageBuilder {
381 #[new]
382 fn new() -> Self {
383 Self {
384 pvno: 2,
385 sender: None,
386 recipient: None,
387 transaction_id: None,
388 sender_nonce: None,
389 recip_nonce: None,
390 body_tag: 0,
391 body_der: None,
392 }
393 }
394
395 fn pvno<'py>(slf: Bound<'py, Self>, pvno: i64) -> Bound<'py, Self> {
397 slf.borrow_mut().pvno = pvno;
398 slf
399 }
400
401 fn sender_rfc822<'py>(slf: Bound<'py, Self>, email: &str) -> Bound<'py, Self> {
403 slf.borrow_mut().sender = Some(synta_certificate::GeneralNameSpec::rfc822(email));
404 slf
405 }
406
407 fn sender_dns<'py>(slf: Bound<'py, Self>, host: &str) -> Bound<'py, Self> {
409 slf.borrow_mut().sender = Some(synta_certificate::GeneralNameSpec::dns(host));
410 slf
411 }
412
413 fn sender_directory_name<'py>(slf: Bound<'py, Self>, name_der: &[u8]) -> Bound<'py, Self> {
418 slf.borrow_mut().sender =
419 Some(synta_certificate::GeneralNameSpec::directory_name(name_der));
420 slf
421 }
422
423 fn sender_uri<'py>(slf: Bound<'py, Self>, uri: &str) -> Bound<'py, Self> {
425 slf.borrow_mut().sender = Some(synta_certificate::GeneralNameSpec::uri(uri));
426 slf
427 }
428
429 fn recipient_rfc822<'py>(slf: Bound<'py, Self>, email: &str) -> Bound<'py, Self> {
431 slf.borrow_mut().recipient = Some(synta_certificate::GeneralNameSpec::rfc822(email));
432 slf
433 }
434
435 fn recipient_dns<'py>(slf: Bound<'py, Self>, host: &str) -> Bound<'py, Self> {
437 slf.borrow_mut().recipient = Some(synta_certificate::GeneralNameSpec::dns(host));
438 slf
439 }
440
441 fn recipient_uri<'py>(slf: Bound<'py, Self>, uri: &str) -> Bound<'py, Self> {
443 slf.borrow_mut().recipient = Some(synta_certificate::GeneralNameSpec::uri(uri));
444 slf
445 }
446
447 fn recipient_directory_name<'py>(slf: Bound<'py, Self>, name_der: &[u8]) -> Bound<'py, Self> {
449 slf.borrow_mut().recipient =
450 Some(synta_certificate::GeneralNameSpec::directory_name(name_der));
451 slf
452 }
453
454 fn transaction_id<'py>(slf: Bound<'py, Self>, bytes: &[u8]) -> Bound<'py, Self> {
456 slf.borrow_mut().transaction_id = Some(bytes.to_vec());
457 slf
458 }
459
460 fn sender_nonce<'py>(slf: Bound<'py, Self>, bytes: &[u8]) -> Bound<'py, Self> {
462 slf.borrow_mut().sender_nonce = Some(bytes.to_vec());
463 slf
464 }
465
466 fn recip_nonce<'py>(slf: Bound<'py, Self>, bytes: &[u8]) -> Bound<'py, Self> {
468 slf.borrow_mut().recip_nonce = Some(bytes.to_vec());
469 slf
470 }
471
472 fn body_pkiconf<'py>(slf: Bound<'py, Self>) -> Bound<'py, Self> {
474 let mut b = slf.borrow_mut();
475 b.body_tag = 0;
476 b.body_der = None;
477 drop(b);
478 slf
479 }
480
481 fn body_ir<'py>(slf: Bound<'py, Self>, cert_req_messages_der: &[u8]) -> Bound<'py, Self> {
484 let mut b = slf.borrow_mut();
485 b.body_tag = 1;
486 b.body_der = Some(cert_req_messages_der.to_vec());
487 drop(b);
488 slf
489 }
490
491 fn body_cr<'py>(slf: Bound<'py, Self>, cert_req_messages_der: &[u8]) -> Bound<'py, Self> {
494 let mut b = slf.borrow_mut();
495 b.body_tag = 2;
496 b.body_der = Some(cert_req_messages_der.to_vec());
497 drop(b);
498 slf
499 }
500
501 fn body_kur<'py>(slf: Bound<'py, Self>, cert_req_messages_der: &[u8]) -> Bound<'py, Self> {
504 let mut b = slf.borrow_mut();
505 b.body_tag = 3;
506 b.body_der = Some(cert_req_messages_der.to_vec());
507 drop(b);
508 slf
509 }
510
511 fn body_p10cr<'py>(slf: Bound<'py, Self>, csr_der: &[u8]) -> Bound<'py, Self> {
514 let mut b = slf.borrow_mut();
515 b.body_tag = 4;
516 b.body_der = Some(csr_der.to_vec());
517 drop(b);
518 slf
519 }
520
521 fn body_genm<'py>(slf: Bound<'py, Self>, gen_msg_der: &[u8]) -> Bound<'py, Self> {
524 let mut b = slf.borrow_mut();
525 b.body_tag = 5;
526 b.body_der = Some(gen_msg_der.to_vec());
527 drop(b);
528 slf
529 }
530
531 fn build(&self, py: Python<'_>) -> PyResult<PyCMPMessage> {
536 use pyo3::exceptions::PyValueError;
537 use synta_certificate::CMPMessageBuilder;
538
539 let sender_spec = self
540 .sender
541 .clone()
542 .ok_or_else(|| PyValueError::new_err("sender is required"))?;
543 let recipient_spec = self
544 .recipient
545 .clone()
546 .ok_or_else(|| PyValueError::new_err("recipient is required"))?;
547
548 let mut rust_builder = CMPMessageBuilder::new()
549 .pvno(self.pvno)
550 .sender(sender_spec)
551 .recipient(recipient_spec);
552
553 if let Some(ref b) = self.transaction_id {
554 rust_builder = rust_builder.transaction_id(b);
555 }
556 if let Some(ref b) = self.sender_nonce {
557 rust_builder = rust_builder.sender_nonce(b);
558 }
559 if let Some(ref b) = self.recip_nonce {
560 rust_builder = rust_builder.recip_nonce(b);
561 }
562
563 match self.body_tag {
564 0 => {
565 rust_builder = rust_builder.body_pkiconf();
566 }
567 1 => {
568 rust_builder = rust_builder.body_ir(self.body_der.as_deref().unwrap_or(&[]));
569 }
570 2 => {
571 rust_builder = rust_builder.body_cr(self.body_der.as_deref().unwrap_or(&[]));
572 }
573 3 => {
574 rust_builder = rust_builder.body_kur(self.body_der.as_deref().unwrap_or(&[]));
575 }
576 4 => {
577 rust_builder = rust_builder.body_p10cr(self.body_der.as_deref().unwrap_or(&[]));
578 }
579 5 => {
580 rust_builder = rust_builder.body_genm(self.body_der.as_deref().unwrap_or(&[]));
581 }
582 _ => {}
583 }
584
585 let der = rust_builder
586 .build()
587 .map_err(|e| PyValueError::new_err(e.to_string()))?;
588
589 let py_bytes = PyBytes::new(py, &der).unbind();
590 let raw: &'static [u8] = unsafe { std::mem::transmute(py_bytes.as_bytes(py)) };
591 Ok(PyCMPMessage {
592 _data: py_bytes,
593 raw,
594 inner: OnceLock::new(),
595 pvno_cache: OnceLock::new(),
596 body_type_cache: OnceLock::new(),
597 body_der_cache: OnceLock::new(),
598 sender_der_cache: OnceLock::new(),
599 recipient_der_cache: OnceLock::new(),
600 transaction_id_cache: OnceLock::new(),
601 sender_nonce_cache: OnceLock::new(),
602 recip_nonce_cache: OnceLock::new(),
603 protection_alg_oid_cache: OnceLock::new(),
604 message_time_cache: OnceLock::new(),
605 })
606 }
607
608 fn __repr__(&self) -> String {
609 use synta_certificate::GeneralNameSpec;
610 let sender = match &self.sender {
611 None => "None".to_string(),
612 Some(GeneralNameSpec::Rfc822(s)) => format!("rfc822:{s}"),
613 Some(GeneralNameSpec::Dns(s)) => format!("dns:{s}"),
614 Some(GeneralNameSpec::Uri(s)) => format!("uri:{s}"),
615 Some(GeneralNameSpec::DirectoryName(_)) => "directoryName".to_string(),
616 Some(GeneralNameSpec::IpAddress(_)) => "iPAddress".to_string(),
617 Some(GeneralNameSpec::RegisteredId(oid)) => format!("registeredID:{oid}"),
618 };
619 format!("CMPMessageBuilder(pvno={}, sender={})", self.pvno, sender)
620 }
621}
622
623pub(super) fn register_cmp_submodule(parent: &Bound<'_, PyModule>) -> PyResult<()> {
627 let py = parent.py();
628 let m = PyModule::new(py, "cmp")?;
629
630 m.add_class::<PyCMPMessage>()?;
631 m.add_class::<PyCMPMessageBuilder>()?;
632
633 m.add(
635 "ID_PASSWORD_BASED_MAC",
636 super::oid_const(py, synta_certificate::cmp_types::ID_PASSWORD_BASED_MAC),
637 )?;
638 m.add(
639 "ID_DHBASED_MAC",
640 super::oid_const(py, synta_certificate::cmp_types::ID_DHBASED_MAC),
641 )?;
642 m.add(
643 "ID_KEM_BASED_MAC",
644 super::oid_const(py, synta_certificate::cmp_types::ID_KEM_BASED_MAC),
645 )?;
646
647 m.add(
649 "ID_KP_CM_KGA",
650 super::oid_const(py, synta_certificate::cmp_types::ID_KP_CM_KGA),
651 )?;
652
653 m.add(
655 "ID_REG_CTRL_ALT_CERT_TEMPLATE",
656 super::oid_const(
657 py,
658 synta_certificate::cmp_types::ID_REG_CTRL_ALT_CERT_TEMPLATE,
659 ),
660 )?;
661 m.add(
662 "ID_REG_CTRL_ALG_ID",
663 super::oid_const(py, synta_certificate::cmp_types::ID_REG_CTRL_ALG_ID),
664 )?;
665 m.add(
666 "ID_REG_CTRL_RSA_KEY_LEN",
667 super::oid_const(py, synta_certificate::cmp_types::ID_REG_CTRL_RSA_KEY_LEN),
668 )?;
669
670 crate::install_submodule(
671 parent,
672 &m,
673 "synta.cmp",
674 Some(concat!(
675 "synta.cmp — RFC 9810 Certificate Management Protocol v3 types.\n\n",
676 "Provides CMPMessage (wrapping PKIMessage) for decoding CMP\n",
677 "envelopes. body_type names the active PKIBody arm; body_der\n",
678 "returns the raw bytes for further decoding with synta.crmf or\n",
679 "a synta.Decoder. Also exposes MAC algorithm and key-purpose\n",
680 "OID constants.",
681 )),
682 )
683}