Skip to main content

netget/server/kerberos/
kdc.rs

1//! The KDC engine: everything between a request's bytes and the model's decision, and
2//! everything between the decision and the reply's bytes. No I/O.
3//!
4//! [`Kdc::analyze`] reads a request and either answers it mechanically — a malformed message,
5//! a wrong realm, an unsupported option or enctype, a pre-authentication that does not decrypt,
6//! a stale or replayed timestamp, a TGT or authenticator that fails its integrity check — or
7//! returns a [`Pending`] request for the model: a question of policy, asked only once every
8//! cryptographic fact in it has been established.
9//!
10//! [`Kdc::issue`], [`Kdc::preauth_required`] and [`Kdc::refuse`] build the three answers the
11//! model can choose. `issue` still enforces what is not policy: no ticket for a principal
12//! without a configured key, none without pre-authentication when the principal requires it,
13//! no lifetime past the configured ceiling, the TGT's end time or the client's own `till`, and
14//! no flag the client did not ask for or its TGT does not carry.
15
16use std::collections::HashMap;
17use std::net::SocketAddr;
18use std::sync::Mutex;
19
20use anyhow::{anyhow, Context, Result};
21use picky_asn1::wrapper::{
22    Asn1SequenceOf, ExplicitContextTag0, ExplicitContextTag1, ExplicitContextTag10,
23    ExplicitContextTag11, ExplicitContextTag12, ExplicitContextTag2, ExplicitContextTag3,
24    ExplicitContextTag4, ExplicitContextTag5, ExplicitContextTag6, ExplicitContextTag7,
25    ExplicitContextTag8, ExplicitContextTag9, IntegerAsn1, OctetStringAsn1, Optional,
26};
27use picky_krb::data_types::{
28    Authenticator, EncTicketPart, EncTicketPartInner, EncryptedData, HostAddresses, LastReqInner,
29    PaData, PaEncTsEnc, Ticket, TicketInner, TransitedEncoding,
30};
31use picky_krb::messages::{
32    ApReq, AsRep, AsReq, EncAsRepPart, EncKdcRepPart, EncTgsRepPart, KdcRep, KdcReq, KrbError,
33    KrbErrorInner, TgsRep, TgsReq,
34};
35use serde_json::{json, Value};
36
37use super::codec::{
38    self, decode, encode, etype_name, flag_names, flags, flags_u32, int, int_i64, kdc_options,
39    kstring, kstring_text, now_secs, rfc3339, ticket_flags, unix, Key, Principal, ETYPE_AES256,
40    KDC_ERR_BADOPTION, KDC_ERR_C_PRINCIPAL_UNKNOWN, KDC_ERR_ETYPE_NOSUPP, KDC_ERR_NEVER_VALID,
41    KDC_ERR_PADATA_TYPE_NOSUPP, KDC_ERR_PREAUTH_FAILED, KDC_ERR_PREAUTH_REQUIRED,
42    KDC_ERR_S_PRINCIPAL_UNKNOWN, KDC_ERR_WRONG_REALM, KRB_AP_ERR_BADMATCH,
43    KRB_AP_ERR_BAD_INTEGRITY, KRB_AP_ERR_INAPP_CKSUM, KRB_AP_ERR_MODIFIED, KRB_AP_ERR_MSG_TYPE,
44    KRB_AP_ERR_REPEAT, KRB_AP_ERR_SKEW, KRB_AP_ERR_TKT_EXPIRED, KRB_AP_ERR_TKT_NYV,
45    KRB_ERR_GENERIC, PA_ENC_TIMESTAMP, PA_ETYPE_INFO2, PA_TGS_REQ, SUPPORTED_ETYPES,
46};
47
48/// Remembered pre-authentication timestamps and TGS authenticators. Only requests that already
49/// decrypted correctly are recorded, so a peer without a key cannot fill it.
50pub const MAX_REPLAY_ENTRIES: usize = 65_536;
51
52/// Most principals the operator may configure.
53pub const MAX_PRINCIPALS: usize = 1024;
54
55/// Most enctypes listed in an event; a request may list more, and they are still considered.
56const MAX_ETYPES_SHOWN: usize = 16;
57
58/// Key version number of every key this KDC holds. Keys come from configuration each start, so
59/// there is only ever one version.
60pub const KVNO: i64 = 1;
61
62/// The ticket-granting service's keys are random for the life of the server: a TGT does not
63/// survive a restart, and no key the operator typed protects every ticket.
64const KRBTGT: &str = "krbtgt";
65
66/// A configured principal: its keys (one per supported enctype) and how they were salted.
67pub struct PrincipalEntry {
68    pub principal: Principal,
69    keys: Vec<Key>,
70    pub salt: String,
71    pub require_preauth: bool,
72}
73
74impl PrincipalEntry {
75    fn key(&self, etype: i32) -> Option<&Key> {
76        self.keys.iter().find(|k| k.etype() == etype)
77    }
78
79    /// The strongest key, which encrypts tickets for this principal.
80    fn best_key(&self) -> &Key {
81        self.key(ETYPE_AES256).unwrap_or(&self.keys[0])
82    }
83}
84
85/// Operator configuration, with every key already derived. Not `Debug`.
86pub struct KdcConfig {
87    pub realm: String,
88    principals: HashMap<Vec<String>, PrincipalEntry>,
89    pub max_ticket_lifetime: i64,
90    pub max_renewable_lifetime: i64,
91    pub max_skew: i64,
92}
93
94impl KdcConfig {
95    /// Derive every key. `passwords` maps a principal name (`alice`, `host/web.example.com`,
96    /// optionally with `@REALM`) to its password; `no_preauth` lists principals that may be
97    /// issued a ticket without pre-authentication.
98    pub fn new(
99        realm: &str,
100        passwords: &[(String, String)],
101        no_preauth: &[String],
102        max_ticket_lifetime: i64,
103        max_renewable_lifetime: i64,
104        max_skew: i64,
105    ) -> Result<Self> {
106        codec::check_realm(realm)?;
107        if passwords.is_empty() {
108            return Err(anyhow!(
109                "principal_passwords names no principal; a KDC with no principals can issue \
110                 nothing"
111            ));
112        }
113        if passwords.len() > MAX_PRINCIPALS {
114            return Err(anyhow!(
115                "at most {MAX_PRINCIPALS} principals may be configured"
116            ));
117        }
118        let mut principals = HashMap::new();
119        for (name, password) in passwords {
120            let (principal, named_realm) =
121                Principal::parse(name).with_context(|| format!("principal '{name}'"))?;
122            if let Some(r) = named_realm {
123                if r != realm {
124                    return Err(anyhow!(
125                        "principal '{name}' names realm {r}, but this KDC serves {realm}"
126                    ));
127                }
128            }
129            if principal.components.first().map(String::as_str) == Some(KRBTGT) {
130                return Err(anyhow!(
131                    "'{name}' is reserved: the ticket-granting service's key is generated at \
132                     startup and never configured"
133                ));
134            }
135            if password.is_empty() {
136                return Err(anyhow!("principal '{name}' has an empty password"));
137            }
138            let salt = principal.default_salt(realm);
139            let keys = SUPPORTED_ETYPES
140                .iter()
141                .map(|e| Key::from_password(*e, password, &salt))
142                .collect::<Result<Vec<_>>>()?;
143            let require_preauth = !no_preauth.iter().any(|n| {
144                Principal::parse(n)
145                    .map(|(p, _)| p.same_name(&principal))
146                    .unwrap_or(false)
147            });
148            if principals
149                .insert(
150                    principal.components.clone(),
151                    PrincipalEntry {
152                        principal,
153                        keys,
154                        salt,
155                        require_preauth,
156                    },
157                )
158                .is_some()
159            {
160                return Err(anyhow!("principal '{name}' is configured twice"));
161            }
162        }
163        for n in no_preauth {
164            let (p, _) =
165                Principal::parse(n).with_context(|| format!("no_preauth principal '{n}'"))?;
166            if !principals.contains_key(&p.components) {
167                return Err(anyhow!(
168                    "no_preauth_principals names '{n}', which principal_passwords does not"
169                ));
170            }
171        }
172        let tgs = Principal::krbtgt(realm);
173        let keys = SUPPORTED_ETYPES
174            .iter()
175            .map(|e| Key::random(*e))
176            .collect::<Result<Vec<_>>>()?;
177        principals.insert(
178            tgs.components.clone(),
179            PrincipalEntry {
180                salt: tgs.default_salt(realm),
181                principal: tgs,
182                keys,
183                require_preauth: true,
184            },
185        );
186        Ok(Self {
187            realm: realm.to_string(),
188            principals,
189            max_ticket_lifetime,
190            max_renewable_lifetime,
191            max_skew,
192        })
193    }
194
195    fn entry(&self, p: &Principal) -> Option<&PrincipalEntry> {
196        self.principals.get(&p.components)
197    }
198
199    /// Every configured principal's name, for the startup log. Names only.
200    pub fn principal_names(&self) -> Vec<String> {
201        let mut v: Vec<String> = self
202            .principals
203            .values()
204            .map(|e| e.principal.to_string())
205            .collect();
206        v.sort();
207        v
208    }
209}
210
211/// Which exchange a pending request belongs to.
212#[derive(Debug, Clone, Copy, PartialEq, Eq)]
213pub enum Exchange {
214    As,
215    Tgs,
216}
217
218/// What the TGT presented with a TGS-REQ established.
219#[derive(Clone)]
220pub struct TgtFacts {
221    pub flags: u32,
222    pub authtime: i64,
223    pub endtime: i64,
224    pub renew_till: Option<i64>,
225}
226
227/// A request whose cryptography checked out, waiting for the model's policy decision.
228/// Holds the key the reply will be encrypted in, so it is not `Debug`.
229pub struct Pending {
230    pub exchange: Exchange,
231    pub client: Principal,
232    pub client_realm: String,
233    pub client_known: bool,
234    pub service: Principal,
235    pub service_known: bool,
236    pub options: u32,
237    /// The client's requested end time; `None` means "as long as allowed" (RFC 4120's zero).
238    pub till: Option<i64>,
239    pub rtime: Option<i64>,
240    pub nonce: IntegerAsn1,
241    /// The client's enctypes, in its order.
242    pub etypes: Vec<i32>,
243    pub addresses: Option<HostAddresses>,
244    /// The enctype a verified PA-ENC-TIMESTAMP was made with.
245    pub preauth_etype: Option<i32>,
246    /// TGS only: the key and usage the reply's encrypted part uses (subkey or session key).
247    reply_key: Option<(Key, i32)>,
248    pub tgt: Option<TgtFacts>,
249    pub source: SocketAddr,
250    pub transport: &'static str,
251}
252
253impl Pending {
254    pub fn preauth_verified(&self) -> bool {
255        self.preauth_etype.is_some()
256    }
257
258    /// The first enctype the client listed that this KDC supports.
259    fn session_etype(&self) -> Option<i32> {
260        self.etypes
261            .iter()
262            .copied()
263            .find(|e| SUPPORTED_ETYPES.contains(e))
264    }
265}
266
267/// A reply produced without asking the model.
268pub struct Mechanical {
269    pub reply: Vec<u8>,
270    /// Stable `decision=` token for the log.
271    pub decision: &'static str,
272    pub detail: String,
273}
274
275/// What [`Kdc::analyze`] concluded.
276pub enum Analysis {
277    /// Answered by Rust; the model is not asked.
278    Answer(Mechanical),
279    /// A policy question for the model.
280    Ask(Box<Pending>),
281    /// Not a request; a KDC does not answer replies.
282    Ignore(String),
283}
284
285/// What the model granted with `kerberos_issue_ticket`. Every field narrows; none widens past
286/// what the request asked for and the configuration allows.
287#[derive(Debug, Clone, Default)]
288pub struct Grant {
289    pub lifetime_secs: Option<i64>,
290    pub renewable_lifetime_secs: Option<i64>,
291    pub renewable: bool,
292    pub forwardable: bool,
293    pub proxiable: bool,
294    pub ok_as_delegate: bool,
295}
296
297/// The outcome of [`Kdc::issue`].
298pub enum Issued {
299    /// A ticket, and what it carries (for the log and the access record).
300    Ticket {
301        reply: Vec<u8>,
302        summary: Value,
303        clamped: bool,
304    },
305    /// Rust refused although the model said issue: what is missing is not policy.
306    Enforced {
307        reply: Vec<u8>,
308        decision: &'static str,
309        detail: String,
310    },
311}
312
313#[derive(Default)]
314struct ReplayCache {
315    seen: HashMap<(String, i64, i64), i64>,
316}
317
318impl ReplayCache {
319    /// Record `key`, expiring at `expiry`. `false` when it was already there (a replay) or the
320    /// cache is full of live entries.
321    fn insert(&mut self, key: (String, i64, i64), expiry: i64, now: i64) -> bool {
322        if self.seen.contains_key(&key) {
323            return false;
324        }
325        if self.seen.len() >= MAX_REPLAY_ENTRIES {
326            self.seen.retain(|_, exp| *exp > now);
327            if self.seen.len() >= MAX_REPLAY_ENTRIES {
328                return false;
329            }
330        }
331        self.seen.insert(key, expiry);
332        true
333    }
334}
335
336/// The KDC: configuration plus the replay cache.
337pub struct Kdc {
338    pub config: KdcConfig,
339    replay: Mutex<ReplayCache>,
340}
341
342/// Build a mechanical answer.
343fn mech(reply: Vec<u8>, decision: &'static str, detail: impl Into<String>) -> Analysis {
344    Analysis::Answer(Mechanical {
345        reply,
346        decision,
347        detail: detail.into(),
348    })
349}
350
351fn opt_time(t: &picky_krb::data_types::KerberosTime) -> Option<i64> {
352    // RFC 4120 §5.4.1: a `till` of 19700101000000Z means "as late as allowed".
353    unix(t).filter(|s| *s > 0)
354}
355
356impl Kdc {
357    pub fn new(config: KdcConfig) -> Self {
358        Self {
359            config,
360            replay: Mutex::new(ReplayCache::default()),
361        }
362    }
363
364    // -----------------------------------------------------------------------------------------
365    // KRB-ERROR
366    // -----------------------------------------------------------------------------------------
367
368    /// A KRB-ERROR. `sname` defaults to the TGS; `client` adds crealm/cname.
369    pub fn error(
370        &self,
371        code: i32,
372        sname: Option<&Principal>,
373        client: Option<(&Principal, &str)>,
374        e_text: Option<&str>,
375        e_data: Option<Vec<u8>>,
376    ) -> Vec<u8> {
377        match self.try_error(code, sname, client, e_text, e_data) {
378            Ok(v) => v,
379            // Only a name that is not IA5 could make this fail, and every name reaching here
380            // was validated. If it ever did, answer the bare error rather than nothing.
381            Err(_) => self
382                .try_error(code, None, None, None, None)
383                .unwrap_or_default(),
384        }
385    }
386
387    fn try_error(
388        &self,
389        code: i32,
390        sname: Option<&Principal>,
391        client: Option<(&Principal, &str)>,
392        e_text: Option<&str>,
393        e_data: Option<Vec<u8>>,
394    ) -> Result<Vec<u8>> {
395        let realm = &self.config.realm;
396        let sname = match sname {
397            Some(s) => s.clone(),
398            None => Principal::krbtgt(realm),
399        };
400        let (crealm, cname) = match client {
401            Some((p, r)) => (
402                Optional(Some(ExplicitContextTag7(kstring(r)?))),
403                Optional(Some(ExplicitContextTag8(p.to_asn1()?))),
404            ),
405            None => (Optional(None), Optional(None)),
406        };
407        let e_text = match e_text {
408            Some(t) => Optional(Some(ExplicitContextTag11(kstring(t)?))),
409            None => Optional(None),
410        };
411        let error = KrbError::from(KrbErrorInner {
412            pvno: ExplicitContextTag0(int(5)),
413            msg_type: ExplicitContextTag1(int(30)),
414            ctime: Optional(None),
415            cusec: Optional(None),
416            stime: ExplicitContextTag4(codec::ktime(now_secs())),
417            susec: ExplicitContextTag5(int(0)),
418            error_code: ExplicitContextTag6(code as u32),
419            crealm,
420            cname,
421            realm: ExplicitContextTag9(kstring(realm)?),
422            sname: ExplicitContextTag10(sname.to_asn1()?),
423            e_text,
424            e_data: Optional(e_data.map(|d| ExplicitContextTag12(OctetStringAsn1(d)))),
425        });
426        encode(&error)
427    }
428
429    fn salt_for(&self, client: &Principal) -> String {
430        match self.config.entry(client) {
431            Some(e) => e.salt.clone(),
432            None => client.default_salt(&self.config.realm),
433        }
434    }
435
436    // -----------------------------------------------------------------------------------------
437    // Analysis
438    // -----------------------------------------------------------------------------------------
439
440    /// Read one request. See the module docs.
441    pub fn analyze(&self, message: &[u8], source: SocketAddr, transport: &'static str) -> Analysis {
442        let now = now_secs();
443        match message.first() {
444            Some(&codec::TAG_AS_REQ) => self.analyze_as(message, source, transport, now),
445            Some(&codec::TAG_TGS_REQ) => self.analyze_tgs(message, source, transport, now),
446            Some(&tag)
447                if matches!(
448                    tag,
449                    codec::TAG_AS_REP
450                        | codec::TAG_TGS_REP
451                        | codec::TAG_KRB_ERROR
452                        | codec::TAG_AP_REQ
453                ) =>
454            {
455                Analysis::Ignore(format!("message tag 0x{tag:02x} is not a KDC request"))
456            }
457            _ => mech(
458                self.error(KRB_AP_ERR_MSG_TYPE, None, None, None, None),
459                "malformed",
460                "not an AS-REQ or TGS-REQ",
461            ),
462        }
463    }
464
465    /// The checks every KDC-REQ shares: version, realm, options, enctypes, times.
466    fn common(&self, req: &KdcReq, msg_type: i64) -> std::result::Result<Common, Analysis> {
467        let malformed = |detail: &str| {
468            mech(
469                self.error(KRB_ERR_GENERIC, None, None, Some("malformed request"), None),
470                "malformed",
471                detail.to_string(),
472            )
473        };
474        if int_i64(&req.pvno.0) != Some(5) {
475            return Err(mech(
476                self.error(3, None, None, None, None),
477                "malformed",
478                "protocol version is not 5",
479            ));
480        }
481        if int_i64(&req.msg_type.0) != Some(msg_type) {
482            return Err(malformed("msg-type does not match the application tag"));
483        }
484        let body = &req.req_body.0;
485        let realm = kstring_text(&body.realm.0);
486        if codec::check_realm(&realm).is_err() {
487            return Err(malformed("realm is not a printable name"));
488        }
489        let sname = match &body.sname.0 {
490            Some(s) => match Principal::from_asn1(&s.0) {
491                Ok(p) => p,
492                Err(e) => {
493                    return Err(mech(
494                        self.error(
495                            KDC_ERR_S_PRINCIPAL_UNKNOWN,
496                            None,
497                            None,
498                            Some("malformed service name"),
499                            None,
500                        ),
501                        "malformed",
502                        format!("service name: {e}"),
503                    ))
504                }
505            },
506            None => {
507                return Err(mech(
508                    self.error(KDC_ERR_S_PRINCIPAL_UNKNOWN, None, None, None, None),
509                    "malformed",
510                    "no service name",
511                ))
512            }
513        };
514        if realm != self.config.realm {
515            return Err(mech(
516                self.error(KDC_ERR_WRONG_REALM, Some(&sname), None, None, None),
517                "wrong_realm",
518                format!(
519                    "request is for realm '{}'",
520                    crate::utils::sanitize::line_field(&realm)
521                ),
522            ));
523        }
524        let options = flags_u32(&body.kdc_options.0);
525        if options & kdc_options::UNSUPPORTED != 0 {
526            return Err(mech(
527                self.error(
528                    KDC_ERR_BADOPTION,
529                    Some(&sname),
530                    None,
531                    Some("this KDC does not implement the requested options"),
532                    None,
533                ),
534                "unsupported_option",
535                format!(
536                    "options {:?}",
537                    flag_names(options & kdc_options::UNSUPPORTED, kdc_options::NAMES)
538                ),
539            ));
540        }
541        let etypes: Vec<i32> = body
542            .etype
543            .0
544             .0
545            .iter()
546            .filter_map(|e| int_i64(e).and_then(|v| i32::try_from(v).ok()))
547            .collect();
548        if !etypes.iter().any(|e| SUPPORTED_ETYPES.contains(e)) {
549            return Err(mech(
550                self.error(KDC_ERR_ETYPE_NOSUPP, Some(&sname), None, None, None),
551                "etype_nosupp",
552                format!(
553                    "client offered {:?}; this KDC speaks aes256/aes128-cts-hmac-sha1-96 only",
554                    etypes.iter().map(|e| etype_name(*e)).collect::<Vec<_>>()
555                ),
556            ));
557        }
558        let till = opt_time(&body.till.0);
559        let rtime = body.rtime.0.as_ref().and_then(|t| opt_time(&t.0));
560        Ok(Common {
561            sname,
562            options,
563            etypes,
564            till,
565            rtime,
566        })
567    }
568
569    fn analyze_as(
570        &self,
571        message: &[u8],
572        source: SocketAddr,
573        transport: &'static str,
574        now: i64,
575    ) -> Analysis {
576        let req: AsReq = match decode(message) {
577            Ok(r) => r,
578            Err(e) => {
579                return mech(
580                    self.error(KRB_ERR_GENERIC, None, None, Some("malformed request"), None),
581                    "malformed",
582                    format!("AS-REQ: {e}"),
583                )
584            }
585        };
586        let req = req.0;
587        let common = match self.common(&req, 10) {
588            Ok(c) => c,
589            Err(a) => return a,
590        };
591        let body = &req.req_body.0;
592        let client = match body.cname.0.as_ref().map(|c| Principal::from_asn1(&c.0)) {
593            Some(Ok(p)) => p,
594            Some(Err(e)) => {
595                return mech(
596                    self.error(
597                        KDC_ERR_C_PRINCIPAL_UNKNOWN,
598                        Some(&common.sname),
599                        None,
600                        Some("malformed client name"),
601                        None,
602                    ),
603                    "malformed",
604                    format!("client name: {e}"),
605                )
606            }
607            None => {
608                return mech(
609                    self.error(
610                        KDC_ERR_C_PRINCIPAL_UNKNOWN,
611                        Some(&common.sname),
612                        None,
613                        None,
614                        None,
615                    ),
616                    "malformed",
617                    "AS-REQ without a client name",
618                )
619            }
620        };
621        let realm = self.config.realm.clone();
622        let client_entry = self.config.entry(&client);
623        let service_known = self.config.entry(&common.sname).is_some();
624
625        // Pre-authentication, verified here and nowhere else.
626        let padata: &[PaData] = req
627            .padata
628            .0
629            .as_ref()
630            .map(|p| p.0 .0.as_slice())
631            .unwrap_or(&[]);
632        let mut preauth_etype = None;
633        if let Some(pa) = padata
634            .iter()
635            .find(|p| codec::padata_type(p) == PA_ENC_TIMESTAMP as i64)
636        {
637            let refuse = |code: i32, decision: &'static str, detail: String| {
638                mech(
639                    self.error(
640                        code,
641                        Some(&common.sname),
642                        Some((&client, &realm)),
643                        None,
644                        None,
645                    ),
646                    decision,
647                    detail,
648                )
649            };
650            let Some(entry) = client_entry else {
651                // No key to check it with. Answered as a failed pre-authentication so that a
652                // model that hides which principals exist (by requiring pre-authentication of
653                // everyone) is not undone here.
654                return refuse(
655                    KDC_ERR_PREAUTH_FAILED,
656                    "preauth_failed",
657                    format!("{client}: no such principal, so its timestamp cannot verify"),
658                );
659            };
660            let enc: EncryptedData = match decode(&pa.padata_data.0 .0) {
661                Ok(e) => e,
662                Err(e) => {
663                    return refuse(
664                        KDC_ERR_PREAUTH_FAILED,
665                        "preauth_failed",
666                        format!("{client}: PA-ENC-TIMESTAMP is not an EncryptedData: {e}"),
667                    )
668                }
669            };
670            let etype = int_i64(&enc.etype.0)
671                .and_then(|v| i32::try_from(v).ok())
672                .unwrap_or(-1);
673            let Some(key) = entry.key(etype) else {
674                return refuse(
675                    KDC_ERR_ETYPE_NOSUPP,
676                    "etype_nosupp",
677                    format!("{client}: timestamp encrypted with {}", etype_name(etype)),
678                );
679            };
680            let plain = match key.decrypt_data(codec::KU_PA_ENC_TIMESTAMP, &enc) {
681                Ok(p) => p,
682                Err(e) => {
683                    return refuse(
684                        KDC_ERR_PREAUTH_FAILED,
685                        "preauth_failed",
686                        format!("{client}: encrypted timestamp did not verify ({e})"),
687                    )
688                }
689            };
690            let ts: PaEncTsEnc = match decode(&plain) {
691                Ok(t) => t,
692                Err(e) => {
693                    return refuse(
694                        KDC_ERR_PREAUTH_FAILED,
695                        "preauth_failed",
696                        format!("{client}: decrypted timestamp is malformed: {e}"),
697                    )
698                }
699            };
700            let Some(stamp) = unix(&ts.patimestamp.0) else {
701                return refuse(
702                    KDC_ERR_PREAUTH_FAILED,
703                    "preauth_failed",
704                    format!("{client}: timestamp is not a date"),
705                );
706            };
707            if (stamp - now).abs() > self.config.max_skew {
708                return refuse(
709                    KRB_AP_ERR_SKEW,
710                    "preauth_skew",
711                    format!(
712                        "{client}: timestamp is {}s from this KDC's clock",
713                        stamp - now
714                    ),
715                );
716            }
717            let usec = ts
718                .pausec
719                .0
720                .as_ref()
721                .and_then(|u| int_i64(&u.0))
722                .unwrap_or(0);
723            let fresh = self
724                .replay
725                .lock()
726                .map(|mut c| {
727                    c.insert(
728                        (format!("as:{client}"), stamp, usec),
729                        now + 2 * self.config.max_skew,
730                        now,
731                    )
732                })
733                .unwrap_or(false);
734            if !fresh {
735                return refuse(
736                    KRB_AP_ERR_REPEAT,
737                    "preauth_replay",
738                    format!("{client}: this pre-authentication timestamp was already used"),
739                );
740            }
741            preauth_etype = Some(etype);
742        }
743
744        Analysis::Ask(Box::new(Pending {
745            exchange: Exchange::As,
746            client_known: client_entry.is_some(),
747            client,
748            client_realm: realm,
749            service: common.sname,
750            service_known,
751            options: common.options,
752            till: common.till,
753            rtime: common.rtime,
754            nonce: body.nonce.0.clone(),
755            etypes: common.etypes,
756            addresses: body.addresses.0.as_ref().map(|a| a.0.clone()),
757            preauth_etype,
758            reply_key: None,
759            tgt: None,
760            source,
761            transport,
762        }))
763    }
764
765    fn analyze_tgs(
766        &self,
767        message: &[u8],
768        source: SocketAddr,
769        transport: &'static str,
770        now: i64,
771    ) -> Analysis {
772        let req: TgsReq = match decode(message) {
773            Ok(r) => r,
774            Err(e) => {
775                return mech(
776                    self.error(KRB_ERR_GENERIC, None, None, Some("malformed request"), None),
777                    "malformed",
778                    format!("TGS-REQ: {e}"),
779                )
780            }
781        };
782        let req = req.0;
783        let common = match self.common(&req, 12) {
784            Ok(c) => c,
785            Err(a) => return a,
786        };
787        let sname = common.sname.clone();
788        let fail = |code: i32, decision: &'static str, detail: String| {
789            mech(
790                self.error(code, Some(&sname), None, None, None),
791                decision,
792                detail,
793            )
794        };
795        let padata: &[PaData] = req
796            .padata
797            .0
798            .as_ref()
799            .map(|p| p.0 .0.as_slice())
800            .unwrap_or(&[]);
801        let Some(pa) = padata
802            .iter()
803            .find(|p| codec::padata_type(p) == PA_TGS_REQ as i64)
804        else {
805            return fail(
806                KDC_ERR_PADATA_TYPE_NOSUPP,
807                "malformed",
808                "TGS-REQ without PA-TGS-REQ".into(),
809            );
810        };
811        let ap_bytes = &pa.padata_data.0 .0;
812        if ap_bytes.first() != Some(&codec::TAG_AP_REQ) {
813            return fail(
814                KRB_AP_ERR_MSG_TYPE,
815                "malformed",
816                "PA-TGS-REQ is not an AP-REQ".into(),
817            );
818        }
819        let ap: ApReq = match decode(ap_bytes) {
820            Ok(a) => a,
821            Err(e) => return fail(KRB_AP_ERR_MSG_TYPE, "malformed", format!("AP-REQ: {e}")),
822        };
823        let ap = ap.0;
824        let ticket = &ap.ticket.0 .0;
825        let tkt_sname = match Principal::from_asn1(&ticket.sname.0) {
826            Ok(p) => p,
827            Err(e) => {
828                return fail(
829                    KRB_AP_ERR_BAD_INTEGRITY,
830                    "tgt_invalid",
831                    format!("TGT sname: {e}"),
832                )
833            }
834        };
835        if kstring_text(&ticket.realm.0) != self.config.realm
836            || !tkt_sname.is_krbtgt(&self.config.realm)
837        {
838            return fail(
839                35,
840                "tgt_not_ours",
841                format!("presented ticket is for {tkt_sname}, not this realm's TGS"),
842            );
843        }
844        let tgs = self
845            .config
846            .entry(&Principal::krbtgt(&self.config.realm))
847            .expect("the TGS entry is created with the configuration");
848        let tkt_etype = int_i64(&ticket.enc_part.0.etype.0)
849            .and_then(|v| i32::try_from(v).ok())
850            .unwrap_or(-1);
851        let Some(tgs_key) = tgs.key(tkt_etype) else {
852            return fail(
853                KRB_AP_ERR_BAD_INTEGRITY,
854                "tgt_invalid",
855                format!("TGT encrypted with {}", etype_name(tkt_etype)),
856            );
857        };
858        let enc_ticket: EncTicketPart = match tgs_key
859            .decrypt_data(codec::KU_TICKET, &ticket.enc_part.0)
860            .map_err(|e| anyhow!(e))
861            .and_then(|p| decode(&p))
862        {
863            Ok(t) => t,
864            Err(e) => {
865                return fail(
866                    KRB_AP_ERR_BAD_INTEGRITY,
867                    "tgt_integrity",
868                    format!("TGT did not decrypt with this KDC's key: {e}"),
869                )
870            }
871        };
872        let et = enc_ticket.0;
873        let client = match Principal::from_asn1(&et.cname.0) {
874            Ok(p) => p,
875            Err(e) => {
876                return fail(
877                    KRB_AP_ERR_BAD_INTEGRITY,
878                    "tgt_invalid",
879                    format!("TGT cname: {e}"),
880                )
881            }
882        };
883        let crealm = kstring_text(&et.crealm.0);
884        let fail_c = |code: i32, decision: &'static str, detail: String| {
885            mech(
886                self.error(code, Some(&sname), Some((&client, &crealm)), None, None),
887                decision,
888                detail,
889            )
890        };
891        let tgt_flags = flags_u32(&et.flags.0);
892        let (Some(authtime), Some(endtime)) = (unix(&et.auth_time.0), unix(&et.endtime.0)) else {
893            return fail_c(
894                KRB_AP_ERR_BAD_INTEGRITY,
895                "tgt_invalid",
896                "TGT times are not dates".into(),
897            );
898        };
899        if endtime + self.config.max_skew < now {
900            return fail_c(
901                KRB_AP_ERR_TKT_EXPIRED,
902                "tgt_expired",
903                format!("{client}: TGT expired"),
904            );
905        }
906        let start = et
907            .starttime
908            .0
909            .as_ref()
910            .and_then(|t| unix(&t.0))
911            .unwrap_or(authtime);
912        if start - self.config.max_skew > now || tgt_flags & ticket_flags::INVALID != 0 {
913            return fail_c(
914                KRB_AP_ERR_TKT_NYV,
915                "tgt_not_yet_valid",
916                format!("{client}: TGT not yet valid"),
917            );
918        }
919        let renew_till = et.renew_till.0.as_ref().and_then(|t| unix(&t.0));
920        let session = match Key::from_asn1(&et.key.0) {
921            Ok(k) => k,
922            Err(e) => {
923                return fail_c(
924                    KRB_AP_ERR_BAD_INTEGRITY,
925                    "tgt_invalid",
926                    format!("TGT key: {e}"),
927                )
928            }
929        };
930        let authenticator: Authenticator = match session
931            .decrypt_data(codec::KU_TGS_REQ_AUTHENTICATOR, &ap.authenticator.0)
932            .map_err(|e| anyhow!(e))
933            .and_then(|p| decode(&p))
934        {
935            Ok(a) => a,
936            Err(e) => {
937                return fail_c(
938                    KRB_AP_ERR_BAD_INTEGRITY,
939                    "authenticator_integrity",
940                    format!(
941                        "{client}: authenticator did not decrypt with the TGT session key: {e}"
942                    ),
943                )
944            }
945        };
946        let auth = authenticator.0;
947        let auth_client = Principal::from_asn1(&auth.cname.0).ok();
948        if auth_client.as_ref().map(|p| p.same_name(&client)) != Some(true)
949            || kstring_text(&auth.crealm.0) != crealm
950        {
951            return fail_c(
952                KRB_AP_ERR_BADMATCH,
953                "authenticator_mismatch",
954                format!("{client}: authenticator names a different client"),
955            );
956        }
957        let Some(ctime) = unix(&auth.ctime.0) else {
958            return fail_c(
959                KRB_AP_ERR_SKEW,
960                "authenticator_skew",
961                "authenticator time is not a date".into(),
962            );
963        };
964        if (ctime - now).abs() > self.config.max_skew {
965            return fail_c(
966                KRB_AP_ERR_SKEW,
967                "authenticator_skew",
968                format!(
969                    "{client}: authenticator is {}s from this KDC's clock",
970                    ctime - now
971                ),
972            );
973        }
974        // The checksum binds the authenticator to this request body. Without it a captured
975        // authenticator could be stapled to a different request within the skew window.
976        let Some(cksum) = auth.cksum.0.as_ref() else {
977            return fail_c(
978                KRB_AP_ERR_INAPP_CKSUM,
979                "checksum_missing",
980                format!("{client}: authenticator carries no checksum over the request body"),
981            );
982        };
983        let Some(body_bytes) = codec::req_body_bytes(message) else {
984            return fail_c(
985                KRB_ERR_GENERIC,
986                "malformed",
987                "request body could not be located".into(),
988            );
989        };
990        let cksumtype = int_i64(&cksum.0.cksumtype.0)
991            .and_then(|v| i32::try_from(v).ok())
992            .unwrap_or(-1);
993        if cksumtype != session.checksum_type() {
994            return fail_c(
995                KRB_AP_ERR_INAPP_CKSUM,
996                "checksum_type",
997                format!("{client}: checksum type {cksumtype} is not keyed with the session key"),
998            );
999        }
1000        if !session.verify_checksum(
1001            cksumtype,
1002            codec::KU_TGS_REQ_AUTH_CKSUM,
1003            body_bytes,
1004            &cksum.0.checksum.0 .0,
1005        ) {
1006            return fail_c(
1007                KRB_AP_ERR_MODIFIED,
1008                "checksum_mismatch",
1009                format!("{client}: request body does not match the authenticator's checksum"),
1010            );
1011        }
1012        let cusec = int_i64(&auth.cusec.0).unwrap_or(0);
1013        let fresh = self
1014            .replay
1015            .lock()
1016            .map(|mut c| {
1017                c.insert(
1018                    (format!("tgs:{client}@{crealm}"), ctime, cusec),
1019                    now + 2 * self.config.max_skew,
1020                    now,
1021                )
1022            })
1023            .unwrap_or(false);
1024        if !fresh {
1025            return fail_c(
1026                KRB_AP_ERR_REPEAT,
1027                "authenticator_replay",
1028                format!("{client}: authenticator already used"),
1029            );
1030        }
1031        let reply_key = match auth.subkey.0.as_ref() {
1032            Some(sk) => match Key::from_asn1(&sk.0) {
1033                Ok(k) => (k, codec::KU_TGS_REP_ENC_SUBKEY),
1034                Err(e) => {
1035                    return fail_c(
1036                        KDC_ERR_ETYPE_NOSUPP,
1037                        "etype_nosupp",
1038                        format!("{client}: authenticator subkey: {e}"),
1039                    )
1040                }
1041            },
1042            None => (session.clone(), codec::KU_TGS_REP_ENC_SESSION_KEY),
1043        };
1044        let body = &req.req_body.0;
1045        Analysis::Ask(Box::new(Pending {
1046            exchange: Exchange::Tgs,
1047            client_known: crealm == self.config.realm && self.config.entry(&client).is_some(),
1048            client,
1049            client_realm: crealm,
1050            service_known: self.config.entry(&common.sname).is_some(),
1051            service: common.sname,
1052            options: common.options,
1053            till: common.till,
1054            rtime: common.rtime,
1055            nonce: body.nonce.0.clone(),
1056            etypes: common.etypes,
1057            addresses: body.addresses.0.as_ref().map(|a| a.0.clone()),
1058            preauth_etype: None,
1059            reply_key: Some(reply_key),
1060            tgt: Some(TgtFacts {
1061                flags: tgt_flags,
1062                authtime,
1063                endtime,
1064                renew_till,
1065            }),
1066            source,
1067            transport,
1068        }))
1069    }
1070
1071    // -----------------------------------------------------------------------------------------
1072    // What the model is shown
1073    // -----------------------------------------------------------------------------------------
1074
1075    /// The event payload for a pending request. Names, times, options and facts; never a key.
1076    pub fn event_data(&self, p: &Pending) -> Value {
1077        let now = now_secs();
1078        let span = |t: Option<i64>| t.map(|t| t - now);
1079        let mut data = json!({
1080            "client": p.client.to_string(),
1081            "client_realm": p.client_realm,
1082            "client_known": p.client_known,
1083            "service": p.service.to_string(),
1084            "service_known": p.service_known,
1085            "service_is_tgs": p.service.is_krbtgt(&self.config.realm),
1086            "requested_lifetime_secs": span(p.till),
1087            "requested_renewable_lifetime_secs": span(p.rtime),
1088            "options": flag_names(p.options, kdc_options::NAMES),
1089            "etypes": p.etypes.iter().take(MAX_ETYPES_SHOWN).map(|e| etype_name(*e)).collect::<Vec<_>>(),
1090            "addresses": p.addresses.as_ref().map(addresses_json).unwrap_or_default(),
1091            "source_addr": p.source.to_string(),
1092            "transport": p.transport,
1093            "max_ticket_lifetime_secs": self.config.max_ticket_lifetime,
1094            "max_renewable_lifetime_secs": self.config.max_renewable_lifetime,
1095        });
1096        match p.exchange {
1097            Exchange::As => {
1098                data["preauth"] = json!(if p.preauth_verified() {
1099                    "verified"
1100                } else {
1101                    "none"
1102                });
1103                let required = self
1104                    .config
1105                    .entry(&p.client)
1106                    .map(|e| e.require_preauth)
1107                    .unwrap_or(true);
1108                data["preauth_required_by_config"] = json!(required);
1109            }
1110            Exchange::Tgs => {
1111                if let Some(t) = &p.tgt {
1112                    data["tgt_flags"] = json!(flag_names(t.flags, ticket_flags::NAMES));
1113                    data["tgt_expires_in_secs"] = json!(t.endtime - now);
1114                    data["tgt_authtime"] = json!(rfc3339(t.authtime));
1115                    data["tgt_renew_till"] = json!(t.renew_till.map(rfc3339));
1116                }
1117            }
1118        }
1119        data
1120    }
1121
1122    // -----------------------------------------------------------------------------------------
1123    // The three answers
1124    // -----------------------------------------------------------------------------------------
1125
1126    /// KDC_ERR_PREAUTH_REQUIRED with METHOD-DATA naming PA-ENC-TIMESTAMP and an ETYPE-INFO2
1127    /// entry, with salt, for each enctype the client offered that this KDC speaks. Answered for
1128    /// an unknown principal too, if that is what the model chose, with the salt it would have.
1129    pub fn preauth_required(&self, p: &Pending) -> Result<Vec<u8>> {
1130        if p.exchange != Exchange::As {
1131            return Err(anyhow!(
1132                "pre-authentication applies to the AS exchange only"
1133            ));
1134        }
1135        let salt = self.salt_for(&p.client);
1136        let entries: Vec<(i32, String)> = p
1137            .etypes
1138            .iter()
1139            .copied()
1140            .filter(|e| SUPPORTED_ETYPES.contains(e))
1141            .map(|e| (e, salt.clone()))
1142            .collect();
1143        let method_data = Asn1SequenceOf(vec![
1144            codec::padata(PA_ENC_TIMESTAMP, Vec::new()),
1145            codec::padata(PA_ETYPE_INFO2, codec::etype_info2(&entries)?),
1146        ]);
1147        Ok(self.error(
1148            KDC_ERR_PREAUTH_REQUIRED,
1149            Some(&p.service),
1150            Some((&p.client, &p.client_realm)),
1151            None,
1152            Some(encode(&method_data)?),
1153        ))
1154    }
1155
1156    /// The KRB-ERROR the model chose. `code` has been checked against the model's vocabulary.
1157    pub fn refuse(&self, p: &Pending, code: i32, text: Option<&str>) -> Vec<u8> {
1158        let text = text.map(crate::utils::sanitize::line_field);
1159        self.error(
1160            code,
1161            Some(&p.service),
1162            Some((&p.client, &p.client_realm)),
1163            text.as_deref(),
1164            None,
1165        )
1166    }
1167
1168    /// The answer when no decision was produced. Never a ticket.
1169    pub fn fail_closed(&self, p: &Pending, code: i32, text: &str) -> Vec<u8> {
1170        self.error(
1171            code,
1172            Some(&p.service),
1173            Some((&p.client, &p.client_realm)),
1174            Some(text),
1175            None,
1176        )
1177    }
1178
1179    /// Issue the ticket the model granted, within every bound that is not the model's to move.
1180    pub fn issue(&self, p: &Pending, grant: &Grant) -> Result<Issued> {
1181        let enforced = |code: i32, decision: &'static str, detail: String| -> Result<Issued> {
1182            Ok(Issued::Enforced {
1183                reply: self.error(
1184                    code,
1185                    Some(&p.service),
1186                    Some((&p.client, &p.client_realm)),
1187                    None,
1188                    None,
1189                ),
1190                decision,
1191                detail,
1192            })
1193        };
1194        let now = now_secs();
1195        let config = &self.config;
1196
1197        // Who: both ends need a key, and the AS exchange needs pre-authentication where the
1198        // principal requires it.
1199        let client_entry = config.entry(&p.client);
1200        if p.exchange == Exchange::As {
1201            let Some(entry) = client_entry else {
1202                return enforced(
1203                    KDC_ERR_C_PRINCIPAL_UNKNOWN,
1204                    "enforced_unknown_client",
1205                    format!(
1206                        "{}: no key is configured, so no ticket can be issued",
1207                        p.client
1208                    ),
1209                );
1210            };
1211            if entry.require_preauth && !p.preauth_verified() {
1212                return Ok(Issued::Enforced {
1213                    reply: self.preauth_required(p)?,
1214                    decision: "enforced_preauth_required",
1215                    detail: format!("{}: pre-authentication is required", p.client),
1216                });
1217            }
1218        }
1219        let Some(service_entry) = config.entry(&p.service) else {
1220            return enforced(
1221                KDC_ERR_S_PRINCIPAL_UNKNOWN,
1222                "enforced_unknown_service",
1223                format!("{}: no key is configured for this service", p.service),
1224            );
1225        };
1226        let Some(session_etype) = p.session_etype() else {
1227            return enforced(
1228                KDC_ERR_ETYPE_NOSUPP,
1229                "etype_nosupp",
1230                "no common enctype".into(),
1231            );
1232        };
1233
1234        // When: the shortest of the model's lifetime, the ceiling, the client's till and (TGS)
1235        // the TGT's own end.
1236        let mut clamped = false;
1237        let ceiling = config.max_ticket_lifetime;
1238        let model_life = match grant.lifetime_secs {
1239            Some(l) if l > ceiling => {
1240                clamped = true;
1241                ceiling
1242            }
1243            Some(l) => l,
1244            None => ceiling,
1245        };
1246        let mut end = now.saturating_add(model_life);
1247        if let Some(till) = p.till {
1248            end = end.min(till);
1249        }
1250        if let Some(tgt) = &p.tgt {
1251            end = end.min(tgt.endtime);
1252        }
1253        if end <= now {
1254            return enforced(
1255                KDC_ERR_NEVER_VALID,
1256                "enforced_never_valid",
1257                format!("{}: the ticket would end before it starts", p.client),
1258            );
1259        }
1260
1261        // Flags: what the model grants, intersected with what the client asked for and, in
1262        // the TGS exchange, what the TGT itself carries.
1263        let tgt_flags = p.tgt.as_ref().map(|t| t.flags);
1264        let tgt_allows = |f: u32| tgt_flags.map(|t| t & f != 0).unwrap_or(true);
1265        let mut tflags = 0u32;
1266        match p.exchange {
1267            Exchange::As => {
1268                tflags |= ticket_flags::INITIAL;
1269                if p.preauth_verified() {
1270                    tflags |= ticket_flags::PRE_AUTHENT;
1271                }
1272            }
1273            Exchange::Tgs => {
1274                if tgt_flags.unwrap_or(0) & ticket_flags::PRE_AUTHENT != 0 {
1275                    tflags |= ticket_flags::PRE_AUTHENT;
1276                }
1277            }
1278        }
1279        if grant.forwardable
1280            && p.options & kdc_options::FORWARDABLE != 0
1281            && tgt_allows(ticket_flags::FORWARDABLE)
1282        {
1283            tflags |= ticket_flags::FORWARDABLE;
1284        }
1285        if grant.proxiable
1286            && p.options & kdc_options::PROXIABLE != 0
1287            && tgt_allows(ticket_flags::PROXIABLE)
1288        {
1289            tflags |= ticket_flags::PROXIABLE;
1290        }
1291        if grant.ok_as_delegate && !p.service.is_krbtgt(&config.realm) {
1292            tflags |= ticket_flags::OK_AS_DELEGATE;
1293        }
1294        let mut renew_till = None;
1295        let wants_renewable = p.options & kdc_options::RENEWABLE != 0
1296            || (p.options & kdc_options::RENEWABLE_OK != 0
1297                && p.till.map(|t| t > end).unwrap_or(true));
1298        if grant.renewable && wants_renewable && tgt_allows(ticket_flags::RENEWABLE) {
1299            let rceiling = config.max_renewable_lifetime;
1300            let rlife = match grant.renewable_lifetime_secs {
1301                Some(l) if l > rceiling => {
1302                    clamped = true;
1303                    rceiling
1304                }
1305                Some(l) => l,
1306                None => rceiling,
1307            };
1308            let mut rtill = now.saturating_add(rlife);
1309            if p.options & kdc_options::RENEWABLE != 0 {
1310                if let Some(r) = p.rtime {
1311                    rtill = rtill.min(r);
1312                }
1313            } else if let Some(t) = p.till {
1314                rtill = rtill.min(t);
1315            }
1316            if let Some(t) = p.tgt.as_ref().and_then(|t| t.renew_till) {
1317                rtill = rtill.min(t);
1318            }
1319            if rtill > end {
1320                tflags |= ticket_flags::RENEWABLE;
1321                renew_till = Some(rtill);
1322            }
1323        }
1324
1325        let authtime = p.tgt.as_ref().map(|t| t.authtime).unwrap_or(now);
1326        let session = Key::random(session_etype)?;
1327        let realm = &config.realm;
1328        let cname = p.client.to_asn1()?;
1329        let crealm = kstring(&p.client_realm)?;
1330        let sname = p.service.to_asn1()?;
1331
1332        let enc_ticket = EncTicketPart::from(EncTicketPartInner {
1333            flags: ExplicitContextTag0(flags(tflags)),
1334            key: ExplicitContextTag1(session.to_asn1()),
1335            crealm: ExplicitContextTag2(crealm.clone()),
1336            cname: ExplicitContextTag3(cname.clone()),
1337            transited: ExplicitContextTag4(TransitedEncoding {
1338                tr_type: ExplicitContextTag0(int(1)),
1339                contents: ExplicitContextTag1(OctetStringAsn1(Vec::new())),
1340            }),
1341            auth_time: ExplicitContextTag5(codec::ktime(authtime)),
1342            starttime: Optional(Some(ExplicitContextTag6(codec::ktime(now)))),
1343            endtime: ExplicitContextTag7(codec::ktime(end)),
1344            renew_till: Optional(renew_till.map(|t| ExplicitContextTag8(codec::ktime(t)))),
1345            caddr: Optional(p.addresses.clone().map(ExplicitContextTag9)),
1346            authorization_data: Optional(None),
1347        });
1348        let ticket_key = service_entry.best_key();
1349        let ticket = Ticket::from(TicketInner {
1350            tkt_vno: ExplicitContextTag0(int(5)),
1351            realm: ExplicitContextTag1(kstring(realm)?),
1352            sname: ExplicitContextTag2(sname.clone()),
1353            enc_part: ExplicitContextTag3(ticket_key.encrypted_data(
1354                codec::KU_TICKET,
1355                &encode(&enc_ticket)?,
1356                Some(KVNO),
1357            )?),
1358        });
1359
1360        let rep_part = EncKdcRepPart {
1361            key: ExplicitContextTag0(session.to_asn1()),
1362            last_req: ExplicitContextTag1(Asn1SequenceOf(vec![LastReqInner {
1363                lr_type: ExplicitContextTag0(int(0)),
1364                lr_value: ExplicitContextTag1(codec::ktime(now)),
1365            }])),
1366            nonce: ExplicitContextTag2(p.nonce.clone()),
1367            key_expiration: Optional(None),
1368            flags: ExplicitContextTag4(flags(tflags)),
1369            auth_time: ExplicitContextTag5(codec::ktime(authtime)),
1370            start_time: Optional(Some(ExplicitContextTag6(codec::ktime(now)))),
1371            end_time: ExplicitContextTag7(codec::ktime(end)),
1372            renew_till: Optional(renew_till.map(|t| ExplicitContextTag8(codec::ktime(t)))),
1373            srealm: ExplicitContextTag9(kstring(realm)?),
1374            sname: ExplicitContextTag10(sname),
1375            caddr: Optional(p.addresses.clone().map(ExplicitContextTag11)),
1376            encrypted_pa_data: Optional(None),
1377        };
1378
1379        let (msg_type, padata, enc_part) = match p.exchange {
1380            Exchange::As => {
1381                let entry = client_entry.expect("checked above");
1382                // The reply key: the enctype the client pre-authenticated with, or else the
1383                // first of its enctypes this principal has a key for.
1384                let etype = p
1385                    .preauth_etype
1386                    .or_else(|| p.etypes.iter().copied().find(|e| entry.key(*e).is_some()));
1387                let Some(key) = etype.and_then(|e| entry.key(e)) else {
1388                    return enforced(KDC_ERR_ETYPE_NOSUPP, "etype_nosupp", "no client key".into());
1389                };
1390                let info = codec::etype_info2(&[(key.etype(), entry.salt.clone())])?;
1391                let enc = key.encrypted_data(
1392                    codec::KU_AS_REP_ENC_PART,
1393                    &encode(&EncAsRepPart::from(rep_part))?,
1394                    Some(KVNO),
1395                )?;
1396                (
1397                    11,
1398                    Optional(Some(ExplicitContextTag2(Asn1SequenceOf(vec![
1399                        codec::padata(PA_ETYPE_INFO2, info),
1400                    ])))),
1401                    enc,
1402                )
1403            }
1404            Exchange::Tgs => {
1405                let (key, usage) = p
1406                    .reply_key
1407                    .as_ref()
1408                    .context("a TGS request carries its reply key")?;
1409                let enc =
1410                    key.encrypted_data(*usage, &encode(&EncTgsRepPart::from(rep_part))?, None)?;
1411                (13, Optional(None), enc)
1412            }
1413        };
1414        let rep = KdcRep {
1415            pvno: ExplicitContextTag0(int(5)),
1416            msg_type: ExplicitContextTag1(int(msg_type)),
1417            padata,
1418            crealm: ExplicitContextTag3(crealm),
1419            cname: ExplicitContextTag4(cname),
1420            ticket: ExplicitContextTag5(ticket),
1421            enc_part: ExplicitContextTag6(enc_part),
1422        };
1423        let reply = match p.exchange {
1424            Exchange::As => encode(&AsRep::from(rep))?,
1425            Exchange::Tgs => encode(&TgsRep::from(rep))?,
1426        };
1427        let summary = json!({
1428            "client": format!("{}@{}", p.client, p.client_realm),
1429            "service": format!("{}@{}", p.service, realm),
1430            "starttime": rfc3339(now),
1431            "endtime": rfc3339(end),
1432            "lifetime_secs": end - now,
1433            "renew_till": renew_till.map(rfc3339),
1434            "flags": flag_names(tflags, ticket_flags::NAMES),
1435            "session_etype": etype_name(session_etype),
1436            "ticket_etype": etype_name(ticket_key.etype()),
1437        });
1438        Ok(Issued::Ticket {
1439            reply,
1440            summary,
1441            clamped,
1442        })
1443    }
1444}
1445
1446/// The checks shared by AS-REQ and TGS-REQ.
1447struct Common {
1448    sname: Principal,
1449    options: u32,
1450    etypes: Vec<i32>,
1451    till: Option<i64>,
1452    rtime: Option<i64>,
1453}
1454
1455/// Host addresses as text: dotted quads and IPv6 where the type says so, hex otherwise.
1456pub fn addresses_json(addrs: &HostAddresses) -> Vec<Value> {
1457    addrs
1458        .0
1459        .iter()
1460        .take(16)
1461        .map(|a| {
1462            let kind = int_i64(&a.addr_type.0).unwrap_or(-1);
1463            let bytes = &a.address.0 .0;
1464            match (kind, bytes.len()) {
1465                (2, 4) => json!(
1466                    std::net::Ipv4Addr::new(bytes[0], bytes[1], bytes[2], bytes[3]).to_string()
1467                ),
1468                (24, 16) => {
1469                    let mut b = [0u8; 16];
1470                    b.copy_from_slice(bytes);
1471                    json!(std::net::Ipv6Addr::from(b).to_string())
1472                }
1473                _ => json!({"type": kind, "hex": hex::encode(bytes)}),
1474            }
1475        })
1476        .collect()
1477}