1use pyo3::prelude::*;
9use pyo3::types::{PyBytes, PyList, PyModule};
10
11use crate::error::SyntaErr;
12use crate::types::PyObjectIdentifier;
13
14#[pyclass(frozen, name = "OtherName", module = "synta.general_name")]
21pub struct PyOtherName {
22 pub type_id: synta::ObjectIdentifier,
23 pub value: Vec<u8>,
25}
26
27#[pymethods]
28impl PyOtherName {
29 #[getter]
31 fn type_id<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyObjectIdentifier>> {
32 Py::new(py, PyObjectIdentifier::from_oid(self.type_id.clone())).map(|p| p.into_bound(py))
33 }
34
35 #[getter]
37 fn value<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
38 PyBytes::new(py, &self.value)
39 }
40
41 fn __repr__(&self) -> String {
42 format!("OtherName(type_id={})", self.type_id)
43 }
44}
45
46#[pyclass(frozen, name = "RFC822Name", module = "synta.general_name")]
50pub struct PyRfc822Name {
51 pub value: String,
52}
53
54#[pymethods]
55impl PyRfc822Name {
56 #[getter]
58 fn value(&self) -> &str {
59 &self.value
60 }
61
62 fn __repr__(&self) -> String {
63 format!("RFC822Name(value={:?})", self.value)
64 }
65}
66
67#[pyclass(frozen, name = "DNSName", module = "synta.general_name")]
71pub struct PyDnsName {
72 pub value: String,
73}
74
75#[pymethods]
76impl PyDnsName {
77 #[getter]
79 fn value(&self) -> &str {
80 &self.value
81 }
82
83 fn __repr__(&self) -> String {
84 format!("DNSName(value={:?})", self.value)
85 }
86}
87
88#[pyclass(frozen, name = "X400Address", module = "synta.general_name")]
92pub struct PyX400Address {
93 pub raw_der: Vec<u8>,
94}
95
96#[pymethods]
97impl PyX400Address {
98 #[getter]
100 fn raw_der<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
101 PyBytes::new(py, &self.raw_der)
102 }
103
104 fn __repr__(&self) -> String {
105 format!("X400Address(raw_der=<{} bytes>)", self.raw_der.len())
106 }
107}
108
109#[pyclass(frozen, name = "DirectoryName", module = "synta.general_name")]
117pub struct PyDirectoryName {
118 pub name_der: Vec<u8>,
120}
121
122#[pymethods]
123impl PyDirectoryName {
124 #[getter]
126 fn name_der<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
127 PyBytes::new(py, &self.name_der)
128 }
129
130 fn __repr__(&self) -> String {
131 let dn = synta_certificate::name::format_dn(&self.name_der);
132 format!("DirectoryName(name={dn:?})")
133 }
134}
135
136#[pyclass(frozen, name = "EDIPartyName", module = "synta.general_name")]
140pub struct PyEdiPartyName {
141 pub raw_der: Vec<u8>,
142}
143
144#[pymethods]
145impl PyEdiPartyName {
146 #[getter]
148 fn raw_der<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
149 PyBytes::new(py, &self.raw_der)
150 }
151
152 fn __repr__(&self) -> String {
153 format!("EDIPartyName(raw_der=<{} bytes>)", self.raw_der.len())
154 }
155}
156
157#[pyclass(
161 frozen,
162 name = "UniformResourceIdentifier",
163 module = "synta.general_name"
164)]
165pub struct PyUri {
166 pub value: String,
167}
168
169#[pymethods]
170impl PyUri {
171 #[getter]
173 fn value(&self) -> &str {
174 &self.value
175 }
176
177 fn __repr__(&self) -> String {
178 format!("UniformResourceIdentifier(value={:?})", self.value)
179 }
180}
181
182#[pyclass(frozen, name = "IPAddress", module = "synta.general_name")]
188pub struct PyIPAddress {
189 pub address: Vec<u8>,
190}
191
192#[pymethods]
193impl PyIPAddress {
194 #[getter]
196 fn address<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
197 PyBytes::new(py, &self.address)
198 }
199
200 #[getter]
209 fn value<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
210 let ipaddress = PyModule::import(py, "ipaddress")?;
211 let cls = match self.address.len() {
212 4 => ipaddress.getattr("IPv4Address")?,
213 16 => ipaddress.getattr("IPv6Address")?,
214 n => {
215 return Err(pyo3::exceptions::PyValueError::new_err(format!(
216 "invalid IP address length: {n} (expected 4 or 16)"
217 )))
218 }
219 };
220 cls.call1((PyBytes::new(py, &self.address),))
221 }
222
223 fn __repr__(&self) -> String {
224 match self.address.len() {
225 4 => format!(
226 "IPAddress(address={}.{}.{}.{})",
227 self.address[0], self.address[1], self.address[2], self.address[3]
228 ),
229 16 => {
230 let parts: Vec<String> = self
231 .address
232 .chunks(2)
233 .map(|c| format!("{:02x}{:02x}", c[0], c[1]))
234 .collect();
235 format!("IPAddress(address={})", parts.join(":"))
236 }
237 n => format!("IPAddress(address=<{n} bytes>)"),
238 }
239 }
240}
241
242#[pyclass(frozen, name = "RegisteredID", module = "synta.general_name")]
246pub struct PyRegisteredId {
247 pub oid: synta::ObjectIdentifier,
248}
249
250#[pymethods]
251impl PyRegisteredId {
252 #[getter]
254 fn oid<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyObjectIdentifier>> {
255 Py::new(py, PyObjectIdentifier::from_oid(self.oid.clone())).map(|p| p.into_bound(py))
256 }
257
258 fn __repr__(&self) -> String {
259 format!("RegisteredID(oid={})", self.oid)
260 }
261}
262
263fn encode_element(elem: &synta::Element<'_>) -> Option<Vec<u8>> {
269 use synta::traits::Encode;
270 let mut enc = synta::Encoder::new(synta::Encoding::Der);
271 elem.encode(&mut enc).ok()?;
272 enc.finish().ok()
273}
274
275pub fn py_from_general_name<'py>(
280 py: Python<'py>,
281 gn: &synta_certificate::GeneralName<'_>,
282) -> PyResult<Bound<'py, PyAny>> {
283 use synta_certificate::GeneralName;
284
285 match gn {
286 GeneralName::OtherName(on) => {
287 let value = encode_element(&on.value).ok_or_else(|| {
288 pyo3::exceptions::PyValueError::new_err(
289 "failed to encode OtherName value element to DER",
290 )
291 })?;
292 let obj = Py::new(
293 py,
294 PyOtherName {
295 type_id: on.type_id.clone(),
296 value,
297 },
298 )?;
299 Ok(obj.into_bound(py).into_any())
300 }
301 GeneralName::Rfc822Name(ia5) => {
302 let obj = Py::new(
303 py,
304 PyRfc822Name {
305 value: ia5.as_str().to_string(),
306 },
307 )?;
308 Ok(obj.into_bound(py).into_any())
309 }
310 GeneralName::DNSName(ia5) => {
311 let obj = Py::new(
312 py,
313 PyDnsName {
314 value: ia5.as_str().to_string(),
315 },
316 )?;
317 Ok(obj.into_bound(py).into_any())
318 }
319 GeneralName::X400Address(elem) => {
320 let raw_der = encode_element(elem).ok_or_else(|| {
321 pyo3::exceptions::PyValueError::new_err(
322 "failed to encode X400Address element to DER",
323 )
324 })?;
325 let obj = Py::new(py, PyX400Address { raw_der })?;
326 Ok(obj.into_bound(py).into_any())
327 }
328 GeneralName::DirectoryName(name) => {
329 let name_der = name.to_der().map_err(SyntaErr)?;
330 let obj = Py::new(py, PyDirectoryName { name_der })?;
331 Ok(obj.into_bound(py).into_any())
332 }
333 GeneralName::EdiPartyName(epn) => {
334 let raw_der = epn.to_der().map_err(SyntaErr)?;
335 let obj = Py::new(py, PyEdiPartyName { raw_der })?;
336 Ok(obj.into_bound(py).into_any())
337 }
338 GeneralName::UniformResourceIdentifier(ia5) => {
339 let obj = Py::new(
340 py,
341 PyUri {
342 value: ia5.as_str().to_string(),
343 },
344 )?;
345 Ok(obj.into_bound(py).into_any())
346 }
347 GeneralName::IPAddress(oct) => {
348 let obj = Py::new(
349 py,
350 PyIPAddress {
351 address: oct.as_bytes().to_vec(),
352 },
353 )?;
354 Ok(obj.into_bound(py).into_any())
355 }
356 GeneralName::RegisteredID(oid) => {
357 let obj = Py::new(py, PyRegisteredId { oid: oid.clone() })?;
358 Ok(obj.into_bound(py).into_any())
359 }
360 }
361}
362
363pub fn decode_general_names_to_py<'py>(
372 py: Python<'py>,
373 raw: &[u8],
374) -> PyResult<Bound<'py, PyList>> {
375 use synta_certificate::GeneralName;
376
377 let mut decoder = synta::Decoder::new(raw, synta::Encoding::Der);
378 let gns: Vec<GeneralName<'_>> = decoder.decode().map_err(|e| {
379 pyo3::exceptions::PyValueError::new_err(format!(
380 "failed to decode SEQUENCE OF GeneralName: {e}"
381 ))
382 })?;
383
384 let list = PyList::empty(py);
385 for gn in &gns {
386 list.append(py_from_general_name(py, gn)?)?;
387 }
388 Ok(list)
389}