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 let mut enc = synta::Encoder::new(Encoding::Der);
112 self.ac()?.encode(&mut enc).map_err(SyntaErr)?;
113 Ok(PyBytes::new(py, &enc.finish().map_err(SyntaErr)?))
114 }
115
116 #[getter]
118 fn version(&self) -> PyResult<i64> {
119 Ok(self.ac()?.acinfo.version.as_i64().unwrap_or(1))
120 }
121
122 #[getter]
124 fn serial_number<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
125 if let Some(c) = self.serial_number_cache.get() {
126 return Ok(c.clone_ref(py).into_bound(py));
127 }
128 let b = PyBytes::new(py, self.ac()?.acinfo.serial_number.as_bytes());
129 let _ = self.serial_number_cache.set(b.as_unbound().clone_ref(py));
130 Ok(b)
131 }
132
133 #[getter]
135 fn not_before<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyString>> {
136 if let Some(c) = self.not_before_cache.get() {
137 return Ok(c.clone_ref(py).into_bound(py));
138 }
139 let s = self
140 .ac()?
141 .acinfo
142 .attr_cert_validity_period
143 .not_before_time
144 .to_string();
145 let ps = PyString::new(py, &s);
146 let _ = self.not_before_cache.set(ps.as_unbound().clone_ref(py));
147 Ok(ps)
148 }
149
150 #[getter]
152 fn not_after<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyString>> {
153 if let Some(c) = self.not_after_cache.get() {
154 return Ok(c.clone_ref(py).into_bound(py));
155 }
156 let s = self
157 .ac()?
158 .acinfo
159 .attr_cert_validity_period
160 .not_after_time
161 .to_string();
162 let ps = PyString::new(py, &s);
163 let _ = self.not_after_cache.set(ps.as_unbound().clone_ref(py));
164 Ok(ps)
165 }
166
167 #[getter]
169 fn signature_algorithm_oid(&self, py: Python<'_>) -> PyResult<Py<PyObjectIdentifier>> {
170 if let Some(c) = self.signature_algorithm_oid_cache.get() {
171 return Ok(c.clone_ref(py));
172 }
173 let oid = self.ac()?.acinfo.signature.algorithm.clone();
174 let obj = Py::new(py, PyObjectIdentifier::from_oid(oid))?;
175 let _ = self.signature_algorithm_oid_cache.set(obj.clone_ref(py));
176 Ok(obj)
177 }
178
179 #[getter]
181 fn signature<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
182 if let Some(c) = self.signature_cache.get() {
183 return Ok(c.clone_ref(py).into_bound(py));
184 }
185 let b = PyBytes::new(py, self.ac()?.signature.as_bytes());
186 let _ = self.signature_cache.set(b.as_unbound().clone_ref(py));
187 Ok(b)
188 }
189
190 #[getter]
192 fn holder_der<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
193 if let Some(c) = self.holder_der_cache.get() {
194 return Ok(c.clone_ref(py).into_bound(py));
195 }
196 let der = encode_to_der(&self.ac()?.acinfo.holder);
197 let b = PyBytes::new(py, &der);
198 let _ = self.holder_der_cache.set(b.as_unbound().clone_ref(py));
199 Ok(b)
200 }
201
202 #[getter]
204 fn issuer_der<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
205 if let Some(c) = self.issuer_der_cache.get() {
206 return Ok(c.clone_ref(py).into_bound(py));
207 }
208 let der = encode_to_der(&self.ac()?.acinfo.issuer);
209 let b = PyBytes::new(py, &der);
210 let _ = self.issuer_der_cache.set(b.as_unbound().clone_ref(py));
211 Ok(b)
212 }
213
214 #[getter]
220 fn attributes_der<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
221 if let Some(c) = self.attributes_der_cache.get() {
222 return Ok(c.clone_ref(py).into_bound(py));
223 }
224 let der = encode_to_der(&self.ac()?.acinfo.attributes);
225 let b = PyBytes::new(py, &der);
226 let _ = self.attributes_der_cache.set(b.as_unbound().clone_ref(py));
227 Ok(b)
228 }
229
230 fn __repr__(&self) -> PyResult<String> {
231 let ac = self.ac()?;
232 Ok(format!(
233 "AttributeCertificate(serial={})",
234 ac.acinfo
235 .serial_number
236 .as_bytes()
237 .iter()
238 .map(|b| format!("{b:02x}"))
239 .collect::<String>(),
240 ))
241 }
242}
243
244pub(super) fn register_ac_submodule(parent: &Bound<'_, PyModule>) -> PyResult<()> {
248 let py = parent.py();
249 let m = PyModule::new(py, "ac")?;
250
251 m.add_class::<PyAttributeCertificate>()?;
252
253 m.add(
255 "ID_PE_AC_AUDIT_IDENTITY",
256 super::oid_const(
257 py,
258 synta_certificate::attribute_cert_types::ID_PE_AC_AUDIT_IDENTITY,
259 ),
260 )?;
261 m.add(
262 "ID_PE_AA_CONTROLS",
263 super::oid_const(
264 py,
265 synta_certificate::attribute_cert_types::ID_PE_AA_CONTROLS,
266 ),
267 )?;
268 m.add(
269 "ID_PE_AC_PROXYING",
270 super::oid_const(
271 py,
272 synta_certificate::attribute_cert_types::ID_PE_AC_PROXYING,
273 ),
274 )?;
275 m.add(
276 "ID_CE_TARGET_INFORMATION",
277 super::oid_const(
278 py,
279 synta_certificate::attribute_cert_types::ID_CE_TARGET_INFORMATION,
280 ),
281 )?;
282 m.add(
283 "ID_ACA_AUTHENTICATION_INFO",
284 super::oid_const(
285 py,
286 synta_certificate::attribute_cert_types::ID_ACA_AUTHENTICATION_INFO,
287 ),
288 )?;
289 m.add(
290 "ID_ACA_ACCESS_IDENTITY",
291 super::oid_const(
292 py,
293 synta_certificate::attribute_cert_types::ID_ACA_ACCESS_IDENTITY,
294 ),
295 )?;
296 m.add(
297 "ID_ACA_CHARGING_IDENTITY",
298 super::oid_const(
299 py,
300 synta_certificate::attribute_cert_types::ID_ACA_CHARGING_IDENTITY,
301 ),
302 )?;
303 m.add(
304 "ID_ACA_GROUP",
305 super::oid_const(py, synta_certificate::attribute_cert_types::ID_ACA_GROUP),
306 )?;
307 m.add(
308 "ID_ACA_ENC_ATTRS",
309 super::oid_const(
310 py,
311 synta_certificate::attribute_cert_types::ID_ACA_ENC_ATTRS,
312 ),
313 )?;
314 m.add(
315 "ID_AT_ROLE",
316 super::oid_const(py, synta_certificate::attribute_cert_types::ID_AT_ROLE),
317 )?;
318 m.add(
319 "ID_AT_CLEARANCE",
320 super::oid_const(py, synta_certificate::attribute_cert_types::ID_AT_CLEARANCE),
321 )?;
322
323 crate::install_submodule(
324 parent,
325 &m,
326 "synta.ac",
327 Some(concat!(
328 "synta.ac — RFC 5755 Attribute Certificate v2 types.\n\n",
329 "Provides AttributeCertificate for decoding X.509 Attribute\n",
330 "Certificates that bind roles, clearances, or service-auth\n",
331 "attributes to a holder's PKC, along with OID constants for\n",
332 "RFC 5755 extensions and attribute types.",
333 )),
334 )
335}