1use anyhow::{anyhow, Context, Result};
19use hmac::{Hmac, Mac};
20use picky_asn1::bit_string::BitString;
21use picky_asn1::date::Date;
22use picky_asn1::restricted_string::Ia5String;
23use picky_asn1::wrapper::{
24 Asn1SequenceOf, BitStringAsn1, ExplicitContextTag0, ExplicitContextTag1, ExplicitContextTag2,
25 GeneralStringAsn1, GeneralizedTimeAsn1, IntegerAsn1, OctetStringAsn1, Optional,
26};
27use picky_krb::crypto::{ChecksumSuite, CipherSuite};
28use picky_krb::data_types::{
29 EncryptedData, EtypeInfo2Entry, KerberosFlags, KerberosStringAsn1, KerberosTime, PaData,
30 PrincipalName,
31};
32use serde::de::DeserializeOwned;
33use serde::Serialize;
34
35pub mod types {
38 pub use picky_asn1::wrapper::*;
39 pub use picky_krb::data_types::*;
40 pub use picky_krb::messages::*;
41}
42
43pub const MAX_MESSAGE_LEN: usize = 64 * 1024;
47
48pub const MAX_DER_DEPTH: usize = 24;
52
53pub const MAX_DER_ELEMENTS: usize = 4096;
55
56pub const MAX_NAME_COMPONENTS: usize = 8;
58
59pub const MAX_NAME_LEN: usize = 255;
61
62pub const ETYPE_AES256: i32 = 18;
64pub const ETYPE_AES128: i32 = 17;
66pub const SUPPORTED_ETYPES: [i32; 2] = [ETYPE_AES256, ETYPE_AES128];
68
69pub const CKSUM_HMAC_SHA1_96_AES128: i32 = 15;
71pub const CKSUM_HMAC_SHA1_96_AES256: i32 = 16;
73
74pub const PA_TGS_REQ: i32 = 1;
76pub const PA_ENC_TIMESTAMP: i32 = 2;
77pub const PA_ETYPE_INFO2: i32 = 19;
78
79pub const KU_PA_ENC_TIMESTAMP: i32 = 1;
81pub const KU_TICKET: i32 = 2;
82pub const KU_AS_REP_ENC_PART: i32 = 3;
83pub const KU_TGS_REQ_AUTH_CKSUM: i32 = 6;
84pub const KU_TGS_REQ_AUTHENTICATOR: i32 = 7;
85pub const KU_TGS_REP_ENC_SESSION_KEY: i32 = 8;
86pub const KU_TGS_REP_ENC_SUBKEY: i32 = 9;
87pub const KU_AP_REQ_AUTHENTICATOR: i32 = 11;
88
89pub const TAG_AS_REQ: u8 = 0x6a;
91pub const TAG_AS_REP: u8 = 0x6b;
92pub const TAG_TGS_REQ: u8 = 0x6c;
93pub const TAG_TGS_REP: u8 = 0x6d;
94pub const TAG_AP_REQ: u8 = 0x6e;
95pub const TAG_KRB_ERROR: u8 = 0x7e;
96
97pub const NT_PRINCIPAL: i32 = 1;
99pub const NT_SRV_INST: i32 = 2;
100pub const NT_SRV_HST: i32 = 3;
101
102pub const fn bit(n: u32) -> u32 {
108 1 << (31 - n)
109}
110
111pub mod kdc_options {
113 use super::bit;
114 pub const FORWARDABLE: u32 = bit(1);
115 pub const FORWARDED: u32 = bit(2);
116 pub const PROXIABLE: u32 = bit(3);
117 pub const PROXY: u32 = bit(4);
118 pub const ALLOW_POSTDATE: u32 = bit(5);
119 pub const POSTDATED: u32 = bit(6);
120 pub const RENEWABLE: u32 = bit(8);
121 pub const CANONICALIZE: u32 = bit(15);
122 pub const DISABLE_TRANSITED_CHECK: u32 = bit(26);
123 pub const RENEWABLE_OK: u32 = bit(27);
124 pub const ENC_TKT_IN_SKEY: u32 = bit(28);
125 pub const RENEW: u32 = bit(30);
126 pub const VALIDATE: u32 = bit(31);
127
128 pub const UNSUPPORTED: u32 =
131 FORWARDED | PROXY | ALLOW_POSTDATE | POSTDATED | ENC_TKT_IN_SKEY | RENEW | VALIDATE;
132
133 pub const NAMES: &[(u32, &str)] = &[
134 (FORWARDABLE, "forwardable"),
135 (FORWARDED, "forwarded"),
136 (PROXIABLE, "proxiable"),
137 (PROXY, "proxy"),
138 (ALLOW_POSTDATE, "allow_postdate"),
139 (POSTDATED, "postdated"),
140 (RENEWABLE, "renewable"),
141 (CANONICALIZE, "canonicalize"),
142 (DISABLE_TRANSITED_CHECK, "disable_transited_check"),
143 (RENEWABLE_OK, "renewable_ok"),
144 (ENC_TKT_IN_SKEY, "enc_tkt_in_skey"),
145 (RENEW, "renew"),
146 (VALIDATE, "validate"),
147 ];
148}
149
150pub mod ticket_flags {
152 use super::bit;
153 pub const FORWARDABLE: u32 = bit(1);
154 pub const FORWARDED: u32 = bit(2);
155 pub const PROXIABLE: u32 = bit(3);
156 pub const PROXY: u32 = bit(4);
157 pub const MAY_POSTDATE: u32 = bit(5);
158 pub const POSTDATED: u32 = bit(6);
159 pub const INVALID: u32 = bit(7);
160 pub const RENEWABLE: u32 = bit(8);
161 pub const INITIAL: u32 = bit(9);
162 pub const PRE_AUTHENT: u32 = bit(10);
163 pub const HW_AUTHENT: u32 = bit(11);
164 pub const TRANSITED_POLICY_CHECKED: u32 = bit(12);
165 pub const OK_AS_DELEGATE: u32 = bit(13);
166 pub const ENC_PA_REP: u32 = bit(15);
167
168 pub const NAMES: &[(u32, &str)] = &[
169 (FORWARDABLE, "forwardable"),
170 (FORWARDED, "forwarded"),
171 (PROXIABLE, "proxiable"),
172 (PROXY, "proxy"),
173 (MAY_POSTDATE, "may_postdate"),
174 (POSTDATED, "postdated"),
175 (INVALID, "invalid"),
176 (RENEWABLE, "renewable"),
177 (INITIAL, "initial"),
178 (PRE_AUTHENT, "pre_authent"),
179 (HW_AUTHENT, "hw_authent"),
180 (TRANSITED_POLICY_CHECKED, "transited_policy_checked"),
181 (OK_AS_DELEGATE, "ok_as_delegate"),
182 (ENC_PA_REP, "enc_pa_rep"),
183 ];
184}
185
186pub fn flag_names(value: u32, table: &[(u32, &str)]) -> Vec<String> {
188 table
189 .iter()
190 .filter(|(b, _)| value & b != 0)
191 .map(|(_, n)| n.to_string())
192 .collect()
193}
194
195pub fn flags_u32(flags: &KerberosFlags) -> u32 {
198 let data = flags.0.as_bytes();
199 let payload = data.get(1..).unwrap_or(&[]);
200 let mut out = [0u8; 4];
201 for (i, b) in payload.iter().take(4).enumerate() {
202 out[i] = *b;
203 }
204 u32::from_be_bytes(out)
205}
206
207pub fn flags(value: u32) -> KerberosFlags {
209 BitStringAsn1(BitString::with_bytes(value.to_be_bytes().to_vec()))
210}
211
212#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
218pub enum DerError {
219 #[error("empty input")]
220 Empty,
221 #[error("message is {0} bytes; the limit is {MAX_MESSAGE_LEN}")]
222 TooLarge(usize),
223 #[error("truncated element at offset {0}")]
224 Truncated(usize),
225 #[error("indefinite length at offset {0}; DER requires definite lengths")]
226 Indefinite(usize),
227 #[error("multi-byte tag at offset {0}; Kerberos uses none")]
228 LongTag(usize),
229 #[error("length at offset {0} is wider than four octets")]
230 LengthTooWide(usize),
231 #[error("element at offset {0} runs past its enclosing element")]
232 Overrun(usize),
233 #[error("nesting deeper than {MAX_DER_DEPTH} at offset {0}")]
234 TooDeep(usize),
235 #[error("more than {MAX_DER_ELEMENTS} elements")]
236 TooManyElements,
237 #[error("{0} trailing bytes after the message")]
238 Trailing(usize),
239}
240
241struct Header {
243 tag: u8,
244 content_start: usize,
245 content_len: usize,
246}
247
248fn header_at(bytes: &[u8], pos: usize, limit: usize) -> Result<Header, DerError> {
250 let tag = *bytes.get(pos).ok_or(DerError::Truncated(pos))?;
251 if tag & 0x1f == 0x1f {
252 return Err(DerError::LongTag(pos));
253 }
254 let first = *bytes.get(pos + 1).ok_or(DerError::Truncated(pos))?;
255 let (content_len, header_len) = if first < 0x80 {
256 (first as usize, 2)
257 } else if first == 0x80 {
258 return Err(DerError::Indefinite(pos));
259 } else {
260 let n = (first & 0x7f) as usize;
261 if n > 4 {
262 return Err(DerError::LengthTooWide(pos));
263 }
264 let start = pos + 2;
265 let end = start.checked_add(n).ok_or(DerError::Truncated(pos))?;
266 let octets = bytes.get(start..end).ok_or(DerError::Truncated(pos))?;
267 let len = octets
268 .iter()
269 .fold(0usize, |acc, b| (acc << 8) | *b as usize);
270 (len, 2 + n)
271 };
272 let content_start = pos + header_len;
273 let end = content_start
274 .checked_add(content_len)
275 .ok_or(DerError::Overrun(pos))?;
276 if end > limit || end > bytes.len() {
277 return Err(DerError::Overrun(pos));
278 }
279 Ok(Header {
280 tag,
281 content_start,
282 content_len,
283 })
284}
285
286pub fn check_der(bytes: &[u8]) -> Result<(), DerError> {
289 if bytes.is_empty() {
290 return Err(DerError::Empty);
291 }
292 if bytes.len() > MAX_MESSAGE_LEN {
293 return Err(DerError::TooLarge(bytes.len()));
294 }
295 let top = header_at(bytes, 0, bytes.len())?;
296 let top_end = top.content_start + top.content_len;
297 if top_end != bytes.len() {
298 return Err(DerError::Trailing(bytes.len() - top_end));
299 }
300 let mut ends: Vec<usize> = Vec::with_capacity(MAX_DER_DEPTH);
302 let mut elements = 1usize;
303 let mut pos;
304 if top.tag & 0x20 != 0 {
305 ends.push(top_end);
306 pos = top.content_start;
307 } else {
308 return Ok(());
309 }
310 while let Some(&end) = ends.last() {
311 if pos == end {
312 ends.pop();
313 continue;
314 }
315 let h = header_at(bytes, pos, end)?;
316 elements += 1;
317 if elements > MAX_DER_ELEMENTS {
318 return Err(DerError::TooManyElements);
319 }
320 let element_end = h.content_start + h.content_len;
321 if h.tag & 0x20 != 0 {
322 if ends.len() >= MAX_DER_DEPTH {
323 return Err(DerError::TooDeep(pos));
324 }
325 ends.push(element_end);
326 pos = h.content_start;
327 } else {
328 pos = element_end;
329 }
330 }
331 Ok(())
332}
333
334pub fn decode<T: DeserializeOwned>(bytes: &[u8]) -> Result<T> {
340 check_der(bytes).map_err(|e| anyhow!("refused before decoding: {e}"))?;
341 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
342 picky_asn1_der::from_bytes::<T>(bytes)
343 }));
344 match result {
345 Ok(Ok(v)) => Ok(v),
346 Ok(Err(e)) => Err(anyhow!("DER decode failed: {e}")),
347 Err(_) => Err(anyhow!("DER decoder panicked; refused")),
348 }
349}
350
351pub fn encode<T: Serialize>(value: &T) -> Result<Vec<u8>> {
353 picky_asn1_der::to_vec(value).map_err(|e| anyhow!("DER encode failed: {e}"))
354}
355
356pub fn req_body_bytes(message: &[u8]) -> Option<&[u8]> {
359 check_der(message).ok()?;
360 let app = header_at(message, 0, message.len()).ok()?;
361 let seq = header_at(message, app.content_start, message.len()).ok()?;
362 if seq.tag != 0x30 {
363 return None;
364 }
365 let end = seq.content_start + seq.content_len;
366 let mut pos = seq.content_start;
367 while pos < end {
368 let field = header_at(message, pos, end).ok()?;
369 let field_end = field.content_start + field.content_len;
370 if field.tag == 0xa4 {
371 let body = header_at(message, field.content_start, field_end).ok()?;
372 let body_end = body.content_start + body.content_len;
373 if body.tag != 0x30 || body_end != field_end {
374 return None;
375 }
376 return message.get(field.content_start..body_end);
377 }
378 pos = field_end;
379 }
380 None
381}
382
383pub fn int_i64(value: &IntegerAsn1) -> Option<i64> {
389 let bytes = value.as_signed_bytes_be();
390 if bytes.is_empty() || bytes.len() > 8 {
391 return None;
392 }
393 let fill = if bytes[0] & 0x80 != 0 { 0xff } else { 0 };
394 let mut buf = [fill; 8];
395 buf[8 - bytes.len()..].copy_from_slice(bytes);
396 Some(i64::from_be_bytes(buf))
397}
398
399pub fn int(value: i64) -> IntegerAsn1 {
401 IntegerAsn1::from_bytes_be_signed(value.to_be_bytes().to_vec())
402}
403
404pub fn kstring(s: &str) -> Result<KerberosStringAsn1> {
406 Ok(GeneralStringAsn1(
407 Ia5String::from_string(s.to_string())
408 .map_err(|_| anyhow!("'{s}' is not an IA5 (ASCII) string"))?,
409 ))
410}
411
412pub fn kstring_text(s: &KerberosStringAsn1) -> String {
414 s.0.as_utf8().to_string()
415}
416
417pub fn now_secs() -> i64 {
419 crate::utils::clock::SystemTime::now()
420 .duration_since(crate::utils::clock::UNIX_EPOCH)
421 .map(|d| d.as_secs() as i64)
422 .unwrap_or(0)
423}
424
425pub fn ktime(secs: i64) -> KerberosTime {
428 let secs = secs.clamp(0, 253_402_300_799); let dt = chrono::DateTime::from_timestamp(secs, 0).unwrap_or_default();
430 use chrono::{Datelike, Timelike};
431 let date = Date::new(
432 dt.year() as u16,
433 dt.month() as u8,
434 dt.day() as u8,
435 dt.hour() as u8,
436 dt.minute() as u8,
437 dt.second() as u8,
438 )
439 .unwrap_or_else(|| Date::new(1970, 1, 1, 0, 0, 0).expect("the epoch is a valid date"));
440 GeneralizedTimeAsn1(date)
441}
442
443pub fn unix(t: &KerberosTime) -> Option<i64> {
445 let d = &t.0;
446 let date = chrono::NaiveDate::from_ymd_opt(d.year() as i32, d.month() as u32, d.day() as u32)?;
447 let time = date.and_hms_opt(d.hour() as u32, d.minute() as u32, d.second() as u32)?;
448 Some(time.and_utc().timestamp())
449}
450
451pub fn rfc3339(secs: i64) -> String {
453 chrono::DateTime::from_timestamp(secs, 0)
454 .map(|d| d.to_rfc3339_opts(chrono::SecondsFormat::Secs, true))
455 .unwrap_or_default()
456}
457
458#[derive(Clone, PartialEq, Eq, Hash)]
464pub struct Principal {
465 pub name_type: i32,
466 pub components: Vec<String>,
467}
468
469impl std::fmt::Display for Principal {
470 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
471 f.write_str(&self.components.join("/"))
472 }
473}
474
475impl std::fmt::Debug for Principal {
476 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
477 write!(f, "Principal({self})")
478 }
479}
480
481fn check_component(s: &str, what: &str) -> Result<()> {
483 if s.is_empty() {
484 return Err(anyhow!("empty {what}"));
485 }
486 if s.len() > MAX_NAME_LEN {
487 return Err(anyhow!(
488 "{what} is {} bytes; the limit is {MAX_NAME_LEN}",
489 s.len()
490 ));
491 }
492 if crate::utils::sanitize::has_controls(s) {
493 return Err(anyhow!("{what} contains a control character"));
494 }
495 if s.contains('/') || s.contains('@') || s.contains('\\') {
496 return Err(anyhow!("{what} contains '/', '@' or '\\'"));
497 }
498 Ok(())
499}
500
501pub fn check_realm(realm: &str) -> Result<()> {
503 if realm.is_empty() || realm.len() > MAX_NAME_LEN {
504 return Err(anyhow!("realm must be 1-{MAX_NAME_LEN} bytes"));
505 }
506 if !realm.is_ascii() || crate::utils::sanitize::has_controls(realm) {
507 return Err(anyhow!("realm must be printable ASCII"));
508 }
509 if realm.contains('/') || realm.contains('@') || realm.contains('\\') {
510 return Err(anyhow!("realm contains '/', '@' or '\\'"));
511 }
512 Ok(())
513}
514
515impl Principal {
516 pub fn krbtgt(realm: &str) -> Self {
518 Self {
519 name_type: NT_SRV_INST,
520 components: vec!["krbtgt".to_string(), realm.to_string()],
521 }
522 }
523
524 pub fn parse(text: &str) -> Result<(Self, Option<String>)> {
526 let (name, realm) = match text.rsplit_once('@') {
527 Some((n, r)) => {
528 check_realm(r)?;
529 (n, Some(r.to_string()))
530 }
531 None => (text, None),
532 };
533 let components: Vec<String> = name.split('/').map(str::to_string).collect();
534 if components.len() > MAX_NAME_COMPONENTS {
535 return Err(anyhow!(
536 "'{name}' has more than {MAX_NAME_COMPONENTS} components"
537 ));
538 }
539 for c in &components {
540 check_component(c, "principal name component")?;
541 }
542 let name_type = if components.len() > 1 {
543 NT_SRV_INST
544 } else {
545 NT_PRINCIPAL
546 };
547 Ok((
548 Self {
549 name_type,
550 components,
551 },
552 realm,
553 ))
554 }
555
556 pub fn from_asn1(name: &PrincipalName) -> Result<Self> {
558 let name_type = int_i64(&name.name_type.0)
559 .and_then(|v| i32::try_from(v).ok())
560 .context("name-type out of range")?;
561 let parts = &name.name_string.0 .0;
562 if parts.is_empty() || parts.len() > MAX_NAME_COMPONENTS {
563 return Err(anyhow!(
564 "a principal name needs 1-{MAX_NAME_COMPONENTS} components, not {}",
565 parts.len()
566 ));
567 }
568 let mut components = Vec::with_capacity(parts.len());
569 for p in parts {
570 let s = kstring_text(p);
571 check_component(&s, "principal name component")?;
572 components.push(s);
573 }
574 Ok(Self {
575 name_type,
576 components,
577 })
578 }
579
580 pub fn to_asn1(&self) -> Result<PrincipalName> {
581 let mut parts = Vec::with_capacity(self.components.len());
582 for c in &self.components {
583 parts.push(kstring(c)?);
584 }
585 Ok(PrincipalName {
586 name_type: ExplicitContextTag0(int(self.name_type as i64)),
587 name_string: ExplicitContextTag1(Asn1SequenceOf(parts)),
588 })
589 }
590
591 pub fn same_name(&self, other: &Principal) -> bool {
594 self.components == other.components
595 }
596
597 pub fn is_krbtgt(&self, realm: &str) -> bool {
598 self.components.len() == 2 && self.components[0] == "krbtgt" && self.components[1] == realm
599 }
600
601 pub fn default_salt(&self, realm: &str) -> String {
604 let mut s = realm.to_string();
605 for c in &self.components {
606 s.push_str(c);
607 }
608 s
609 }
610}
611
612pub fn etype_name(etype: i32) -> String {
618 match etype {
619 1 => "des-cbc-crc".into(),
620 3 => "des-cbc-md5".into(),
621 16 => "des3-cbc-sha1".into(),
622 17 => "aes128-cts-hmac-sha1-96".into(),
623 18 => "aes256-cts-hmac-sha1-96".into(),
624 19 => "aes128-cts-hmac-sha256-128".into(),
625 20 => "aes256-cts-hmac-sha384-192".into(),
626 23 => "arcfour-hmac".into(),
627 24 => "arcfour-hmac-exp".into(),
628 25 => "camellia128-cts-cmac".into(),
629 26 => "camellia256-cts-cmac".into(),
630 other => format!("etype-{other}"),
631 }
632}
633
634fn suite(etype: i32) -> Result<CipherSuite> {
635 match etype {
636 ETYPE_AES256 => Ok(CipherSuite::Aes256CtsHmacSha196),
637 ETYPE_AES128 => Ok(CipherSuite::Aes128CtsHmacSha196),
638 other => Err(anyhow!("enctype {} is not supported", etype_name(other))),
639 }
640}
641
642fn key_len(etype: i32) -> usize {
643 if etype == ETYPE_AES256 {
644 32
645 } else {
646 16
647 }
648}
649
650#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
652pub enum CryptoError {
653 #[error("unsupported enctype {0}")]
654 Etype(i32),
655 #[error("ciphertext is too short")]
656 Length,
657 #[error("integrity check failed")]
658 Integrity,
659 #[error("cipher error")]
660 Cipher,
661}
662
663#[derive(Clone)]
666pub struct Key {
667 etype: i32,
668 bytes: Vec<u8>,
669}
670
671impl Key {
672 pub fn from_password(etype: i32, password: &str, salt: &str) -> Result<Self> {
675 let bytes = suite(etype)?
676 .cipher()
677 .generate_key_from_password(password.as_bytes(), salt.as_bytes())
678 .map_err(|_| anyhow!("string-to-key failed for {}", etype_name(etype)))?;
679 Ok(Self { etype, bytes })
680 }
681
682 pub fn random(etype: i32) -> Result<Self> {
684 suite(etype)?;
685 let mut bytes = vec![0u8; key_len(etype)];
686 use rand::RngCore;
687 rand::rngs::OsRng.fill_bytes(&mut bytes);
688 Ok(Self { etype, bytes })
689 }
690
691 pub fn from_wire(etype: i32, bytes: Vec<u8>) -> Result<Self> {
693 suite(etype)?;
694 if bytes.len() != key_len(etype) {
695 return Err(anyhow!(
696 "a {} key must be {} bytes",
697 etype_name(etype),
698 key_len(etype)
699 ));
700 }
701 Ok(Self { etype, bytes })
702 }
703
704 pub fn etype(&self) -> i32 {
705 self.etype
706 }
707
708 pub fn to_asn1(&self) -> picky_krb::data_types::EncryptionKey {
710 picky_krb::data_types::EncryptionKey {
711 key_type: ExplicitContextTag0(int(self.etype as i64)),
712 key_value: ExplicitContextTag1(OctetStringAsn1(self.bytes.clone())),
713 }
714 }
715
716 pub fn from_asn1(key: &picky_krb::data_types::EncryptionKey) -> Result<Self> {
718 let etype = int_i64(&key.key_type.0)
719 .and_then(|v| i32::try_from(v).ok())
720 .context("key type out of range")?;
721 Self::from_wire(etype, key.key_value.0 .0.clone())
722 }
723
724 pub fn bytes_for_ccache(&self) -> &[u8] {
726 &self.bytes
727 }
728
729 pub fn encrypt(&self, usage: i32, plaintext: &[u8]) -> Result<Vec<u8>> {
731 suite(self.etype)?
732 .cipher()
733 .encrypt(&self.bytes, usage, plaintext)
734 .map_err(|_| anyhow!("encryption failed"))
735 }
736
737 pub fn encrypted_data(
739 &self,
740 usage: i32,
741 plaintext: &[u8],
742 kvno: Option<i64>,
743 ) -> Result<EncryptedData> {
744 Ok(EncryptedData {
745 etype: ExplicitContextTag0(int(self.etype as i64)),
746 kvno: Optional(kvno.map(|k| ExplicitContextTag1(int(k)))),
747 cipher: ExplicitContextTag2(OctetStringAsn1(self.encrypt(usage, plaintext)?)),
748 })
749 }
750
751 pub fn decrypt(&self, usage: i32, ciphertext: &[u8]) -> Result<Vec<u8>, CryptoError> {
754 let cipher = suite(self.etype)
755 .map_err(|_| CryptoError::Etype(self.etype))?
756 .cipher();
757 if ciphertext.len() < 16 + 12 {
758 return Err(CryptoError::Length);
759 }
760 let parts = cipher
761 .decrypt_no_checksum(&self.bytes, usage, ciphertext)
762 .map_err(|_| CryptoError::Cipher)?;
763 let mut mac =
764 Hmac::<sha1::Sha1>::new_from_slice(&parts.ki).map_err(|_| CryptoError::Cipher)?;
765 mac.update(&parts.confounder);
766 mac.update(&parts.plaintext);
767 let full = mac.finalize().into_bytes();
768 if !constant_time_eq(&full[..12], &parts.checksum) {
769 return Err(CryptoError::Integrity);
770 }
771 Ok(parts.plaintext)
772 }
773
774 pub fn decrypt_data(&self, usage: i32, data: &EncryptedData) -> Result<Vec<u8>, CryptoError> {
776 let etype = int_i64(&data.etype.0).unwrap_or(-1);
777 if etype != self.etype as i64 {
778 return Err(CryptoError::Etype(etype as i32));
779 }
780 self.decrypt(usage, &data.cipher.0 .0)
781 }
782
783 pub fn checksum_type(&self) -> i32 {
785 if self.etype == ETYPE_AES256 {
786 CKSUM_HMAC_SHA1_96_AES256
787 } else {
788 CKSUM_HMAC_SHA1_96_AES128
789 }
790 }
791
792 pub fn checksum(&self, usage: i32, data: &[u8]) -> Result<Vec<u8>> {
797 let suite = if self.etype == ETYPE_AES256 {
798 ChecksumSuite::HmacSha196Aes256
799 } else {
800 ChecksumSuite::HmacSha196Aes128
801 };
802 suite
803 .hasher()
804 .checksum(&self.bytes, usage, data)
805 .map_err(|_| anyhow!("checksum failed"))
806 }
807
808 pub fn verify_checksum(
811 &self,
812 cksumtype: i32,
813 usage: i32,
814 data: &[u8],
815 expected: &[u8],
816 ) -> bool {
817 if cksumtype != self.checksum_type() {
818 return false;
819 }
820 match self.checksum(usage, data) {
821 Ok(sum) => constant_time_eq(&sum, expected),
822 Err(_) => false,
823 }
824 }
825}
826
827pub fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
829 if a.len() != b.len() {
830 return false;
831 }
832 a.iter().zip(b).fold(0u8, |acc, (x, y)| acc | (x ^ y)) == 0
833}
834
835pub fn padata(padata_type: i32, value: Vec<u8>) -> PaData {
840 PaData {
841 padata_type: ExplicitContextTag1(int(padata_type as i64)),
842 padata_data: ExplicitContextTag2(OctetStringAsn1(value)),
843 }
844}
845
846pub fn padata_type(p: &PaData) -> i64 {
847 int_i64(&p.padata_type.0).unwrap_or(-1)
848}
849
850pub fn etype_info2(entries: &[(i32, String)]) -> Result<Vec<u8>> {
852 let mut list = Vec::with_capacity(entries.len());
853 for (etype, salt) in entries {
854 list.push(EtypeInfo2Entry {
855 etype: ExplicitContextTag0(int(*etype as i64)),
856 salt: Optional(Some(ExplicitContextTag1(kstring(salt)?))),
857 s2kparams: Optional(None),
858 });
859 }
860 encode(&Asn1SequenceOf(list))
861}
862
863pub const KDC_ERR_C_PRINCIPAL_UNKNOWN: i32 = 6;
868pub const KDC_ERR_S_PRINCIPAL_UNKNOWN: i32 = 7;
869pub const KDC_ERR_NEVER_VALID: i32 = 11;
870pub const KDC_ERR_POLICY: i32 = 12;
871pub const KDC_ERR_BADOPTION: i32 = 13;
872pub const KDC_ERR_ETYPE_NOSUPP: i32 = 14;
873pub const KDC_ERR_PADATA_TYPE_NOSUPP: i32 = 16;
874pub const KDC_ERR_PREAUTH_FAILED: i32 = 24;
875pub const KDC_ERR_PREAUTH_REQUIRED: i32 = 25;
876pub const KDC_ERR_SVC_UNAVAILABLE: i32 = 29;
877pub const KRB_AP_ERR_BAD_INTEGRITY: i32 = 31;
878pub const KRB_AP_ERR_TKT_EXPIRED: i32 = 32;
879pub const KRB_AP_ERR_TKT_NYV: i32 = 33;
880pub const KRB_AP_ERR_REPEAT: i32 = 34;
881pub const KRB_AP_ERR_BADMATCH: i32 = 36;
882pub const KRB_AP_ERR_SKEW: i32 = 37;
883pub const KRB_AP_ERR_MSG_TYPE: i32 = 40;
884pub const KRB_AP_ERR_MODIFIED: i32 = 41;
885pub const KRB_AP_ERR_INAPP_CKSUM: i32 = 50;
886pub const KRB_ERR_RESPONSE_TOO_BIG: i32 = 52;
887pub const KRB_ERR_GENERIC: i32 = 60;
888pub const KRB_ERR_FIELD_TOOLONG: i32 = 61;
889pub const KDC_ERR_WRONG_REALM: i32 = 68;
890
891pub const ERROR_NAMES: &[(i32, &str)] = &[
893 (0, "KDC_ERR_NONE"),
894 (1, "KDC_ERR_NAME_EXP"),
895 (2, "KDC_ERR_SERVICE_EXP"),
896 (3, "KDC_ERR_BAD_PVNO"),
897 (4, "KDC_ERR_C_OLD_MAST_KVNO"),
898 (5, "KDC_ERR_S_OLD_MAST_KVNO"),
899 (6, "KDC_ERR_C_PRINCIPAL_UNKNOWN"),
900 (7, "KDC_ERR_S_PRINCIPAL_UNKNOWN"),
901 (8, "KDC_ERR_PRINCIPAL_NOT_UNIQUE"),
902 (9, "KDC_ERR_NULL_KEY"),
903 (10, "KDC_ERR_CANNOT_POSTDATE"),
904 (11, "KDC_ERR_NEVER_VALID"),
905 (12, "KDC_ERR_POLICY"),
906 (13, "KDC_ERR_BADOPTION"),
907 (14, "KDC_ERR_ETYPE_NOSUPP"),
908 (15, "KDC_ERR_SUMTYPE_NOSUPP"),
909 (16, "KDC_ERR_PADATA_TYPE_NOSUPP"),
910 (17, "KDC_ERR_TRTYPE_NOSUPP"),
911 (18, "KDC_ERR_CLIENT_REVOKED"),
912 (19, "KDC_ERR_SERVICE_REVOKED"),
913 (20, "KDC_ERR_TGT_REVOKED"),
914 (21, "KDC_ERR_CLIENT_NOTYET"),
915 (22, "KDC_ERR_SERVICE_NOTYET"),
916 (23, "KDC_ERR_KEY_EXPIRED"),
917 (24, "KDC_ERR_PREAUTH_FAILED"),
918 (25, "KDC_ERR_PREAUTH_REQUIRED"),
919 (26, "KDC_ERR_SERVER_NOMATCH"),
920 (27, "KDC_ERR_MUST_USE_USER2USER"),
921 (28, "KDC_ERR_PATH_NOT_ACCEPTED"),
922 (29, "KDC_ERR_SVC_UNAVAILABLE"),
923 (31, "KRB_AP_ERR_BAD_INTEGRITY"),
924 (32, "KRB_AP_ERR_TKT_EXPIRED"),
925 (33, "KRB_AP_ERR_TKT_NYV"),
926 (34, "KRB_AP_ERR_REPEAT"),
927 (35, "KRB_AP_ERR_NOT_US"),
928 (36, "KRB_AP_ERR_BADMATCH"),
929 (37, "KRB_AP_ERR_SKEW"),
930 (38, "KRB_AP_ERR_BADADDR"),
931 (39, "KRB_AP_ERR_BADVERSION"),
932 (40, "KRB_AP_ERR_MSG_TYPE"),
933 (41, "KRB_AP_ERR_MODIFIED"),
934 (42, "KRB_AP_ERR_BADORDER"),
935 (44, "KRB_AP_ERR_BADKEYVER"),
936 (45, "KRB_AP_ERR_NOKEY"),
937 (46, "KRB_AP_ERR_MUT_FAIL"),
938 (47, "KRB_AP_ERR_BADDIRECTION"),
939 (48, "KRB_AP_ERR_METHOD"),
940 (49, "KRB_AP_ERR_BADSEQ"),
941 (50, "KRB_AP_ERR_INAPP_CKSUM"),
942 (51, "KRB_AP_PATH_NOT_ACCEPTED"),
943 (52, "KRB_ERR_RESPONSE_TOO_BIG"),
944 (60, "KRB_ERR_GENERIC"),
945 (61, "KRB_ERR_FIELD_TOOLONG"),
946 (68, "KDC_ERR_WRONG_REALM"),
947];
948
949pub fn error_name(code: i32) -> String {
951 ERROR_NAMES
952 .iter()
953 .find(|(c, _)| *c == code)
954 .map(|(_, n)| n.to_string())
955 .unwrap_or_else(|| format!("KRB_ERROR_{code}"))
956}
957
958pub const MODEL_REFUSALS: &[i32] = &[
963 1, 2, 6, 7, 10, 11, 12, 13, 14, 18, 19, 20, 21, 22, 23, 24, 27,
964];
965
966pub fn model_refusal_code(value: &serde_json::Value) -> Result<i32> {
969 let code = if let Some(n) = value.as_i64() {
970 i32::try_from(n).ok()
971 } else if let Some(s) = value.as_str() {
972 let wanted = s.trim().to_ascii_uppercase().replace(['-', ' '], "_");
973 ERROR_NAMES
974 .iter()
975 .find(|(_, name)| {
976 *name == wanted
977 || name.strip_prefix("KDC_ERR_") == Some(wanted.as_str())
978 || name.strip_prefix("KRB_AP_ERR_") == Some(wanted.as_str())
979 })
980 .map(|(c, _)| *c)
981 } else {
982 None
983 };
984 match code {
985 Some(c) if MODEL_REFUSALS.contains(&c) => Ok(c),
986 _ => Err(anyhow!(
987 "'error' must be one of: {}",
988 MODEL_REFUSALS
989 .iter()
990 .map(|c| error_name(*c).trim_start_matches("KDC_ERR_").to_string())
991 .collect::<Vec<_>>()
992 .join(", ")
993 )),
994 }
995}