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<'static>>()
117 .map_err(SyntaErr)?;
118 let _ = self.inner.set(Box::new(decoded));
119 Ok(self.inner.get().unwrap().as_ref())
120 }
121}
122
123#[pymethods]
124impl PyCMPMessage {
125 #[staticmethod]
130 fn from_der(py: Python<'_>, data: Bound<'_, PyBytes>) -> PyResult<Self> {
131 let py_bytes = data.unbind();
132 {
133 let raw = py_bytes.as_bytes(py);
134 Decoder::new(raw, Encoding::Der)
135 .decode::<synta_certificate::cmp_types::PKIMessage<'_>>()
136 .map_err(SyntaErr)?;
137 }
138 let raw: &'static [u8] = unsafe {
139 let s = py_bytes.bind(py).as_bytes();
140 std::slice::from_raw_parts(s.as_ptr(), s.len())
141 };
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]
383 fn new() -> Self {
384 Self {
385 pvno: 2,
386 sender: None,
387 recipient: None,
388 transaction_id: None,
389 sender_nonce: None,
390 recip_nonce: None,
391 body_tag: 0,
392 body_der: None,
393 }
394 }
395
396 fn pvno<'py>(slf: Bound<'py, Self>, pvno: i64) -> Bound<'py, Self> {
398 slf.borrow_mut().pvno = pvno;
399 slf
400 }
401
402 fn sender_rfc822<'py>(slf: Bound<'py, Self>, email: &str) -> Bound<'py, Self> {
404 slf.borrow_mut().sender = Some(synta_certificate::GeneralNameSpec::rfc822(email));
405 slf
406 }
407
408 fn sender_dns<'py>(slf: Bound<'py, Self>, host: &str) -> Bound<'py, Self> {
410 slf.borrow_mut().sender = Some(synta_certificate::GeneralNameSpec::dns(host));
411 slf
412 }
413
414 fn sender_directory_name<'py>(slf: Bound<'py, Self>, name_der: &[u8]) -> Bound<'py, Self> {
419 slf.borrow_mut().sender =
420 Some(synta_certificate::GeneralNameSpec::directory_name(name_der));
421 slf
422 }
423
424 fn sender_uri<'py>(slf: Bound<'py, Self>, uri: &str) -> Bound<'py, Self> {
426 slf.borrow_mut().sender = Some(synta_certificate::GeneralNameSpec::uri(uri));
427 slf
428 }
429
430 fn recipient_rfc822<'py>(slf: Bound<'py, Self>, email: &str) -> Bound<'py, Self> {
432 slf.borrow_mut().recipient = Some(synta_certificate::GeneralNameSpec::rfc822(email));
433 slf
434 }
435
436 fn recipient_dns<'py>(slf: Bound<'py, Self>, host: &str) -> Bound<'py, Self> {
438 slf.borrow_mut().recipient = Some(synta_certificate::GeneralNameSpec::dns(host));
439 slf
440 }
441
442 fn recipient_uri<'py>(slf: Bound<'py, Self>, uri: &str) -> Bound<'py, Self> {
444 slf.borrow_mut().recipient = Some(synta_certificate::GeneralNameSpec::uri(uri));
445 slf
446 }
447
448 fn recipient_directory_name<'py>(slf: Bound<'py, Self>, name_der: &[u8]) -> Bound<'py, Self> {
450 slf.borrow_mut().recipient =
451 Some(synta_certificate::GeneralNameSpec::directory_name(name_der));
452 slf
453 }
454
455 fn transaction_id<'py>(slf: Bound<'py, Self>, bytes: &[u8]) -> Bound<'py, Self> {
457 slf.borrow_mut().transaction_id = Some(bytes.to_vec());
458 slf
459 }
460
461 fn sender_nonce<'py>(slf: Bound<'py, Self>, bytes: &[u8]) -> Bound<'py, Self> {
463 slf.borrow_mut().sender_nonce = Some(bytes.to_vec());
464 slf
465 }
466
467 fn recip_nonce<'py>(slf: Bound<'py, Self>, bytes: &[u8]) -> Bound<'py, Self> {
469 slf.borrow_mut().recip_nonce = Some(bytes.to_vec());
470 slf
471 }
472
473 fn body_pkiconf<'py>(slf: Bound<'py, Self>) -> Bound<'py, Self> {
475 let mut b = slf.borrow_mut();
476 b.body_tag = 0;
477 b.body_der = None;
478 drop(b);
479 slf
480 }
481
482 fn body_ir<'py>(slf: Bound<'py, Self>, cert_req_messages_der: &[u8]) -> Bound<'py, Self> {
485 let mut b = slf.borrow_mut();
486 b.body_tag = 1;
487 b.body_der = Some(cert_req_messages_der.to_vec());
488 drop(b);
489 slf
490 }
491
492 fn body_cr<'py>(slf: Bound<'py, Self>, cert_req_messages_der: &[u8]) -> Bound<'py, Self> {
495 let mut b = slf.borrow_mut();
496 b.body_tag = 2;
497 b.body_der = Some(cert_req_messages_der.to_vec());
498 drop(b);
499 slf
500 }
501
502 fn body_kur<'py>(slf: Bound<'py, Self>, cert_req_messages_der: &[u8]) -> Bound<'py, Self> {
505 let mut b = slf.borrow_mut();
506 b.body_tag = 3;
507 b.body_der = Some(cert_req_messages_der.to_vec());
508 drop(b);
509 slf
510 }
511
512 fn body_p10cr<'py>(slf: Bound<'py, Self>, csr_der: &[u8]) -> Bound<'py, Self> {
515 let mut b = slf.borrow_mut();
516 b.body_tag = 4;
517 b.body_der = Some(csr_der.to_vec());
518 drop(b);
519 slf
520 }
521
522 fn body_genm<'py>(slf: Bound<'py, Self>, gen_msg_der: &[u8]) -> Bound<'py, Self> {
525 let mut b = slf.borrow_mut();
526 b.body_tag = 5;
527 b.body_der = Some(gen_msg_der.to_vec());
528 drop(b);
529 slf
530 }
531
532 fn build(&self, py: Python<'_>) -> PyResult<PyCMPMessage> {
537 use pyo3::exceptions::PyValueError;
538 use synta_certificate::CMPMessageBuilder;
539
540 let sender_spec = self
541 .sender
542 .clone()
543 .ok_or_else(|| PyValueError::new_err("sender is required"))?;
544 let recipient_spec = self
545 .recipient
546 .clone()
547 .ok_or_else(|| PyValueError::new_err("recipient is required"))?;
548
549 let mut rust_builder = CMPMessageBuilder::new()
550 .pvno(self.pvno)
551 .sender(sender_spec)
552 .recipient(recipient_spec);
553
554 if let Some(ref b) = self.transaction_id {
555 rust_builder = rust_builder.transaction_id(b);
556 }
557 if let Some(ref b) = self.sender_nonce {
558 rust_builder = rust_builder.sender_nonce(b);
559 }
560 if let Some(ref b) = self.recip_nonce {
561 rust_builder = rust_builder.recip_nonce(b);
562 }
563
564 match self.body_tag {
565 0 => {
566 rust_builder = rust_builder.body_pkiconf();
567 }
568 1 => {
569 rust_builder = rust_builder.body_ir(self.body_der.as_deref().unwrap_or(&[]));
570 }
571 2 => {
572 rust_builder = rust_builder.body_cr(self.body_der.as_deref().unwrap_or(&[]));
573 }
574 3 => {
575 rust_builder = rust_builder.body_kur(self.body_der.as_deref().unwrap_or(&[]));
576 }
577 4 => {
578 rust_builder = rust_builder.body_p10cr(self.body_der.as_deref().unwrap_or(&[]));
579 }
580 5 => {
581 rust_builder = rust_builder.body_genm(self.body_der.as_deref().unwrap_or(&[]));
582 }
583 _ => {}
584 }
585
586 let der = rust_builder
587 .build()
588 .map_err(|e| PyValueError::new_err(e.to_string()))?;
589
590 let py_bytes = PyBytes::new(py, &der).unbind();
591 let raw: &'static [u8] = unsafe {
592 let s = py_bytes.bind(py).as_bytes();
593 std::slice::from_raw_parts(s.as_ptr(), s.len())
594 };
595 Ok(PyCMPMessage {
596 _data: py_bytes,
597 raw,
598 inner: OnceLock::new(),
599 pvno_cache: OnceLock::new(),
600 body_type_cache: OnceLock::new(),
601 body_der_cache: OnceLock::new(),
602 sender_der_cache: OnceLock::new(),
603 recipient_der_cache: OnceLock::new(),
604 transaction_id_cache: OnceLock::new(),
605 sender_nonce_cache: OnceLock::new(),
606 recip_nonce_cache: OnceLock::new(),
607 protection_alg_oid_cache: OnceLock::new(),
608 message_time_cache: OnceLock::new(),
609 })
610 }
611
612 fn __repr__(&self) -> String {
613 use synta_certificate::GeneralNameSpec;
614 let sender = match &self.sender {
615 None => "None".to_string(),
616 Some(GeneralNameSpec::Rfc822(s)) => format!("rfc822:{s}"),
617 Some(GeneralNameSpec::Dns(s)) => format!("dns:{s}"),
618 Some(GeneralNameSpec::Uri(s)) => format!("uri:{s}"),
619 Some(GeneralNameSpec::DirectoryName(_)) => "directoryName".to_string(),
620 Some(GeneralNameSpec::IpAddress(_)) => "iPAddress".to_string(),
621 Some(GeneralNameSpec::RegisteredId(oid)) => format!("registeredID:{oid}"),
622 };
623 format!("CMPMessageBuilder(pvno={}, sender={})", self.pvno, sender)
624 }
625}
626
627pub(super) fn register_cmp_submodule(parent: &Bound<'_, PyModule>) -> PyResult<()> {
631 let py = parent.py();
632 let m = PyModule::new(py, "cmp")?;
633
634 m.add_class::<PyCMPMessage>()?;
635 m.add_class::<PyCMPMessageBuilder>()?;
636
637 m.add(
639 "ID_PASSWORD_BASED_MAC",
640 super::oid_const(py, synta_certificate::cmp_types::ID_PASSWORD_BASED_MAC),
641 )?;
642 m.add(
643 "ID_DHBASED_MAC",
644 super::oid_const(py, synta_certificate::cmp_types::ID_DHBASED_MAC),
645 )?;
646 m.add(
647 "ID_KEM_BASED_MAC",
648 super::oid_const(py, synta_certificate::cmp_types::ID_KEM_BASED_MAC),
649 )?;
650
651 m.add(
653 "ID_KP_CM_KGA",
654 super::oid_const(py, synta_certificate::cmp_types::ID_KP_CM_KGA),
655 )?;
656
657 m.add(
659 "ID_REG_CTRL_ALT_CERT_TEMPLATE",
660 super::oid_const(
661 py,
662 synta_certificate::cmp_types::ID_REG_CTRL_ALT_CERT_TEMPLATE,
663 ),
664 )?;
665 m.add(
666 "ID_REG_CTRL_ALG_ID",
667 super::oid_const(py, synta_certificate::cmp_types::ID_REG_CTRL_ALG_ID),
668 )?;
669 m.add(
670 "ID_REG_CTRL_RSA_KEY_LEN",
671 super::oid_const(py, synta_certificate::cmp_types::ID_REG_CTRL_RSA_KEY_LEN),
672 )?;
673
674 crate::install_submodule(
675 parent,
676 &m,
677 "synta.cmp",
678 Some(concat!(
679 "synta.cmp — RFC 9810 Certificate Management Protocol v3 types.\n\n",
680 "Provides CMPMessage (wrapping PKIMessage) for decoding CMP\n",
681 "envelopes. body_type names the active PKIBody arm; body_der\n",
682 "returns the raw bytes for further decoding with synta.crmf or\n",
683 "a synta.Decoder. Also exposes MAC algorithm and key-purpose\n",
684 "OID constants.",
685 )),
686 )
687}