1use 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
48pub const MAX_REPLAY_ENTRIES: usize = 65_536;
51
52pub const MAX_PRINCIPALS: usize = 1024;
54
55const MAX_ETYPES_SHOWN: usize = 16;
57
58pub const KVNO: i64 = 1;
61
62const KRBTGT: &str = "krbtgt";
65
66pub 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 fn best_key(&self) -> &Key {
81 self.key(ETYPE_AES256).unwrap_or(&self.keys[0])
82 }
83}
84
85pub 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 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 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
213pub enum Exchange {
214 As,
215 Tgs,
216}
217
218#[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
227pub 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 pub till: Option<i64>,
239 pub rtime: Option<i64>,
240 pub nonce: IntegerAsn1,
241 pub etypes: Vec<i32>,
243 pub addresses: Option<HostAddresses>,
244 pub preauth_etype: Option<i32>,
246 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 fn session_etype(&self) -> Option<i32> {
260 self.etypes
261 .iter()
262 .copied()
263 .find(|e| SUPPORTED_ETYPES.contains(e))
264 }
265}
266
267pub struct Mechanical {
269 pub reply: Vec<u8>,
270 pub decision: &'static str,
272 pub detail: String,
273}
274
275pub enum Analysis {
277 Answer(Mechanical),
279 Ask(Box<Pending>),
281 Ignore(String),
283}
284
285#[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
297pub enum Issued {
299 Ticket {
301 reply: Vec<u8>,
302 summary: Value,
303 clamped: bool,
304 },
305 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 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
336pub struct Kdc {
338 pub config: KdcConfig,
339 replay: Mutex<ReplayCache>,
340}
341
342fn 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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
1446struct Common {
1448 sname: Principal,
1449 options: u32,
1450 etypes: Vec<i32>,
1451 till: Option<i64>,
1452 rtime: Option<i64>,
1453}
1454
1455pub 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}