1use std::sync::OnceLock;
7
8use pyo3::prelude::*;
9use pyo3::types::{PyBytes, PyString};
10
11use synta::traits::Encode;
12use synta::{Decoder, Encoding};
13
14use crate::error::SyntaErr;
15use crate::types::PyObjectIdentifier;
16
17fn encode_to_der<T: Encode>(v: &T) -> Vec<u8> {
21 let mut enc = synta::Encoder::new(Encoding::Der);
22 if v.encode(&mut enc).is_err() {
23 return Vec::new();
24 }
25 enc.finish().unwrap_or_default()
26}
27
28#[pyclass(frozen, name = "AttributeCertificate")]
44pub struct PyAttributeCertificate {
45 _data: Py<PyBytes>,
46 raw: &'static [u8],
47 inner: OnceLock<Box<synta_certificate::attribute_cert_types::AttributeCertificate<'static>>>,
48 serial_number_cache: OnceLock<Py<PyBytes>>,
50 not_before_cache: OnceLock<Py<PyString>>,
51 not_after_cache: OnceLock<Py<PyString>>,
52 signature_algorithm_oid_cache: OnceLock<Py<PyObjectIdentifier>>,
53 signature_cache: OnceLock<Py<PyBytes>>,
54 holder_der_cache: OnceLock<Py<PyBytes>>,
55 issuer_der_cache: OnceLock<Py<PyBytes>>,
56 attributes_der_cache: OnceLock<Py<PyBytes>>,
57}
58
59impl PyAttributeCertificate {
60 fn ac(
61 &self,
62 ) -> PyResult<&synta_certificate::attribute_cert_types::AttributeCertificate<'static>> {
63 if let Some(v) = self.inner.get() {
64 return Ok(v.as_ref());
65 }
66 let mut dec = Decoder::new(self.raw, Encoding::Der);
67 let decoded = dec
68 .decode::<synta_certificate::attribute_cert_types::AttributeCertificate<'_>>()
69 .map_err(SyntaErr)?;
70 let decoded: synta_certificate::attribute_cert_types::AttributeCertificate<'static> =
72 unsafe { std::mem::transmute(decoded) };
73 let _ = self.inner.set(Box::new(decoded));
74 Ok(self.inner.get().unwrap().as_ref())
75 }
76}
77
78#[pymethods]
79impl PyAttributeCertificate {
80 #[staticmethod]
85 fn from_der(py: Python<'_>, data: Bound<'_, PyBytes>) -> PyResult<Self> {
86 let py_bytes = data.unbind();
87 {
88 let raw = py_bytes.as_bytes(py);
89 Decoder::new(raw, Encoding::Der)
90 .decode::<synta_certificate::attribute_cert_types::AttributeCertificate<'_>>()
91 .map_err(SyntaErr)?;
92 }
93 let raw: &'static [u8] = unsafe { std::mem::transmute(py_bytes.as_bytes(py)) };
94 Ok(Self {
95 _data: py_bytes,
96 raw,
97 inner: OnceLock::new(),
98 serial_number_cache: OnceLock::new(),
99 not_before_cache: OnceLock::new(),
100 not_after_cache: OnceLock::new(),
101 signature_algorithm_oid_cache: OnceLock::new(),
102 signature_cache: OnceLock::new(),
103 holder_der_cache: OnceLock::new(),
104 issuer_der_cache: OnceLock::new(),
105 attributes_der_cache: OnceLock::new(),
106 })
107 }
108
109 fn to_der<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
111 Ok(PyBytes::new(py, &self.ac()?.to_der().map_err(SyntaErr)?))
112 }
113
114 #[staticmethod]
124 fn from_pem(py: Python<'_>, data: &[u8]) -> PyResult<Self> {
125 let blocks = synta_certificate::pem_blocks(data);
126 let der = match blocks.as_slice() {
127 [] => {
128 return Err(pyo3::exceptions::PyValueError::new_err(
129 "no PEM block found in input",
130 ))
131 }
132 [(_, first), ..] => first,
133 };
134 let py_bytes = pyo3::types::PyBytes::new(py, der).unbind();
135 {
136 let raw = py_bytes.as_bytes(py);
137 synta::Decoder::new(raw, Encoding::Der)
138 .decode::<synta_certificate::attribute_cert_types::AttributeCertificate<'_>>()
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 serial_number_cache: OnceLock::new(),
147 not_before_cache: OnceLock::new(),
148 not_after_cache: OnceLock::new(),
149 signature_algorithm_oid_cache: OnceLock::new(),
150 signature_cache: OnceLock::new(),
151 holder_der_cache: OnceLock::new(),
152 issuer_der_cache: OnceLock::new(),
153 attributes_der_cache: OnceLock::new(),
154 })
155 }
156
157 fn to_pem<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
165 let pem = synta_certificate::der_to_pem("ATTRIBUTE CERTIFICATE", self.raw);
166 PyBytes::new(py, &pem)
167 }
168
169 #[getter]
171 fn version(&self) -> PyResult<i64> {
172 Ok(self.ac()?.acinfo.version.as_i64().unwrap_or(1))
173 }
174
175 #[getter]
177 fn serial_number<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
178 if let Some(c) = self.serial_number_cache.get() {
179 return Ok(c.clone_ref(py).into_bound(py));
180 }
181 let b = PyBytes::new(py, self.ac()?.acinfo.serial_number.as_bytes());
182 let _ = self.serial_number_cache.set(b.as_unbound().clone_ref(py));
183 Ok(b)
184 }
185
186 #[getter]
188 fn not_before<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyString>> {
189 if let Some(c) = self.not_before_cache.get() {
190 return Ok(c.clone_ref(py).into_bound(py));
191 }
192 let s = self
193 .ac()?
194 .acinfo
195 .attr_cert_validity_period
196 .not_before_time
197 .to_string();
198 let ps = PyString::new(py, &s);
199 let _ = self.not_before_cache.set(ps.as_unbound().clone_ref(py));
200 Ok(ps)
201 }
202
203 #[getter]
205 fn not_after<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyString>> {
206 if let Some(c) = self.not_after_cache.get() {
207 return Ok(c.clone_ref(py).into_bound(py));
208 }
209 let s = self
210 .ac()?
211 .acinfo
212 .attr_cert_validity_period
213 .not_after_time
214 .to_string();
215 let ps = PyString::new(py, &s);
216 let _ = self.not_after_cache.set(ps.as_unbound().clone_ref(py));
217 Ok(ps)
218 }
219
220 #[getter]
222 fn signature_algorithm_oid(&self, py: Python<'_>) -> PyResult<Py<PyObjectIdentifier>> {
223 if let Some(c) = self.signature_algorithm_oid_cache.get() {
224 return Ok(c.clone_ref(py));
225 }
226 let oid = self.ac()?.acinfo.signature.algorithm.clone();
227 let obj = Py::new(py, PyObjectIdentifier::from_oid(oid))?;
228 let _ = self.signature_algorithm_oid_cache.set(obj.clone_ref(py));
229 Ok(obj)
230 }
231
232 #[getter]
234 fn signature<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
235 if let Some(c) = self.signature_cache.get() {
236 return Ok(c.clone_ref(py).into_bound(py));
237 }
238 let b = PyBytes::new(py, self.ac()?.signature.as_bytes());
239 let _ = self.signature_cache.set(b.as_unbound().clone_ref(py));
240 Ok(b)
241 }
242
243 #[getter]
245 fn holder_der<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
246 if let Some(c) = self.holder_der_cache.get() {
247 return Ok(c.clone_ref(py).into_bound(py));
248 }
249 let der = encode_to_der(&self.ac()?.acinfo.holder);
250 let b = PyBytes::new(py, &der);
251 let _ = self.holder_der_cache.set(b.as_unbound().clone_ref(py));
252 Ok(b)
253 }
254
255 #[getter]
257 fn issuer_der<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
258 if let Some(c) = self.issuer_der_cache.get() {
259 return Ok(c.clone_ref(py).into_bound(py));
260 }
261 let der = encode_to_der(&self.ac()?.acinfo.issuer);
262 let b = PyBytes::new(py, &der);
263 let _ = self.issuer_der_cache.set(b.as_unbound().clone_ref(py));
264 Ok(b)
265 }
266
267 #[getter]
273 fn attributes_der<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
274 if let Some(c) = self.attributes_der_cache.get() {
275 return Ok(c.clone_ref(py).into_bound(py));
276 }
277 let der = encode_to_der(&self.ac()?.acinfo.attributes);
278 let b = PyBytes::new(py, &der);
279 let _ = self.attributes_der_cache.set(b.as_unbound().clone_ref(py));
280 Ok(b)
281 }
282
283 fn verify_issued_by(&self, issuer: &super::cert::PyCertificate) -> PyResult<()> {
300 use synta_certificate::{default_signature_verifier, SignatureVerifier};
301
302 let ac = self.ac()?;
303 let issuer_cert = issuer.cert()?;
304
305 let tbs_der = encode_to_der(&ac.acinfo);
307 if tbs_der.is_empty() {
308 return Err(pyo3::exceptions::PyValueError::new_err(
309 "failed to encode AttributeCertificateInfo",
310 ));
311 }
312
313 let sig_alg_der = encode_to_der(&ac.signature_algorithm);
315 if sig_alg_der.is_empty() {
316 return Err(pyo3::exceptions::PyValueError::new_err(
317 "failed to encode AC signatureAlgorithm",
318 ));
319 }
320
321 let spki_der = encode_to_der(&issuer_cert.tbs_certificate.subject_public_key_info);
323 if spki_der.is_empty() {
324 return Err(pyo3::exceptions::PyValueError::new_err(
325 "failed to encode issuer SubjectPublicKeyInfo",
326 ));
327 }
328
329 default_signature_verifier()
331 .verify_certificate_signature(
332 &tbs_der,
333 &sig_alg_der,
334 ac.signature.as_bytes(),
335 &spki_der,
336 )
337 .map_err(|e| {
338 pyo3::exceptions::PyValueError::new_err(format!("AC signature invalid: {e}"))
339 })
340 }
341
342 fn __repr__(&self) -> PyResult<String> {
343 let ac = self.ac()?;
344 Ok(format!(
345 "AttributeCertificate(serial={})",
346 ac.acinfo
347 .serial_number
348 .as_bytes()
349 .iter()
350 .map(|b| format!("{b:02x}"))
351 .collect::<String>(),
352 ))
353 }
354}
355
356pub(super) fn register_ac_submodule(parent: &Bound<'_, PyModule>) -> PyResult<()> {
360 let py = parent.py();
361 let m = PyModule::new(py, "ac")?;
362
363 m.add_class::<PyAttributeCertificate>()?;
364 m.add_class::<PyAttributeCertificateBuilder>()?;
365
366 m.add(
368 "ID_PE_AC_AUDIT_IDENTITY",
369 super::oid_const(
370 py,
371 synta_certificate::attribute_cert_types::ID_PE_AC_AUDIT_IDENTITY,
372 ),
373 )?;
374 m.add(
375 "ID_PE_AA_CONTROLS",
376 super::oid_const(
377 py,
378 synta_certificate::attribute_cert_types::ID_PE_AA_CONTROLS,
379 ),
380 )?;
381 m.add(
382 "ID_PE_AC_PROXYING",
383 super::oid_const(
384 py,
385 synta_certificate::attribute_cert_types::ID_PE_AC_PROXYING,
386 ),
387 )?;
388 m.add(
389 "ID_CE_TARGET_INFORMATION",
390 super::oid_const(
391 py,
392 synta_certificate::attribute_cert_types::ID_CE_TARGET_INFORMATION,
393 ),
394 )?;
395 m.add(
396 "ID_ACA_AUTHENTICATION_INFO",
397 super::oid_const(
398 py,
399 synta_certificate::attribute_cert_types::ID_ACA_AUTHENTICATION_INFO,
400 ),
401 )?;
402 m.add(
403 "ID_ACA_ACCESS_IDENTITY",
404 super::oid_const(
405 py,
406 synta_certificate::attribute_cert_types::ID_ACA_ACCESS_IDENTITY,
407 ),
408 )?;
409 m.add(
410 "ID_ACA_CHARGING_IDENTITY",
411 super::oid_const(
412 py,
413 synta_certificate::attribute_cert_types::ID_ACA_CHARGING_IDENTITY,
414 ),
415 )?;
416 m.add(
417 "ID_ACA_GROUP",
418 super::oid_const(py, synta_certificate::attribute_cert_types::ID_ACA_GROUP),
419 )?;
420 m.add(
421 "ID_ACA_ENC_ATTRS",
422 super::oid_const(
423 py,
424 synta_certificate::attribute_cert_types::ID_ACA_ENC_ATTRS,
425 ),
426 )?;
427 m.add(
428 "ID_AT_ROLE",
429 super::oid_const(py, synta_certificate::attribute_cert_types::ID_AT_ROLE),
430 )?;
431 m.add(
432 "ID_AT_CLEARANCE",
433 super::oid_const(py, synta_certificate::attribute_cert_types::ID_AT_CLEARANCE),
434 )?;
435
436 crate::install_submodule(
437 parent,
438 &m,
439 "synta.ac",
440 Some(concat!(
441 "synta.ac — RFC 5755 Attribute Certificate v2 types.\n\n",
442 "Provides AttributeCertificate for decoding X.509 Attribute\n",
443 "Certificates that bind roles, clearances, or service-auth\n",
444 "attributes to a holder's PKC, along with OID constants for\n",
445 "RFC 5755 extensions and attribute types.",
446 )),
447 )
448}
449
450#[pyclass(name = "AttributeCertificateBuilder")]
472pub struct PyAttributeCertificateBuilder {
473 inner: synta_certificate::AttributeCertificateBuilder,
474}
475
476#[pymethods]
477impl PyAttributeCertificateBuilder {
478 #[new]
480 fn new() -> Self {
481 Self {
482 inner: synta_certificate::AttributeCertificateBuilder::new(),
483 }
484 }
485
486 fn serial_number<'py>(slf: Bound<'py, Self>, n: i64) -> Bound<'py, Self> {
492 let old = std::mem::replace(
493 &mut slf.borrow_mut().inner,
494 synta_certificate::AttributeCertificateBuilder::new(),
495 );
496 slf.borrow_mut().inner = old.serial_number(n);
497 slf
498 }
499
500 fn not_before<'py>(slf: Bound<'py, Self>, s: &str) -> Bound<'py, Self> {
508 let old = std::mem::replace(
509 &mut slf.borrow_mut().inner,
510 synta_certificate::AttributeCertificateBuilder::new(),
511 );
512 slf.borrow_mut().inner = old.not_before(s);
513 slf
514 }
515
516 fn not_after<'py>(slf: Bound<'py, Self>, s: &str) -> Bound<'py, Self> {
524 let old = std::mem::replace(
525 &mut slf.borrow_mut().inner,
526 synta_certificate::AttributeCertificateBuilder::new(),
527 );
528 slf.borrow_mut().inner = old.not_after(s);
529 slf
530 }
531
532 fn issuer_rfc822<'py>(slf: Bound<'py, Self>, email: &str) -> Bound<'py, Self> {
538 let old = std::mem::replace(
539 &mut slf.borrow_mut().inner,
540 synta_certificate::AttributeCertificateBuilder::new(),
541 );
542 slf.borrow_mut().inner = old.issuer_rfc822(email);
543 slf
544 }
545
546 fn issuer_dns<'py>(slf: Bound<'py, Self>, name: &str) -> Bound<'py, Self> {
552 let old = std::mem::replace(
553 &mut slf.borrow_mut().inner,
554 synta_certificate::AttributeCertificateBuilder::new(),
555 );
556 slf.borrow_mut().inner = old.issuer_dns(name);
557 slf
558 }
559
560 fn holder_entity_name_rfc822<'py>(slf: Bound<'py, Self>, email: &str) -> Bound<'py, Self> {
566 let old = std::mem::replace(
567 &mut slf.borrow_mut().inner,
568 synta_certificate::AttributeCertificateBuilder::new(),
569 );
570 slf.borrow_mut().inner = old.holder_entity_name_rfc822(email);
571 slf
572 }
573
574 fn build<'py>(&mut self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
582 let inner = std::mem::replace(
583 &mut self.inner,
584 synta_certificate::AttributeCertificateBuilder::new(),
585 );
586 let der = inner
587 .build()
588 .map_err(pyo3::exceptions::PyValueError::new_err)?;
589 Ok(PyBytes::new(py, &der))
590 }
591
592 fn __repr__(&self) -> String {
593 "AttributeCertificateBuilder()".to_string()
594 }
595}