1use pyo3::exceptions::PyOverflowError;
27use pyo3::prelude::*;
28use pyo3::types::{PyBytes, PyList};
29
30use crate::error::SyntaErr;
31use crate::types::PyObjectIdentifier;
32use synta::traits::Encode;
33use synta::{Decoder, Encoding, ObjectIdentifier};
34
35fn encode_element_to_vec(elem: &synta::Element<'_>) -> PyResult<Vec<u8>> {
39 let mut encoder = synta::Encoder::new(Encoding::Der);
40 elem.encode(&mut encoder).map_err(SyntaErr)?;
41 Ok(encoder.finish().map_err(SyntaErr)?)
42}
43
44#[pyclass(frozen, name = "EncryptionKey")]
55pub struct PyEncryptionKey {
56 keytype: i64,
57 keyvalue: Vec<u8>,
58}
59
60#[pymethods]
61impl PyEncryptionKey {
62 #[staticmethod]
64 fn from_der(data: &[u8]) -> PyResult<Self> {
65 let mut dec = Decoder::new(data, Encoding::Der);
66 let parsed: synta_krb5::pkinit::EncryptionKey<'_> = dec.decode().map_err(SyntaErr)?;
67 Ok(Self {
68 keytype: parsed
69 .keytype
70 .as_i64()
71 .map_err(|_| PyOverflowError::new_err("keytype out of i64 range"))?,
72 keyvalue: parsed.keyvalue.as_bytes().to_vec(),
73 })
74 }
75
76 #[getter]
78 fn keytype(&self) -> i64 {
79 self.keytype
80 }
81
82 #[getter]
84 fn keyvalue<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
85 PyBytes::new(py, &self.keyvalue)
86 }
87
88 fn __repr__(&self) -> String {
89 format!(
90 "EncryptionKey(keytype={}, keyvalue=<{} bytes>)",
91 self.keytype,
92 self.keyvalue.len()
93 )
94 }
95}
96
97#[pyclass(frozen, name = "Checksum")]
106pub struct PyChecksum {
107 cksumtype: i64,
108 checksum: Vec<u8>,
109}
110
111#[pymethods]
112impl PyChecksum {
113 #[staticmethod]
115 fn from_der(data: &[u8]) -> PyResult<Self> {
116 let mut dec = Decoder::new(data, Encoding::Der);
117 let parsed: synta_krb5::pkinit::Checksum<'_> = dec.decode().map_err(SyntaErr)?;
118 Ok(Self {
119 cksumtype: parsed
120 .cksumtype
121 .as_i64()
122 .map_err(|_| PyOverflowError::new_err("cksumtype out of i64 range"))?,
123 checksum: parsed.checksum.as_bytes().to_vec(),
124 })
125 }
126
127 #[getter]
129 fn cksumtype(&self) -> i64 {
130 self.cksumtype
131 }
132
133 #[getter]
135 fn checksum<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
136 PyBytes::new(py, &self.checksum)
137 }
138
139 fn __repr__(&self) -> String {
140 format!(
141 "Checksum(cksumtype={}, checksum=<{} bytes>)",
142 self.cksumtype,
143 self.checksum.len()
144 )
145 }
146}
147
148#[pyclass(frozen, name = "KDFAlgorithmId")]
157pub struct PyKDFAlgorithmId {
158 kdf_id: ObjectIdentifier,
159}
160
161#[pymethods]
162impl PyKDFAlgorithmId {
163 #[staticmethod]
165 fn from_der(data: &[u8]) -> PyResult<Self> {
166 let mut dec = Decoder::new(data, Encoding::Der);
167 let parsed: synta_krb5::pkinit::KDFAlgorithmId = dec.decode().map_err(SyntaErr)?;
168 Ok(Self {
169 kdf_id: parsed.kdf_id,
170 })
171 }
172
173 #[getter]
175 fn kdf_id(&self, py: Python<'_>) -> PyResult<Py<PyObjectIdentifier>> {
176 Py::new(py, PyObjectIdentifier::from_oid(self.kdf_id.clone()))
177 }
178
179 fn __repr__(&self) -> String {
180 format!("KDFAlgorithmId(kdf_id='{}')", self.kdf_id)
181 }
182}
183
184#[pyclass(frozen, name = "IssuerAndSerialNumber")]
193pub struct PyIssuerAndSerialNumber {
194 issuer: Vec<u8>,
195 serial_number_bytes: Vec<u8>,
196}
197
198#[pymethods]
199impl PyIssuerAndSerialNumber {
200 #[staticmethod]
202 fn from_der(data: &[u8]) -> PyResult<Self> {
203 let mut dec = Decoder::new(data, Encoding::Der);
204 let parsed: synta_krb5::pkinit::IssuerAndSerialNumber<'_> =
205 dec.decode().map_err(SyntaErr)?;
206 Ok(Self {
207 issuer: parsed.issuer.as_bytes().to_vec(),
208 serial_number_bytes: parsed.serial_number.as_bytes().to_vec(),
209 })
210 }
211
212 #[getter]
214 fn issuer<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
215 PyBytes::new(py, &self.issuer)
216 }
217
218 #[getter]
220 fn serial_number<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
221 let bytes_obj = PyBytes::new(py, &self.serial_number_bytes);
222 let kwargs = pyo3::types::PyDict::new(py);
223 kwargs.set_item(pyo3::intern!(py, "signed"), true)?;
224 py.get_type::<pyo3::types::PyInt>().call_method(
225 "from_bytes",
226 (bytes_obj, "big"),
227 Some(&kwargs),
228 )
229 }
230
231 fn __repr__(&self) -> String {
232 format!(
233 "IssuerAndSerialNumber(issuer=<{} bytes>, serial=<{} bytes>)",
234 self.issuer.len(),
235 self.serial_number_bytes.len(),
236 )
237 }
238}
239
240#[pyclass(frozen, name = "PKAuthenticator")]
249pub struct PyPKAuthenticator {
250 cusec: i64,
251 ctime: String,
252 nonce: i64,
253 pa_checksum: Option<Vec<u8>>,
254 freshness_token: Option<Vec<u8>>,
255}
256
257#[pymethods]
258impl PyPKAuthenticator {
259 #[staticmethod]
261 fn from_der(data: &[u8]) -> PyResult<Self> {
262 let mut dec = Decoder::new(data, Encoding::Der);
263 let parsed: synta_krb5::pkinit::PKAuthenticator<'_> = dec.decode().map_err(SyntaErr)?;
264 Ok(Self {
265 cusec: parsed
266 .cusec
267 .as_i64()
268 .map_err(|_| PyOverflowError::new_err("cusec out of i64 range"))?,
269 ctime: parsed.ctime.to_string(),
270 nonce: parsed
271 .nonce
272 .as_i64()
273 .map_err(|_| PyOverflowError::new_err("nonce out of i64 range"))?,
274 pa_checksum: parsed.pa_checksum.map(|s| s.as_bytes().to_vec()),
275 freshness_token: parsed.freshness_token.map(|s| s.as_bytes().to_vec()),
276 })
277 }
278
279 #[getter]
281 fn cusec(&self) -> i64 {
282 self.cusec
283 }
284
285 #[getter]
287 fn ctime(&self) -> &str {
288 &self.ctime
289 }
290
291 #[getter]
293 fn nonce(&self) -> i64 {
294 self.nonce
295 }
296
297 #[getter]
299 fn pa_checksum<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyBytes>> {
300 self.pa_checksum.as_deref().map(|b| PyBytes::new(py, b))
301 }
302
303 #[getter]
305 fn freshness_token<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyBytes>> {
306 self.freshness_token.as_deref().map(|b| PyBytes::new(py, b))
307 }
308
309 fn __repr__(&self) -> String {
310 format!(
311 "PKAuthenticator(ctime='{}', nonce={})",
312 self.ctime, self.nonce
313 )
314 }
315}
316
317#[pyclass(frozen, name = "DHRepInfo")]
326pub struct PyDHRepInfo {
327 dh_signed_data: Vec<u8>,
328 server_dhnonce: Option<Vec<u8>>,
329}
330
331#[pymethods]
332impl PyDHRepInfo {
333 #[staticmethod]
335 fn from_der(data: &[u8]) -> PyResult<Self> {
336 let mut dec = Decoder::new(data, Encoding::Der);
337 let parsed: synta_krb5::pkinit::DHRepInfo<'_> = dec.decode().map_err(SyntaErr)?;
338 Ok(Self {
339 dh_signed_data: parsed.dh_signed_data.as_bytes().to_vec(),
340 server_dhnonce: parsed.server_dhnonce.map(|s| s.as_bytes().to_vec()),
341 })
342 }
343
344 #[getter]
346 fn dh_signed_data<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
347 PyBytes::new(py, &self.dh_signed_data)
348 }
349
350 #[getter]
352 fn server_dhnonce<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyBytes>> {
353 self.server_dhnonce.as_deref().map(|b| PyBytes::new(py, b))
354 }
355
356 fn __repr__(&self) -> String {
357 format!(
358 "DHRepInfo(dh_signed_data=<{} bytes>)",
359 self.dh_signed_data.len()
360 )
361 }
362}
363
364#[pyclass(frozen, name = "KDCDHKeyInfo")]
373pub struct PyKDCDHKeyInfo {
374 subject_public_key: Vec<u8>,
375 nonce: i64,
376 dh_key_expiration: Option<String>,
377}
378
379#[pymethods]
380impl PyKDCDHKeyInfo {
381 #[staticmethod]
383 fn from_der(data: &[u8]) -> PyResult<Self> {
384 let mut dec = Decoder::new(data, Encoding::Der);
385 let parsed: synta_krb5::pkinit::KDCDHKeyInfo<'_> = dec.decode().map_err(SyntaErr)?;
386 Ok(Self {
387 subject_public_key: parsed.subject_public_key.as_bytes().to_vec(),
388 nonce: parsed
389 .nonce
390 .as_i64()
391 .map_err(|_| PyOverflowError::new_err("nonce out of i64 range"))?,
392 dh_key_expiration: parsed.dh_key_expiration.map(|t| t.to_string()),
393 })
394 }
395
396 #[getter]
398 fn subject_public_key<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
399 PyBytes::new(py, &self.subject_public_key)
400 }
401
402 #[getter]
404 fn nonce(&self) -> i64 {
405 self.nonce
406 }
407
408 #[getter]
410 fn dh_key_expiration(&self) -> Option<&str> {
411 self.dh_key_expiration.as_deref()
412 }
413
414 fn __repr__(&self) -> String {
415 format!(
416 "KDCDHKeyInfo(nonce={}, subject_public_key=<{} bytes>)",
417 self.nonce,
418 self.subject_public_key.len()
419 )
420 }
421}
422
423#[pyclass(frozen, name = "ExternalPrincipalIdentifier")]
436pub struct PyExternalPrincipalIdentifier {
437 subject_name: Option<Vec<u8>>,
438 issuer_and_serial_number: Option<Py<PyIssuerAndSerialNumber>>,
439 subject_key_identifier: Option<Vec<u8>>,
440}
441
442#[pymethods]
443impl PyExternalPrincipalIdentifier {
444 #[staticmethod]
446 fn from_der(py: Python<'_>, data: &[u8]) -> PyResult<Self> {
447 let mut dec = Decoder::new(data, Encoding::Der);
448 let parsed: synta_krb5::pkinit::ExternalPrincipalIdentifier<'_> =
449 dec.decode().map_err(SyntaErr)?;
450 let issuer_and_serial_number = parsed
451 .issuer_and_serial_number
452 .map(|isn| {
453 Py::new(
454 py,
455 PyIssuerAndSerialNumber {
456 issuer: isn.issuer.as_bytes().to_vec(),
457 serial_number_bytes: isn.serial_number.as_bytes().to_vec(),
458 },
459 )
460 })
461 .transpose()?;
462 Ok(Self {
463 subject_name: parsed.subject_name.map(|s| s.as_bytes().to_vec()),
464 issuer_and_serial_number,
465 subject_key_identifier: parsed.subject_key_identifier.map(|s| s.as_bytes().to_vec()),
466 })
467 }
468
469 #[getter]
471 fn subject_name<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyBytes>> {
472 self.subject_name.as_deref().map(|b| PyBytes::new(py, b))
473 }
474
475 #[getter]
477 fn issuer_and_serial_number<'py>(
478 &self,
479 py: Python<'py>,
480 ) -> Option<Bound<'py, PyIssuerAndSerialNumber>> {
481 self.issuer_and_serial_number
482 .as_ref()
483 .map(|x| x.clone_ref(py).into_bound(py))
484 }
485
486 #[getter]
488 fn subject_key_identifier<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyBytes>> {
489 self.subject_key_identifier
490 .as_deref()
491 .map(|b| PyBytes::new(py, b))
492 }
493}
494
495#[pyclass(frozen, name = "ReplyKeyPack")]
504pub struct PyReplyKeyPack {
505 reply_key: Py<PyEncryptionKey>,
506 as_checksum: Py<PyChecksum>,
507}
508
509#[pymethods]
510impl PyReplyKeyPack {
511 #[staticmethod]
513 fn from_der(py: Python<'_>, data: &[u8]) -> PyResult<Self> {
514 let mut dec = Decoder::new(data, Encoding::Der);
515 let parsed: synta_krb5::pkinit::ReplyKeyPack<'_> = dec.decode().map_err(SyntaErr)?;
516 let reply_key = Py::new(
517 py,
518 PyEncryptionKey {
519 keytype: parsed
520 .reply_key
521 .keytype
522 .as_i64()
523 .map_err(|_| PyOverflowError::new_err("keytype out of i64 range"))?,
524 keyvalue: parsed.reply_key.keyvalue.as_bytes().to_vec(),
525 },
526 )?;
527 let as_checksum = Py::new(
528 py,
529 PyChecksum {
530 cksumtype: parsed
531 .as_checksum
532 .cksumtype
533 .as_i64()
534 .map_err(|_| PyOverflowError::new_err("cksumtype out of i64 range"))?,
535 checksum: parsed.as_checksum.checksum.as_bytes().to_vec(),
536 },
537 )?;
538 Ok(Self {
539 reply_key,
540 as_checksum,
541 })
542 }
543
544 #[getter]
546 fn reply_key<'py>(&self, py: Python<'py>) -> Bound<'py, PyEncryptionKey> {
547 self.reply_key.clone_ref(py).into_bound(py)
548 }
549
550 #[getter]
552 fn as_checksum<'py>(&self, py: Python<'py>) -> Bound<'py, PyChecksum> {
553 self.as_checksum.clone_ref(py).into_bound(py)
554 }
555
556 fn __repr__(&self) -> String {
557 String::from("ReplyKeyPack(...)")
558 }
559}
560
561#[pyclass(frozen, name = "PaPkAsRep")]
573pub struct PyPaPkAsRep {
574 variant: &'static str,
575 dh_info: Option<Py<PyDHRepInfo>>,
576 enc_key_pack: Option<Vec<u8>>,
577}
578
579#[pymethods]
580impl PyPaPkAsRep {
581 #[staticmethod]
583 fn from_der(py: Python<'_>, data: &[u8]) -> PyResult<Self> {
584 let mut dec = Decoder::new(data, Encoding::Der);
585 let parsed: synta_krb5::pkinit::PaPkAsRep<'_> = dec.decode().map_err(SyntaErr)?;
586 match parsed {
587 synta_krb5::pkinit::PaPkAsRep::DhInfo(dh) => {
588 let dh_info = Py::new(
589 py,
590 PyDHRepInfo {
591 dh_signed_data: dh.dh_signed_data.as_bytes().to_vec(),
592 server_dhnonce: dh.server_dhnonce.map(|s| s.as_bytes().to_vec()),
593 },
594 )?;
595 Ok(Self {
596 variant: "DhInfo",
597 dh_info: Some(dh_info),
598 enc_key_pack: None,
599 })
600 }
601 synta_krb5::pkinit::PaPkAsRep::EncKeyPack(pack) => Ok(Self {
602 variant: "EncKeyPack",
603 dh_info: None,
604 enc_key_pack: Some(pack.as_bytes().to_vec()),
605 }),
606 }
607 }
608
609 #[getter]
611 fn variant(&self) -> &'static str {
612 self.variant
613 }
614
615 #[getter]
617 fn dh_info<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyDHRepInfo>> {
618 self.dh_info
619 .as_ref()
620 .map(|x| x.clone_ref(py).into_bound(py))
621 }
622
623 #[getter]
625 fn enc_key_pack<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyBytes>> {
626 self.enc_key_pack.as_deref().map(|b| PyBytes::new(py, b))
627 }
628
629 fn __repr__(&self) -> String {
630 format!("PaPkAsRep(variant='{}')", self.variant)
631 }
632}
633
634#[pyclass(frozen, name = "AuthPack")]
643pub struct PyAuthPack {
644 pk_authenticator: Py<PyPKAuthenticator>,
645 client_public_value: Option<Vec<u8>>,
646 supported_cmstypes: Option<Vec<u8>>,
647 client_dhnonce: Option<Vec<u8>>,
648 supported_kdfs: Option<Vec<Py<PyKDFAlgorithmId>>>,
649}
650
651#[pymethods]
652impl PyAuthPack {
653 #[staticmethod]
655 fn from_der(py: Python<'_>, data: &[u8]) -> PyResult<Self> {
656 let mut dec = Decoder::new(data, Encoding::Der);
657 let parsed: synta_krb5::pkinit::AuthPack<'_> = dec.decode().map_err(SyntaErr)?;
658 let pka = parsed.pk_authenticator;
659 let pk_authenticator = Py::new(
660 py,
661 PyPKAuthenticator {
662 cusec: pka
663 .cusec
664 .as_i64()
665 .map_err(|_| PyOverflowError::new_err("cusec out of i64 range"))?,
666 ctime: pka.ctime.to_string(),
667 nonce: pka
668 .nonce
669 .as_i64()
670 .map_err(|_| PyOverflowError::new_err("nonce out of i64 range"))?,
671 pa_checksum: pka.pa_checksum.map(|s| s.as_bytes().to_vec()),
672 freshness_token: pka.freshness_token.map(|s| s.as_bytes().to_vec()),
673 },
674 )?;
675 let client_public_value = parsed
676 .client_public_value
677 .as_ref()
678 .map(encode_element_to_vec)
679 .transpose()?;
680 let supported_cmstypes = parsed
681 .supported_cmstypes
682 .as_ref()
683 .map(encode_element_to_vec)
684 .transpose()?;
685 let client_dhnonce = parsed.client_dhnonce.map(|s| s.as_bytes().to_vec());
686 let supported_kdfs = parsed
687 .supported_kdfs
688 .map(|kdfs| {
689 kdfs.into_iter()
690 .map(|kdf| Py::new(py, PyKDFAlgorithmId { kdf_id: kdf.kdf_id }))
691 .collect::<PyResult<Vec<_>>>()
692 })
693 .transpose()?;
694 Ok(Self {
695 pk_authenticator,
696 client_public_value,
697 supported_cmstypes,
698 client_dhnonce,
699 supported_kdfs,
700 })
701 }
702
703 #[getter]
705 fn pk_authenticator<'py>(&self, py: Python<'py>) -> Bound<'py, PyPKAuthenticator> {
706 self.pk_authenticator.clone_ref(py).into_bound(py)
707 }
708
709 #[getter]
711 fn client_public_value<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyBytes>> {
712 self.client_public_value
713 .as_deref()
714 .map(|b| PyBytes::new(py, b))
715 }
716
717 #[getter]
719 fn supported_cmstypes<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyBytes>> {
720 self.supported_cmstypes
721 .as_deref()
722 .map(|b| PyBytes::new(py, b))
723 }
724
725 #[getter]
727 fn client_dhnonce<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyBytes>> {
728 self.client_dhnonce.as_deref().map(|b| PyBytes::new(py, b))
729 }
730
731 #[getter]
733 fn supported_kdfs<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyList>>> {
734 self.supported_kdfs
735 .as_ref()
736 .map(|kdfs| {
737 let list = PyList::empty(py);
738 for kdf in kdfs {
739 list.append(kdf.clone_ref(py).into_bound(py))?;
740 }
741 Ok(list)
742 })
743 .transpose()
744 }
745
746 fn __repr__(&self) -> String {
747 String::from("AuthPack(...)")
748 }
749}
750
751#[pyclass(frozen, name = "PaPkAsReq")]
760pub struct PyPaPkAsReq {
761 signed_auth_pack: Vec<u8>,
762 trusted_certifiers: Option<Vec<Py<PyExternalPrincipalIdentifier>>>,
763 kdc_pk_id: Option<Vec<u8>>,
764}
765
766#[pymethods]
767impl PyPaPkAsReq {
768 #[staticmethod]
770 fn from_der(py: Python<'_>, data: &[u8]) -> PyResult<Self> {
771 let mut dec = Decoder::new(data, Encoding::Der);
772 let parsed: synta_krb5::pkinit::PaPkAsReq<'_> = dec.decode().map_err(SyntaErr)?;
773 let signed_auth_pack = parsed.signed_auth_pack.as_bytes().to_vec();
774 let trusted_certifiers = parsed
775 .trusted_certifiers
776 .map(|certs| {
777 certs
778 .into_iter()
779 .map(|epi| {
780 let isn = epi
781 .issuer_and_serial_number
782 .map(|isn| {
783 Py::new(
784 py,
785 PyIssuerAndSerialNumber {
786 issuer: isn.issuer.as_bytes().to_vec(),
787 serial_number_bytes: isn.serial_number.as_bytes().to_vec(),
788 },
789 )
790 })
791 .transpose()?;
792 Py::new(
793 py,
794 PyExternalPrincipalIdentifier {
795 subject_name: epi.subject_name.map(|s| s.as_bytes().to_vec()),
796 issuer_and_serial_number: isn,
797 subject_key_identifier: epi
798 .subject_key_identifier
799 .map(|s| s.as_bytes().to_vec()),
800 },
801 )
802 })
803 .collect::<PyResult<Vec<_>>>()
804 })
805 .transpose()?;
806 let kdc_pk_id = parsed.kdc_pk_id.map(|s| s.as_bytes().to_vec());
807 Ok(Self {
808 signed_auth_pack,
809 trusted_certifiers,
810 kdc_pk_id,
811 })
812 }
813
814 #[getter]
816 fn signed_auth_pack<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
817 PyBytes::new(py, &self.signed_auth_pack)
818 }
819
820 #[getter]
822 fn trusted_certifiers<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyList>>> {
823 self.trusted_certifiers
824 .as_ref()
825 .map(|certs| {
826 let list = PyList::empty(py);
827 for cert in certs {
828 list.append(cert.clone_ref(py).into_bound(py))?;
829 }
830 Ok(list)
831 })
832 .transpose()
833 }
834
835 #[getter]
837 fn kdc_pk_id<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyBytes>> {
838 self.kdc_pk_id.as_deref().map(|b| PyBytes::new(py, b))
839 }
840
841 fn __repr__(&self) -> String {
842 format!(
843 "PaPkAsReq(signed_auth_pack=<{} bytes>)",
844 self.signed_auth_pack.len()
845 )
846 }
847}
848
849pub fn register_pkinit_classes(m: &Bound<'_, PyModule>) -> PyResult<()> {
855 m.add_class::<PyEncryptionKey>()?;
856 m.add_class::<PyChecksum>()?;
857 m.add_class::<PyKDFAlgorithmId>()?;
858 m.add_class::<PyIssuerAndSerialNumber>()?;
859 m.add_class::<PyPKAuthenticator>()?;
860 m.add_class::<PyDHRepInfo>()?;
861 m.add_class::<PyKDCDHKeyInfo>()?;
862 m.add_class::<PyExternalPrincipalIdentifier>()?;
863 m.add_class::<PyReplyKeyPack>()?;
864 m.add_class::<PyPaPkAsRep>()?;
865 m.add_class::<PyAuthPack>()?;
866 m.add_class::<PyPaPkAsReq>()?;
867 Ok(())
868}