_synta/krb5.rs
1//! Python bindings for Kerberos V5 and PKINIT types from [`synta_krb5`].
2//!
3//! This module builds the ``synta.krb5`` Python submodule, exposing the
4//! Kerberos V5 and PKINIT ASN.1 types needed for certificate construction
5//! and for parsing Kerberos AS exchanges.
6//!
7//! # Contents
8//!
9//! ## Principal-name support
10//!
11//! * [`PyKrb5PrincipalName`] — the RFC 4556 `KRB5PrincipalName` OtherName
12//! value embedded in PKINIT certificates.
13//! * `KRB5_PRINCIPAL_NAME_OID` — `ObjectIdentifier` for `id-pkinit-san`
14//! (1.3.6.1.5.2.2, RFC 4556 §3.2.2).
15//! * `NT_*` integer constants — principal name types from RFC 4120 §6.2.
16//!
17//! ## PKINIT protocol types (RFC 4556 + RFC 6112 + RFC 8636)
18//!
19//! Provided by [`crate::pkinit::register_pkinit_classes`]:
20//!
21//! | Python class | RFC / section | Description |
22//! |-------------------------------|---------------------|------------------------------------------------|
23//! | `EncryptionKey` | RFC 3961 §2 | Algorithm type and raw key material |
24//! | `Checksum` | RFC 3961 §4 | Algorithm type and raw checksum bytes |
25//! | `KDFAlgorithmId` | RFC 8636 §3.1 | KDF OID used in the PKINIT exchange |
26//! | `IssuerAndSerialNumber` | RFC 4556 §3.2.2 | Certificate identified by issuer + serial |
27//! | `ExternalPrincipalIdentifier` | RFC 4556 §3.2.2 | Certificate by name, issuer/serial, or SKI |
28//! | `PKAuthenticator` | RFC 4556 §3.2.1 | Client proof of liveness in AS-REQ |
29//! | `AuthPack` | RFC 4556 §3.2.1 | Signed client authentication package |
30//! | `PaPkAsReq` | RFC 4556 §3.2.2 | PKINIT pre-authentication request |
31//! | `DHRepInfo` | RFC 4556 §3.2.4 | KDC Diffie-Hellman reply data |
32//! | `KDCDHKeyInfo` | RFC 4556 §3.2.4 | KDC DH public key and nonce |
33//! | `ReplyKeyPack` | RFC 4556 §3.2.3 | Session key and checksum from KDC |
34//! | `PaPkAsRep` | RFC 4556 §3.2.4 | PKINIT pre-authentication reply (CHOICE) |
35//!
36//! # Principal name types (`NT_*`)
37//!
38//! | Constant | Value | RFC / source | Typical use |
39//! |--------------------|-------|------------------|--------------------------------------|
40//! | `NT_UNKNOWN` | 0 | RFC 4120 §6.2 | Unknown / unspecified |
41//! | `NT_PRINCIPAL` | 1 | RFC 4120 §6.2 | User or host principal |
42//! | `NT_SRV_INST` | 2 | RFC 4120 §6.2 | Service + instance (e.g. `krbtgt`) |
43//! | `NT_SRV_HST` | 3 | RFC 4120 §6.2 | Service + hostname |
44//! | `NT_SRV_XHST` | 4 | RFC 4120 §6.2 | Service + host (remaining components)|
45//! | `NT_UID` | 5 | RFC 4120 §6.2 | Unique ID |
46//! | `NT_X500_PRINCIPAL`| 6 | RFC 4120 §6.2 | Encoded X.500 DN |
47//! | `NT_SMTP_NAME` | 7 | RFC 4120 §6.2 | SMTP email address |
48//! | `NT_ENTERPRISE` | 10 | RFC 6806 §5 | Enterprise principal (UPN-like) |
49//! | `NT_WELLKNOWN` | 11 | RFC 8062 §3 | Well-known principal (anonymous) |
50//! | `NT_SRV_HST_DOMAIN`| 12 | MS-SFU §2.1 | Host-based service (Windows) |
51//!
52//! # Register
53//!
54//! Call [`register_krb5_module`] from the extension crate's `#[pymodule]`:
55//!
56//! ```rust,ignore
57//! krb5::register_krb5_module(m)?;
58//! ```
59
60use pyo3::prelude::*;
61use pyo3::types::PyBytes;
62
63use synta::tag::{TagClass, TAG_SEQUENCE};
64use synta::traits::{Decode, Encode};
65use synta::{Decoder, Encoder, Encoding};
66
67use synta_krb5::constants::{
68 ETYPE_AES128_CTS_HMAC_SHA1_96, ETYPE_AES128_CTS_HMAC_SHA256_128, ETYPE_AES256_CTS_HMAC_SHA1_96,
69 ETYPE_AES256_CTS_HMAC_SHA384_192, ETYPE_CAMELLIA128_CTS_CMAC, ETYPE_CAMELLIA256_CTS_CMAC,
70 ETYPE_DES3_CBC_MD5, ETYPE_DES3_CBC_SHA1, ETYPE_DES3_CBC_SHA1_KD, ETYPE_DES_CBC_CRC,
71 ETYPE_DES_CBC_MD4, ETYPE_DES_CBC_MD5, ETYPE_DES_HMAC_SHA1, ETYPE_RC4_HMAC, ETYPE_RC4_HMAC_EXP,
72 ID_PKINIT_SAN_COMPONENTS, NT_ENTERPRISE, NT_PRINCIPAL, NT_SMTP_NAME, NT_SRV_HST,
73 NT_SRV_HST_DOMAIN, NT_SRV_INST, NT_SRV_XHST, NT_UID, NT_UNKNOWN, NT_WELLKNOWN,
74 NT_X500_PRINCIPAL,
75};
76use synta_krb5::kerberos_v5::Int32;
77
78use crate::error::SyntaErr;
79use crate::types::PyObjectIdentifier;
80
81/// Build a `PyObjectIdentifier` from a slice of OID arc components.
82///
83/// Returns `Err` only on OOM; the components are hardcoded constants
84/// that are always valid OIDs.
85fn oid_const(py: Python<'_>, components: &[u32]) -> PyResult<Py<pyo3::PyAny>> {
86 use synta::ObjectIdentifier;
87 let inner = ObjectIdentifier::new(components).expect("hardcoded OID is always valid");
88 Ok(Py::new(py, PyObjectIdentifier::from_oid(inner))?.into_any())
89}
90
91// ─── encoding helpers ────────────────────────────────────────────────────────
92
93/// Append a SEQUENCE TLV wrapping `inner` to `enc`.
94fn write_sequence(enc: &mut Encoder, inner: &[u8]) -> Result<(), synta::Error> {
95 enc.write_tag(synta::Tag::universal_constructed(TAG_SEQUENCE))?;
96 enc.write_length(inner.len())?;
97 enc.write_bytes(inner);
98 Ok(())
99}
100
101/// Append a context-specific explicit tag `[tag_num]` wrapping `inner`.
102fn write_explicit_ctx(enc: &mut Encoder, tag_num: u32, inner: &[u8]) -> Result<(), synta::Error> {
103 enc.write_tag(synta::Tag::new(TagClass::ContextSpecific, true, tag_num))?;
104 enc.write_length(inner.len())?;
105 enc.write_bytes(inner);
106 Ok(())
107}
108
109/// Encode `value` as DER, returning the bytes.
110fn encode_to_vec<T: Encode>(value: &T) -> Result<Vec<u8>, synta::Error> {
111 let mut enc = Encoder::new(Encoding::Der);
112 value.encode(&mut enc)?;
113 enc.finish()
114}
115
116// ─── decoding helpers ────────────────────────────────────────────────────────
117
118fn read_tag_check(
119 dec: &mut Decoder<'_>,
120 class: TagClass,
121 number: u32,
122 constructed: bool,
123 label: &'static str,
124) -> PyResult<()> {
125 let tag = dec.read_tag().map_err(SyntaErr)?;
126 if tag.class() != class || tag.number() != number || tag.is_constructed() != constructed {
127 return Err(pyo3::exceptions::PyValueError::new_err(format!(
128 "KRB5PrincipalName: expected {label} (class={class:?}, tag={number}), \
129 got class={:?} tag={} constructed={}",
130 tag.class(),
131 tag.number(),
132 tag.is_constructed()
133 )));
134 }
135 Ok(())
136}
137
138fn read_definite_len(dec: &mut Decoder<'_>) -> PyResult<usize> {
139 Ok(dec
140 .read_length()
141 .map_err(SyntaErr)?
142 .definite()
143 .map_err(SyntaErr)?)
144}
145
146/// Read a constructed TLV with the given tag and return the content bytes.
147fn read_content<'a>(
148 dec: &mut Decoder<'a>,
149 class: TagClass,
150 number: u32,
151 label: &'static str,
152) -> PyResult<&'a [u8]> {
153 read_tag_check(dec, class, number, true, label)?;
154 let len = read_definite_len(dec)?;
155 Ok(dec.read_bytes(len).map_err(SyntaErr)?)
156}
157
158/// Read a SEQUENCE and return its content bytes.
159fn read_sequence<'a>(dec: &mut Decoder<'a>, label: &'static str) -> PyResult<&'a [u8]> {
160 read_content(dec, TagClass::Universal, TAG_SEQUENCE, label)
161}
162
163/// Read a context-specific explicit tag `[N]` and return its content bytes.
164fn read_explicit_ctx<'a>(
165 dec: &mut Decoder<'a>,
166 tag_num: u32,
167 label: &'static str,
168) -> PyResult<&'a [u8]> {
169 read_content(dec, TagClass::ContextSpecific, tag_num, label)
170}
171
172// ─── KRB5PrincipalName encode / decode ──────────────────────────────────────
173
174/// Encode a `KRB5PrincipalName` (RFC 4556) SEQUENCE to DER.
175///
176/// ```asn1
177/// KRB5PrincipalName ::= SEQUENCE {
178/// realm [0] GeneralString,
179/// principalName [1] PrincipalName
180/// }
181/// PrincipalName ::= SEQUENCE {
182/// name-type [0] Int32,
183/// name-string [1] SEQUENCE OF GeneralString
184/// }
185/// ```
186fn encode_krb5principalname(
187 realm: &str,
188 name_type: i32,
189 components: &[String],
190) -> PyResult<Vec<u8>> {
191 // 1. name-string: SEQUENCE OF GeneralString
192 let ns_items: Result<Vec<Vec<u8>>, synta::Error> = components
193 .iter()
194 .map(|c| encode_to_vec(&synta::GeneralString::new(c.as_bytes().to_vec())))
195 .collect();
196 let ns_items = ns_items.map_err(SyntaErr)?;
197 let ns_flat: Vec<u8> = ns_items.concat();
198
199 let mut ns_seq_enc = Encoder::new(Encoding::Der);
200 write_sequence(&mut ns_seq_enc, &ns_flat).map_err(SyntaErr)?;
201 let ns_seq_bytes = ns_seq_enc.finish().map_err(SyntaErr)?;
202
203 // 2. name-type: INTEGER
204 let nt_bytes = encode_to_vec(&synta::Integer::from_i64(name_type as i64)).map_err(SyntaErr)?;
205
206 // 3. PrincipalName = SEQUENCE { [0] name-type, [1] name-string }
207 let mut nt_ctx = Encoder::new(Encoding::Der);
208 write_explicit_ctx(&mut nt_ctx, 0, &nt_bytes).map_err(SyntaErr)?;
209 let nt_ctx_bytes = nt_ctx.finish().map_err(SyntaErr)?;
210
211 let mut ns_ctx = Encoder::new(Encoding::Der);
212 write_explicit_ctx(&mut ns_ctx, 1, &ns_seq_bytes).map_err(SyntaErr)?;
213 let ns_ctx_bytes = ns_ctx.finish().map_err(SyntaErr)?;
214
215 let pn_inner = [nt_ctx_bytes, ns_ctx_bytes].concat();
216 let mut pn_enc = Encoder::new(Encoding::Der);
217 write_sequence(&mut pn_enc, &pn_inner).map_err(SyntaErr)?;
218 let pn_bytes = pn_enc.finish().map_err(SyntaErr)?;
219
220 // 4. realm: GeneralString
221 let realm_gs_bytes =
222 encode_to_vec(&synta::GeneralString::new(realm.as_bytes().to_vec())).map_err(SyntaErr)?;
223
224 // 5. Outer SEQUENCE { [0] realm, [1] principalName }
225 let mut realm_ctx = Encoder::new(Encoding::Der);
226 write_explicit_ctx(&mut realm_ctx, 0, &realm_gs_bytes).map_err(SyntaErr)?;
227 let realm_ctx_bytes = realm_ctx.finish().map_err(SyntaErr)?;
228
229 let mut pn_ctx = Encoder::new(Encoding::Der);
230 write_explicit_ctx(&mut pn_ctx, 1, &pn_bytes).map_err(SyntaErr)?;
231 let pn_ctx_bytes = pn_ctx.finish().map_err(SyntaErr)?;
232
233 let outer_content = [realm_ctx_bytes, pn_ctx_bytes].concat();
234 let mut outer = Encoder::new(Encoding::Der);
235 write_sequence(&mut outer, &outer_content).map_err(SyntaErr)?;
236 Ok(outer.finish().map_err(SyntaErr)?)
237}
238
239/// Decode a `KRB5PrincipalName` (RFC 4556) DER blob.
240///
241/// Returns `(realm, name_type, components)`.
242fn decode_krb5principalname(data: &[u8]) -> PyResult<(String, i32, Vec<String>)> {
243 let ve = |s: &str| pyo3::exceptions::PyValueError::new_err(s.to_string());
244
245 // Outer SEQUENCE
246 let mut outer = Decoder::new(data, Encoding::Der);
247 let outer_content = read_sequence(&mut outer, "outer SEQUENCE")?;
248
249 // [0] realm GeneralString
250 let mut seq = Decoder::new(outer_content, Encoding::Der);
251 let realm_bytes = read_explicit_ctx(&mut seq, 0, "[0] realm")?;
252 let mut realm_dec = Decoder::new(realm_bytes, Encoding::Der);
253 let realm_gs = synta::GeneralString::decode(&mut realm_dec).map_err(SyntaErr)?;
254 let realm = String::from_utf8(realm_gs.as_bytes().to_vec())
255 .map_err(|_| ve("KRB5PrincipalName: realm is not valid UTF-8"))?;
256
257 // [1] principalName PrincipalName
258 let pn_bytes = read_explicit_ctx(&mut seq, 1, "[1] principalName")?;
259
260 // PrincipalName SEQUENCE
261 let mut pn_dec = Decoder::new(pn_bytes, Encoding::Der);
262 let pn_inner = read_sequence(&mut pn_dec, "PrincipalName SEQUENCE")?;
263
264 // [0] name-type Int32
265 let mut pn_inner_dec = Decoder::new(pn_inner, Encoding::Der);
266 let nt_bytes = read_explicit_ctx(&mut pn_inner_dec, 0, "[0] name-type")?;
267 let mut nt_dec = Decoder::new(nt_bytes, Encoding::Der);
268 let int32 = Int32::decode(&mut nt_dec).map_err(SyntaErr)?;
269 let name_type = int32.get();
270
271 // [1] name-string SEQUENCE OF GeneralString
272 let ns_ctx_bytes = read_explicit_ctx(&mut pn_inner_dec, 1, "[1] name-string")?;
273 let mut ns_ctx_dec = Decoder::new(ns_ctx_bytes, Encoding::Der);
274 let ns_bytes = read_sequence(&mut ns_ctx_dec, "name-string SEQUENCE")?;
275
276 let mut components = Vec::new();
277 let mut ns_dec = Decoder::new(ns_bytes, Encoding::Der);
278 while !ns_dec.is_empty() {
279 let gs = synta::GeneralString::decode(&mut ns_dec).map_err(SyntaErr)?;
280 let s = String::from_utf8(gs.as_bytes().to_vec())
281 .map_err(|_| ve("KRB5PrincipalName: name component is not valid UTF-8"))?;
282 components.push(s);
283 }
284
285 Ok((realm, name_type, components))
286}
287
288// ─── Python class ────────────────────────────────────────────────────────────
289
290/// RFC 4556 ``KRB5PrincipalName`` — Kerberos principal embedded in a PKINIT
291/// certificate as an ``OtherName`` Subject Alternative Name.
292///
293/// ``KRB5PrincipalName`` is defined in RFC 4556 §3.2.2 as:
294///
295/// ```text
296/// KRB5PrincipalName ::= SEQUENCE {
297/// realm [0] Realm, -- GeneralString
298/// principalName [1] PrincipalName -- RFC 4120 §5.2.2
299/// }
300///
301/// PrincipalName ::= SEQUENCE {
302/// name-type [0] Int32,
303/// name-string [1] SEQUENCE OF KerberosString -- GeneralString
304/// }
305/// ```
306///
307/// The ``OtherName`` OID for this type is ``1.3.6.1.5.2.2``,
308/// available as ``synta.krb5.KRB5_PRINCIPAL_NAME_OID``.
309///
310/// **Name types** — use the ``NT_*`` module constants:
311///
312/// * ``NT_PRINCIPAL`` (1) — user principal, e.g. ``alice@EXAMPLE.COM``
313/// * ``NT_SRV_INST`` (2) — service + instance, e.g. ``krbtgt/EXAMPLE.COM``
314/// * ``NT_SRV_HST`` (3) — service + hostname, e.g. ``host/server.example.com``
315/// * ``NT_ENTERPRISE`` (10) — enterprise (UPN-style), e.g. ``user@domain.example``
316///
317/// **Display format**: Kerberos principal strings conventionally use
318/// ``/`` to join components and ``@`` to separate from the realm:
319/// ``service/instance@REALM``. This class stores the components and realm
320/// individually; format them yourself if you need a display string.
321///
322/// Example — PKINIT TGT principal for ``EXAMPLE.COM``:
323///
324/// ```python
325/// import synta.krb5
326///
327/// name = synta.krb5.Krb5PrincipalName(
328/// realm="EXAMPLE.COM",
329/// name_type=synta.krb5.NT_SRV_INST,
330/// components=["krbtgt", "EXAMPLE.COM"],
331/// )
332/// # OID for OtherName: synta.krb5.KRB5_PRINCIPAL_NAME_OID (1.3.6.1.5.2.2)
333/// der_bytes = name.to_der() # DER SEQUENCE bytes for OtherName.value
334/// ```
335///
336/// Example — decode from an OtherName SAN value:
337///
338/// ```python
339/// parsed = synta.krb5.Krb5PrincipalName.from_der(der_bytes)
340/// print(parsed.realm) # "EXAMPLE.COM"
341/// print("/".join(parsed.components) + "@" + parsed.realm)
342/// # "krbtgt/EXAMPLE.COM@EXAMPLE.COM"
343/// ```
344#[pyclass(name = "Krb5PrincipalName", frozen)]
345pub struct PyKrb5PrincipalName {
346 realm: String,
347 name_type: i32,
348 components: Vec<String>,
349}
350
351#[pymethods]
352impl PyKrb5PrincipalName {
353 /// Create a new ``Krb5PrincipalName``.
354 ///
355 /// Args:
356 /// realm: Kerberos realm (ASCII string, e.g. ``"EXAMPLE.COM"``).
357 /// name_type: Principal name type. Use one of the ``NT_*`` module
358 /// constants: ``NT_PRINCIPAL``, ``NT_SRV_INST``, ``NT_SRV_HST``,
359 /// ``NT_ENTERPRISE``, etc.
360 /// components: Name-string components. The number and meaning of
361 /// components depends on ``name_type``:
362 ///
363 /// * ``NT_PRINCIPAL`` — one component: the user name.
364 /// * ``NT_SRV_INST`` — two components: service name and instance
365 /// (e.g. ``["krbtgt", "EXAMPLE.COM"]``).
366 /// * ``NT_SRV_HST`` — two components: service name and hostname
367 /// (e.g. ``["host", "server.example.com"]``).
368 /// * ``NT_ENTERPRISE`` — one component: the enterprise name.
369 ///
370 /// Raises ``ValueError`` if ``realm`` or any component contains characters
371 /// outside ASCII (Kerberos strings are ``GeneralString``, which is a subset
372 /// of ASCII in practice).
373 #[new]
374 #[pyo3(signature = (realm, name_type, components))]
375 fn new(realm: String, name_type: i32, components: Vec<String>) -> PyResult<Self> {
376 if !realm.is_ascii() {
377 return Err(pyo3::exceptions::PyValueError::new_err(
378 "realm must be an ASCII string",
379 ));
380 }
381 for comp in &components {
382 if !comp.is_ascii() {
383 return Err(pyo3::exceptions::PyValueError::new_err(format!(
384 "name component {comp:?} must be an ASCII string"
385 )));
386 }
387 }
388 Ok(Self {
389 realm,
390 name_type,
391 components,
392 })
393 }
394
395 /// Kerberos realm string (e.g. ``"EXAMPLE.COM"``).
396 ///
397 /// Always ASCII. Conventionally upper-case DNS domain name.
398 #[getter]
399 fn realm(&self) -> &str {
400 &self.realm
401 }
402
403 /// Principal name type integer (one of the ``NT_*`` module constants).
404 ///
405 /// Common values: ``NT_PRINCIPAL`` (1), ``NT_SRV_INST`` (2),
406 /// ``NT_SRV_HST`` (3), ``NT_ENTERPRISE`` (10).
407 #[getter]
408 fn name_type(&self) -> i32 {
409 self.name_type
410 }
411
412 /// Name-string components as a list of ASCII strings.
413 ///
414 /// The list length and semantics depend on ``name_type``. For
415 /// ``NT_SRV_INST`` the conventional display form joins components with
416 /// ``"/"`` and appends ``"@" + realm``, e.g.:
417 ///
418 /// ```python
419 /// "/".join(name.components) + "@" + name.realm
420 /// # -> "krbtgt/EXAMPLE.COM@EXAMPLE.COM"
421 /// ```
422 #[getter]
423 fn components<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, pyo3::types::PyList>> {
424 pyo3::types::PyList::new(py, self.components.iter())
425 }
426
427 /// Encode this ``Krb5PrincipalName`` to DER bytes.
428 ///
429 /// Returns the complete DER encoding of the ``KRB5PrincipalName``
430 /// ``SEQUENCE``. These bytes are the ``value`` field of an X.509
431 /// ``OtherName`` SAN with OID ``1.3.6.1.5.2.2`` (id-pkinit-san).
432 ///
433 /// Example — build an OtherName value for a PKINIT TGT principal:
434 ///
435 /// ```python
436 /// import synta.krb5
437 ///
438 /// name = synta.krb5.Krb5PrincipalName("EXAMPLE.COM", synta.krb5.NT_SRV_INST,
439 /// ["krbtgt", "EXAMPLE.COM"])
440 /// oid = synta.krb5.KRB5_PRINCIPAL_NAME_OID # ObjectIdentifier("1.3.6.1.5.2.2")
441 /// other_name_value = name.to_der()
442 /// # Pass (oid, other_name_value) to your X.509 library's OtherName constructor.
443 /// ```
444 fn to_der<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
445 let der = encode_krb5principalname(&self.realm, self.name_type, &self.components)?;
446 Ok(PyBytes::new(py, &der))
447 }
448
449 /// Decode a ``Krb5PrincipalName`` from DER bytes.
450 ///
451 /// Parses the DER encoding of a ``KRB5PrincipalName`` SEQUENCE as it
452 /// appears in the ``value`` field of an X.509 ``OtherName`` SAN with
453 /// OID ``1.3.6.1.5.2.2``.
454 ///
455 /// Args:
456 /// data: DER-encoded ``KRB5PrincipalName`` SEQUENCE bytes.
457 ///
458 /// Returns a new ``Krb5PrincipalName`` instance.
459 ///
460 /// Raises ``ValueError`` on malformed or truncated input.
461 ///
462 /// Example — decode a ``KRB5PrincipalName`` from a certificate OtherName:
463 ///
464 /// ```python
465 /// import synta, synta.krb5, synta.oids
466 ///
467 /// cert = synta.Certificate.from_der(der)
468 /// san_der = cert.get_extension_value_der(synta.oids.SUBJECT_ALT_NAME)
469 /// # ... extract the OtherName value bytes from the SAN extension ...
470 /// # Check OtherName OID == synta.krb5.KRB5_PRINCIPAL_NAME_OID
471 /// name = synta.krb5.Krb5PrincipalName.from_der(other_name_value)
472 /// print(name.realm, name.name_type, name.components)
473 /// ```
474 #[staticmethod]
475 fn from_der(data: &[u8]) -> PyResult<Self> {
476 let (realm, name_type, components) = decode_krb5principalname(data)?;
477 Ok(Self {
478 realm,
479 name_type,
480 components,
481 })
482 }
483
484 /// Return a developer-readable string representation.
485 ///
486 /// Example:
487 ///
488 /// ```python
489 /// >>> repr(synta.krb5.Krb5PrincipalName("EXAMPLE.COM", 2, ["krbtgt", "EXAMPLE.COM"]))
490 /// "Krb5PrincipalName(realm='EXAMPLE.COM', name_type=2, components=['krbtgt', 'EXAMPLE.COM'])"
491 /// ```
492 fn __repr__(&self) -> String {
493 format!(
494 "Krb5PrincipalName(realm={:?}, name_type={}, components={:?})",
495 self.realm, self.name_type, self.components
496 )
497 }
498
499 /// Return ``True`` if both instances have identical realm, name_type,
500 /// and components.
501 fn __eq__(&self, other: PyRef<'_, Self>) -> bool {
502 self.realm == other.realm
503 && self.name_type == other.name_type
504 && self.components == other.components
505 }
506}
507
508// ─── submodule registration ──────────────────────────────────────────────────
509
510/// Build and register the ``synta.krb5`` submodule into `parent`.
511///
512/// Adds the following to the submodule:
513///
514/// **Classes** — principal-name support:
515/// `Krb5PrincipalName`
516///
517/// **Classes** — PKINIT protocol types (RFC 4556 + RFC 6112 + RFC 8636):
518/// `EncryptionKey`, `Checksum`, `KDFAlgorithmId`, `IssuerAndSerialNumber`,
519/// `ExternalPrincipalIdentifier`, `PKAuthenticator`, `AuthPack`, `PaPkAsReq`,
520/// `DHRepInfo`, `KDCDHKeyInfo`, `ReplyKeyPack`, `PaPkAsRep`
521///
522/// **OID constant**: `KRB5_PRINCIPAL_NAME_OID` — `ObjectIdentifier` for
523/// `id-pkinit-san` (1.3.6.1.5.2.2, RFC 4556 §3.2.2).
524///
525/// **Name-type constants** (RFC 4120 §6.2):
526/// `NT_UNKNOWN`, `NT_PRINCIPAL`, `NT_SRV_INST`, `NT_SRV_HST`, `NT_SRV_XHST`,
527/// `NT_UID`, `NT_X500_PRINCIPAL`, `NT_SMTP_NAME`, `NT_ENTERPRISE`,
528/// `NT_WELLKNOWN`, `NT_SRV_HST_DOMAIN`
529///
530/// **Encryption-type constants** (RFC 3961 / RFC 3962 / RFC 6803 / RFC 8009):
531/// `ETYPE_DES_CBC_CRC`, `ETYPE_DES_CBC_MD4`, `ETYPE_DES_CBC_MD5`,
532/// `ETYPE_DES3_CBC_MD5`, `ETYPE_DES3_CBC_SHA1`, `ETYPE_DES_HMAC_SHA1`,
533/// `ETYPE_DES3_CBC_SHA1_KD`, `ETYPE_AES128_CTS_HMAC_SHA1_96`,
534/// `ETYPE_AES256_CTS_HMAC_SHA1_96`, `ETYPE_AES128_CTS_HMAC_SHA256_128`,
535/// `ETYPE_AES256_CTS_HMAC_SHA384_192`, `ETYPE_RC4_HMAC`, `ETYPE_RC4_HMAC_EXP`,
536/// `ETYPE_CAMELLIA128_CTS_CMAC`, `ETYPE_CAMELLIA256_CTS_CMAC`
537///
538/// Also installs the submodule into `sys.modules["synta.krb5"]` so that
539/// ``import synta.krb5`` and ``from synta.krb5 import ...`` work after
540/// ``import synta``.
541pub fn register_krb5_module(parent: &Bound<'_, PyModule>) -> PyResult<()> {
542 let py = parent.py();
543 let m = PyModule::new(py, "krb5")?;
544
545 // Principal name type constants (RFC 4120 §6.2)
546 m.add("NT_UNKNOWN", NT_UNKNOWN)?;
547 m.add("NT_PRINCIPAL", NT_PRINCIPAL)?;
548 m.add("NT_SRV_INST", NT_SRV_INST)?;
549 m.add("NT_SRV_HST", NT_SRV_HST)?;
550 m.add("NT_SRV_XHST", NT_SRV_XHST)?;
551 m.add("NT_UID", NT_UID)?;
552 m.add("NT_X500_PRINCIPAL", NT_X500_PRINCIPAL)?;
553 m.add("NT_SMTP_NAME", NT_SMTP_NAME)?;
554 m.add("NT_ENTERPRISE", NT_ENTERPRISE)?;
555 m.add("NT_WELLKNOWN", NT_WELLKNOWN)?;
556 m.add("NT_SRV_HST_DOMAIN", NT_SRV_HST_DOMAIN)?;
557
558 // Encryption type constants (RFC 3961, RFC 3962, RFC 6803, RFC 8009)
559 m.add("ETYPE_DES_CBC_CRC", ETYPE_DES_CBC_CRC)?;
560 m.add("ETYPE_DES_CBC_MD4", ETYPE_DES_CBC_MD4)?;
561 m.add("ETYPE_DES_CBC_MD5", ETYPE_DES_CBC_MD5)?;
562 m.add("ETYPE_DES3_CBC_MD5", ETYPE_DES3_CBC_MD5)?;
563 m.add("ETYPE_DES3_CBC_SHA1", ETYPE_DES3_CBC_SHA1)?;
564 m.add("ETYPE_DES_HMAC_SHA1", ETYPE_DES_HMAC_SHA1)?;
565 m.add("ETYPE_DES3_CBC_SHA1_KD", ETYPE_DES3_CBC_SHA1_KD)?;
566 m.add(
567 "ETYPE_AES128_CTS_HMAC_SHA1_96",
568 ETYPE_AES128_CTS_HMAC_SHA1_96,
569 )?;
570 m.add(
571 "ETYPE_AES256_CTS_HMAC_SHA1_96",
572 ETYPE_AES256_CTS_HMAC_SHA1_96,
573 )?;
574 m.add(
575 "ETYPE_AES128_CTS_HMAC_SHA256_128",
576 ETYPE_AES128_CTS_HMAC_SHA256_128,
577 )?;
578 m.add(
579 "ETYPE_AES256_CTS_HMAC_SHA384_192",
580 ETYPE_AES256_CTS_HMAC_SHA384_192,
581 )?;
582 m.add("ETYPE_RC4_HMAC", ETYPE_RC4_HMAC)?;
583 m.add("ETYPE_RC4_HMAC_EXP", ETYPE_RC4_HMAC_EXP)?;
584 m.add("ETYPE_CAMELLIA128_CTS_CMAC", ETYPE_CAMELLIA128_CTS_CMAC)?;
585 m.add("ETYPE_CAMELLIA256_CTS_CMAC", ETYPE_CAMELLIA256_CTS_CMAC)?;
586
587 // OID constants
588 m.add(
589 "KRB5_PRINCIPAL_NAME_OID",
590 oid_const(py, ID_PKINIT_SAN_COMPONENTS)?,
591 )?;
592
593 // KRB5PrincipalName class
594 m.add_class::<PyKrb5PrincipalName>()?;
595
596 // PKINIT protocol types (RFC 4556 + RFC 6112 + RFC 8636)
597 crate::pkinit::register_pkinit_classes(&m)?;
598
599 crate::install_submodule(
600 parent,
601 &m,
602 "synta.krb5",
603 Some(
604 "Kerberos V5 ASN.1 types: principal names, PKINIT protocol types, \
605 name-type constants (NT_*), and encryption-type constants (ETYPE_*).",
606 ),
607 )
608}