1use crate::errors::{AuthError, Result};
30use aes::cipher::{BlockDecrypt, BlockEncrypt, KeyInit};
31use base64::Engine as _;
32use ring::rand::SecureRandom;
33use serde::{Deserialize, Serialize};
34use sha2::Digest;
35use std::collections::HashMap;
36use std::sync::Arc;
37use std::time::{SystemTime, UNIX_EPOCH};
38use subtle::ConstantTimeEq;
39use tokio::sync::RwLock;
40
41const ETYPE_AES128: i32 = 17;
45const ETYPE_AES256: i32 = 18;
47
48const KEY_USAGE_TICKET: u32 = 2;
50const KEY_USAGE_AP_REQ_AUTH: u32 = 11;
52
53const AES_BLOCK: usize = 16;
55const HMAC_LEN: usize = 12;
57const CONFOUNDER_LEN: usize = 16;
59
60const SPNEGO_OID_BYTES: &[u8] = &[0x2b, 0x06, 0x01, 0x05, 0x05, 0x02];
62const KRB5_OID_BYTES: &[u8] = &[0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02];
64
65#[allow(dead_code)]
69#[derive(Debug)]
70struct DerTlv<'a> {
71 class: u8,
73 constructed: bool,
75 tag_num: u32,
77 value: &'a [u8],
79}
80
81fn parse_der(data: &[u8]) -> Result<(DerTlv<'_>, &[u8])> {
83 if data.is_empty() {
84 return Err(AuthError::validation("Empty DER data"));
85 }
86
87 let b0 = data[0];
88 let class = b0 >> 6;
89 let constructed = (b0 & 0x20) != 0;
90 let mut pos: usize = 1;
91
92 let tag_num = if (b0 & 0x1f) == 0x1f {
94 let mut t: u32 = 0;
95 loop {
96 if pos >= data.len() {
97 return Err(AuthError::validation("DER tag truncated"));
98 }
99 let b = data[pos];
100 pos += 1;
101 t = t
102 .checked_shl(7)
103 .ok_or_else(|| AuthError::validation("DER tag too large"))?
104 | (b & 0x7f) as u32;
105 if (b & 0x80) == 0 {
106 break;
107 }
108 }
109 t
110 } else {
111 (b0 & 0x1f) as u32
112 };
113
114 if pos >= data.len() {
116 return Err(AuthError::validation("DER length missing"));
117 }
118 let len_byte = data[pos];
119 pos += 1;
120
121 let length = if len_byte < 0x80 {
122 len_byte as usize
123 } else if len_byte == 0x80 {
124 return Err(AuthError::validation(
125 "Indefinite length not supported in DER",
126 ));
127 } else {
128 let num_bytes = (len_byte & 0x7f) as usize;
129 if num_bytes > 4 || pos + num_bytes > data.len() {
130 return Err(AuthError::validation("DER length overflow"));
131 }
132 let mut l: usize = 0;
133 for &b in &data[pos..pos + num_bytes] {
134 l = l
135 .checked_shl(8)
136 .ok_or_else(|| AuthError::validation("DER length too large"))?
137 | b as usize;
138 }
139 pos += num_bytes;
140 l
141 };
142
143 if pos + length > data.len() {
144 return Err(AuthError::validation("DER value truncated"));
145 }
146
147 Ok((
148 DerTlv {
149 class,
150 constructed,
151 tag_num,
152 value: &data[pos..pos + length],
153 },
154 &data[pos + length..],
155 ))
156}
157
158fn parse_der_contents(data: &[u8]) -> Result<Vec<DerTlv<'_>>> {
160 let mut result = Vec::new();
161 let mut remaining = data;
162 while !remaining.is_empty() {
163 let (tlv, rest) = parse_der(remaining)?;
164 result.push(tlv);
165 remaining = rest;
166 }
167 Ok(result)
168}
169
170fn get_ctx_field<'a, 'b>(fields: &'b [DerTlv<'a>], tag: u32) -> Option<&'b DerTlv<'a>> {
172 fields.iter().find(|f| f.class == 2 && f.tag_num == tag)
173}
174
175fn unwrap_explicit<'a>(tlv: &DerTlv<'a>) -> Result<DerTlv<'a>> {
177 let (inner, _) = parse_der(tlv.value)?;
178 Ok(inner)
179}
180
181fn parse_der_integer(data: &[u8]) -> Result<i64> {
183 if data.is_empty() {
184 return Err(AuthError::validation("Empty INTEGER"));
185 }
186 let mut val: i64 = if data[0] & 0x80 != 0 { -1 } else { 0 };
187 for &b in data {
188 val = (val << 8) | b as i64;
189 }
190 Ok(val)
191}
192
193fn parse_der_string(data: &[u8]) -> Result<String> {
195 String::from_utf8(data.to_vec())
196 .map_err(|e| AuthError::validation(format!("Invalid string encoding: {e}")))
197}
198
199fn parse_kerberos_time(data: &[u8]) -> Result<u64> {
201 let s = parse_der_string(data)?;
202 if s.len() < 15 || !s.ends_with('Z') {
203 return Err(AuthError::validation("Invalid KerberosTime format"));
204 }
205 let dt = chrono::NaiveDateTime::parse_from_str(&s[..14], "%Y%m%d%H%M%S")
206 .map_err(|e| AuthError::validation(format!("Invalid KerberosTime: {e}")))?;
207 Ok(dt.and_utc().timestamp() as u64)
208}
209
210fn test_bit_flag(flags_data: &[u8], bit_num: usize) -> bool {
213 if flags_data.is_empty() {
214 return false;
215 }
216 let byte_idx = 1 + bit_num / 8;
218 let bit_idx = 7 - (bit_num % 8);
219 if byte_idx >= flags_data.len() {
220 return false;
221 }
222 (flags_data[byte_idx] >> bit_idx) & 1 == 1
223}
224
225fn oid_matches(tlv: &DerTlv<'_>, expected: &[u8]) -> bool {
227 tlv.class == 0 && tlv.tag_num == 6 && tlv.value == expected
228}
229
230struct ParsedEncryptedData<'a> {
234 etype: i32,
235 kvno: Option<u32>,
236 cipher: &'a [u8],
237}
238
239struct ParsedPrincipalName {
241 components: Vec<String>,
242}
243
244impl ParsedPrincipalName {
245 fn to_string_without_realm(&self) -> String {
247 self.components.join("/")
248 }
249}
250
251struct ParsedTicket<'a> {
253 realm: String,
254 sname: ParsedPrincipalName,
255 enc_part: ParsedEncryptedData<'a>,
256}
257
258#[allow(dead_code)]
260struct ParsedApReq<'a> {
261 ap_options: u32,
262 ticket: ParsedTicket<'a>,
263 authenticator: ParsedEncryptedData<'a>,
264}
265
266#[allow(dead_code)]
268struct DecryptedTicketPart {
269 flags_raw: Vec<u8>,
270 session_key_type: i32,
271 session_key_value: Vec<u8>,
272 crealm: String,
273 cname: ParsedPrincipalName,
274 auth_time: u64,
275 end_time: u64,
276 start_time: Option<u64>,
277 renew_till: Option<u64>,
278}
279
280struct DecryptedAuthenticator {
282 crealm: String,
283 cname: ParsedPrincipalName,
284 cusec: u32,
285 ctime: u64,
286}
287
288fn parse_encrypted_data<'a>(data: &'a [u8]) -> Result<ParsedEncryptedData<'a>> {
289 let (seq, _) = parse_der(data)?;
290 if seq.tag_num != 16 {
291 return Err(AuthError::validation("EncryptedData: expected SEQUENCE"));
292 }
293 let fields = parse_der_contents(seq.value)?;
294
295 let etype_f = get_ctx_field(&fields, 0)
296 .ok_or_else(|| AuthError::validation("EncryptedData missing etype"))?;
297 let etype = parse_der_integer(unwrap_explicit(etype_f)?.value)? as i32;
298
299 let kvno = if let Some(f) = get_ctx_field(&fields, 1) {
300 Some(parse_der_integer(unwrap_explicit(f)?.value)? as u32)
301 } else {
302 None
303 };
304
305 let cipher_f = get_ctx_field(&fields, 2)
306 .ok_or_else(|| AuthError::validation("EncryptedData missing cipher"))?;
307 let cipher_tlv = unwrap_explicit(cipher_f)?;
308
309 Ok(ParsedEncryptedData {
310 etype,
311 kvno,
312 cipher: cipher_tlv.value,
313 })
314}
315
316fn parse_principal_name(data: &[u8]) -> Result<ParsedPrincipalName> {
317 let (seq, _) = parse_der(data)?;
318 let fields = parse_der_contents(seq.value)?;
319
320 let strings_f = get_ctx_field(&fields, 1)
322 .ok_or_else(|| AuthError::validation("PrincipalName missing name-string"))?;
323 let (strings_seq, _) = parse_der(strings_f.value)?;
324 let string_tlvs = parse_der_contents(strings_seq.value)?;
325
326 let mut components = Vec::new();
327 for tlv in &string_tlvs {
328 components.push(parse_der_string(tlv.value)?);
329 }
330
331 Ok(ParsedPrincipalName { components })
332}
333
334fn parse_ticket<'a>(data: &'a [u8]) -> Result<ParsedTicket<'a>> {
335 let (app, _) = parse_der(data)?;
337 if app.class != 1 || app.tag_num != 1 {
338 return Err(AuthError::validation("Expected Ticket (APPLICATION 1)"));
339 }
340 let (seq, _) = parse_der(app.value)?;
341 let fields = parse_der_contents(seq.value)?;
342
343 let vno_f =
345 get_ctx_field(&fields, 0).ok_or_else(|| AuthError::validation("Ticket missing tkt-vno"))?;
346 let vno = parse_der_integer(unwrap_explicit(vno_f)?.value)?;
347 if vno != 5 {
348 return Err(AuthError::validation(format!(
349 "Unsupported ticket version: {vno}"
350 )));
351 }
352
353 let realm_f =
355 get_ctx_field(&fields, 1).ok_or_else(|| AuthError::validation("Ticket missing realm"))?;
356 let realm = parse_der_string(unwrap_explicit(realm_f)?.value)?;
357
358 let sname_f =
360 get_ctx_field(&fields, 2).ok_or_else(|| AuthError::validation("Ticket missing sname"))?;
361 let sname = parse_principal_name(sname_f.value)?;
362
363 let enc_f = get_ctx_field(&fields, 3)
365 .ok_or_else(|| AuthError::validation("Ticket missing enc-part"))?;
366 let enc_part = parse_encrypted_data(enc_f.value)?;
367
368 Ok(ParsedTicket {
369 realm,
370 sname,
371 enc_part,
372 })
373}
374
375fn parse_ap_req<'a>(data: &'a [u8]) -> Result<ParsedApReq<'a>> {
376 let (app, _) = parse_der(data)?;
378 if app.class != 1 || app.tag_num != 14 {
379 return Err(AuthError::validation("Expected AP-REQ (APPLICATION 14)"));
380 }
381 let (seq, _) = parse_der(app.value)?;
382 let fields = parse_der_contents(seq.value)?;
383
384 let pvno_f =
386 get_ctx_field(&fields, 0).ok_or_else(|| AuthError::validation("AP-REQ missing pvno"))?;
387 let pvno = parse_der_integer(unwrap_explicit(pvno_f)?.value)?;
388 if pvno != 5 {
389 return Err(AuthError::validation(format!(
390 "Unsupported Kerberos version: {pvno}"
391 )));
392 }
393
394 let mt_f = get_ctx_field(&fields, 1)
396 .ok_or_else(|| AuthError::validation("AP-REQ missing msg-type"))?;
397 let mt = parse_der_integer(unwrap_explicit(mt_f)?.value)?;
398 if mt != 14 {
399 return Err(AuthError::validation(format!(
400 "Expected AP-REQ msg-type 14, got {mt}"
401 )));
402 }
403
404 let opts_f = get_ctx_field(&fields, 2)
406 .ok_or_else(|| AuthError::validation("AP-REQ missing ap-options"))?;
407 let opts_tlv = unwrap_explicit(opts_f)?;
408 let ap_options = parse_ap_options(&opts_tlv)?;
409
410 let ticket_f =
412 get_ctx_field(&fields, 3).ok_or_else(|| AuthError::validation("AP-REQ missing ticket"))?;
413 let ticket = parse_ticket(ticket_f.value)?;
414
415 let auth_f = get_ctx_field(&fields, 4)
417 .ok_or_else(|| AuthError::validation("AP-REQ missing authenticator"))?;
418 let authenticator = parse_encrypted_data(auth_f.value)?;
419
420 Ok(ParsedApReq {
421 ap_options,
422 ticket,
423 authenticator,
424 })
425}
426
427fn parse_ap_options(tlv: &DerTlv<'_>) -> Result<u32> {
429 if tlv.value.len() < 2 {
430 return Ok(0);
431 }
432 let mut flags: u32 = 0;
434 for (i, &b) in tlv.value[1..].iter().enumerate() {
435 if i >= 4 {
436 break;
437 }
438 flags |= (b as u32) << (24 - i * 8);
439 }
440 Ok(flags)
441}
442
443fn parse_enc_ticket_part(data: &[u8]) -> Result<DecryptedTicketPart> {
445 let (app, _) = parse_der(data)?;
446 if app.class != 1 || app.tag_num != 3 {
447 return Err(AuthError::validation(
448 "Expected EncTicketPart (APPLICATION 3)",
449 ));
450 }
451 let (seq, _) = parse_der(app.value)?;
452 let fields = parse_der_contents(seq.value)?;
453
454 let flags_f = get_ctx_field(&fields, 0)
456 .ok_or_else(|| AuthError::validation("EncTicketPart missing flags"))?;
457 let flags_tlv = unwrap_explicit(flags_f)?;
458 let flags_raw = flags_tlv.value.to_vec();
459
460 let key_f = get_ctx_field(&fields, 1)
462 .ok_or_else(|| AuthError::validation("EncTicketPart missing key"))?;
463 let (key_seq, _) = parse_der(key_f.value)?;
464 let key_fields = parse_der_contents(key_seq.value)?;
465 let key_type_f = get_ctx_field(&key_fields, 0)
466 .ok_or_else(|| AuthError::validation("EncryptionKey missing keytype"))?;
467 let key_type = parse_der_integer(unwrap_explicit(key_type_f)?.value)? as i32;
468 let key_val_f = get_ctx_field(&key_fields, 1)
469 .ok_or_else(|| AuthError::validation("EncryptionKey missing keyvalue"))?;
470 let key_value = unwrap_explicit(key_val_f)?.value.to_vec();
471
472 let crealm_f = get_ctx_field(&fields, 2)
474 .ok_or_else(|| AuthError::validation("EncTicketPart missing crealm"))?;
475 let crealm = parse_der_string(unwrap_explicit(crealm_f)?.value)?;
476
477 let cname_f = get_ctx_field(&fields, 3)
479 .ok_or_else(|| AuthError::validation("EncTicketPart missing cname"))?;
480 let cname = parse_principal_name(cname_f.value)?;
481
482 let authtime_f = get_ctx_field(&fields, 5)
484 .ok_or_else(|| AuthError::validation("EncTicketPart missing authtime"))?;
485 let auth_time = parse_kerberos_time(unwrap_explicit(authtime_f)?.value)?;
486
487 let start_time = if let Some(f) = get_ctx_field(&fields, 6) {
489 Some(parse_kerberos_time(unwrap_explicit(f)?.value)?)
490 } else {
491 None
492 };
493
494 let endtime_f = get_ctx_field(&fields, 7)
496 .ok_or_else(|| AuthError::validation("EncTicketPart missing endtime"))?;
497 let end_time = parse_kerberos_time(unwrap_explicit(endtime_f)?.value)?;
498
499 let renew_till = if let Some(f) = get_ctx_field(&fields, 8) {
501 Some(parse_kerberos_time(unwrap_explicit(f)?.value)?)
502 } else {
503 None
504 };
505
506 Ok(DecryptedTicketPart {
507 flags_raw,
508 session_key_type: key_type,
509 session_key_value: key_value,
510 crealm,
511 cname,
512 auth_time,
513 end_time,
514 start_time,
515 renew_till,
516 })
517}
518
519fn parse_authenticator(data: &[u8]) -> Result<DecryptedAuthenticator> {
521 let (app, _) = parse_der(data)?;
522 if app.class != 1 || app.tag_num != 2 {
523 return Err(AuthError::validation(
524 "Expected Authenticator (APPLICATION 2)",
525 ));
526 }
527 let (seq, _) = parse_der(app.value)?;
528 let fields = parse_der_contents(seq.value)?;
529
530 let vno_f = get_ctx_field(&fields, 0)
532 .ok_or_else(|| AuthError::validation("Authenticator missing vno"))?;
533 let vno = parse_der_integer(unwrap_explicit(vno_f)?.value)?;
534 if vno != 5 {
535 return Err(AuthError::validation(format!(
536 "Unsupported authenticator version: {vno}"
537 )));
538 }
539
540 let crealm_f = get_ctx_field(&fields, 1)
542 .ok_or_else(|| AuthError::validation("Authenticator missing crealm"))?;
543 let crealm = parse_der_string(unwrap_explicit(crealm_f)?.value)?;
544
545 let cname_f = get_ctx_field(&fields, 2)
547 .ok_or_else(|| AuthError::validation("Authenticator missing cname"))?;
548 let cname = parse_principal_name(cname_f.value)?;
549
550 let cusec_f = get_ctx_field(&fields, 3)
552 .ok_or_else(|| AuthError::validation("Authenticator missing cusec"))?;
553 let cusec = parse_der_integer(unwrap_explicit(cusec_f)?.value)? as u32;
554
555 let ctime_f = get_ctx_field(&fields, 4)
557 .ok_or_else(|| AuthError::validation("Authenticator missing ctime"))?;
558 let ctime = parse_kerberos_time(unwrap_explicit(ctime_f)?.value)?;
559
560 Ok(DecryptedAuthenticator {
561 crealm,
562 cname,
563 cusec,
564 ctime,
565 })
566}
567
568fn gcd(a: usize, b: usize) -> usize {
572 if b == 0 { a } else { gcd(b, a % b) }
573}
574
575fn lcm(a: usize, b: usize) -> usize {
577 a / gcd(a, b) * b
578}
579
580fn nfold(input: &[u8], output_len: usize) -> Vec<u8> {
585 let in_bytes = input.len();
586 let out_bytes = output_len;
587 let lcm_val = lcm(in_bytes, out_bytes);
588 let in_bits = in_bytes * 8;
589
590 let mut out = vec![0u8; out_bytes];
591 let mut byte: u32 = 0;
592
593 for i in (0..lcm_val).rev() {
594 let msbit =
596 ((in_bits - 1) + ((in_bits + 13) * (i / in_bytes)) + ((in_bytes - i % in_bytes) * 8))
597 % in_bits;
598
599 let high = input[((in_bytes - 1).wrapping_sub(msbit >> 3)) % in_bytes] as u32;
601 let low = input[(in_bytes.wrapping_sub(msbit >> 3)) % in_bytes] as u32;
602 byte += ((high << 8 | low) >> ((msbit & 7) + 1)) & 0xff;
603
604 byte += out[i % out_bytes] as u32;
606 out[i % out_bytes] = (byte & 0xff) as u8;
607
608 byte >>= 8;
610 }
611
612 if byte != 0 {
614 for i in (0..out_bytes).rev() {
615 byte += out[i] as u32;
616 out[i] = (byte & 0xff) as u8;
617 byte >>= 8;
618 }
619 }
620
621 out
622}
623
624fn aes_ecb_encrypt(key: &[u8], block: &[u8; AES_BLOCK]) -> [u8; AES_BLOCK] {
627 let mut blk = aes::cipher::generic_array::GenericArray::clone_from_slice(block);
628 match key.len() {
629 16 => {
630 let cipher =
631 aes::Aes128::new(aes::cipher::generic_array::GenericArray::from_slice(key));
632 cipher.encrypt_block(&mut blk);
633 }
634 32 => {
635 let cipher =
636 aes::Aes256::new(aes::cipher::generic_array::GenericArray::from_slice(key));
637 cipher.encrypt_block(&mut blk);
638 }
639 _ => unreachable!("aes_ecb_encrypt: unsupported key length"),
640 }
641 let mut out = [0u8; AES_BLOCK];
642 out.copy_from_slice(&blk);
643 out
644}
645
646fn aes_ecb_decrypt(key: &[u8], block: &[u8; AES_BLOCK]) -> [u8; AES_BLOCK] {
648 let mut blk = aes::cipher::generic_array::GenericArray::clone_from_slice(block);
649 match key.len() {
650 16 => {
651 let cipher =
652 aes::Aes128::new(aes::cipher::generic_array::GenericArray::from_slice(key));
653 cipher.decrypt_block(&mut blk);
654 }
655 32 => {
656 let cipher =
657 aes::Aes256::new(aes::cipher::generic_array::GenericArray::from_slice(key));
658 cipher.decrypt_block(&mut blk);
659 }
660 _ => unreachable!("aes_ecb_decrypt: unsupported key length"),
661 }
662 let mut out = [0u8; AES_BLOCK];
663 out.copy_from_slice(&blk);
664 out
665}
666
667fn xor_bytes(a: &[u8], b: &[u8]) -> Vec<u8> {
669 a.iter().zip(b.iter()).map(|(&x, &y)| x ^ y).collect()
670}
671
672fn derive_key_aes(base_key: &[u8], usage: u32, key_type: u8) -> Vec<u8> {
679 let constant = [
680 (usage >> 24) as u8,
681 (usage >> 16) as u8,
682 (usage >> 8) as u8,
683 usage as u8,
684 key_type,
685 ];
686
687 let nfolded = nfold(&constant, AES_BLOCK);
689
690 let key_size = base_key.len(); let mut derived = Vec::with_capacity(key_size);
692
693 let mut input: [u8; AES_BLOCK] = nfolded.try_into().expect("nfold produced wrong size");
694 while derived.len() < key_size {
695 let encrypted = aes_ecb_encrypt(base_key, &input);
696 derived.extend_from_slice(&encrypted);
697 input = encrypted;
698 }
699
700 derived.truncate(key_size);
701 derived
702}
703
704fn aes_cbc_decrypt(key: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>> {
707 if ciphertext.len() % AES_BLOCK != 0 || ciphertext.is_empty() {
708 return Err(AuthError::crypto(
709 "AES-CBC ciphertext must be a non-empty multiple of block size",
710 ));
711 }
712
713 let mut plaintext = Vec::with_capacity(ciphertext.len());
714 let mut prev = [0u8; AES_BLOCK]; for chunk in ciphertext.chunks_exact(AES_BLOCK) {
717 let ct_block: [u8; AES_BLOCK] = chunk.try_into().unwrap();
718 let decrypted = aes_ecb_decrypt(key, &ct_block);
719 let pt_block = xor_bytes(&decrypted, &prev);
720 plaintext.extend_from_slice(&pt_block);
721 prev = ct_block;
722 }
723
724 Ok(plaintext)
725}
726
727fn aes_cts_decrypt(key: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>> {
734 let n = ciphertext.len();
735 if n < AES_BLOCK {
736 return Err(AuthError::crypto("AES-CTS ciphertext too short"));
737 }
738
739 if n == AES_BLOCK {
740 let ct: [u8; AES_BLOCK] = ciphertext.try_into().unwrap();
742 return Ok(aes_ecb_decrypt(key, &ct).to_vec());
743 }
744
745 if n % AES_BLOCK == 0 {
746 return aes_cbc_decrypt(key, ciphertext);
748 }
749
750 let partial_len = n % AES_BLOCK;
752 let num_full_blocks = n / AES_BLOCK; let preceding_len = (num_full_blocks - 1) * AES_BLOCK;
754
755 let c_second_last: [u8; AES_BLOCK] = ciphertext[preceding_len..preceding_len + AES_BLOCK]
756 .try_into()
757 .unwrap();
758 let c_last = &ciphertext[preceding_len + AES_BLOCK..];
759
760 let prev_cipher = if preceding_len >= AES_BLOCK {
762 &ciphertext[preceding_len - AES_BLOCK..preceding_len]
763 } else {
764 &[0u8; AES_BLOCK][..] };
766
767 let d = aes_ecb_decrypt(key, &c_second_last);
769
770 let p_last = xor_bytes(&d[..partial_len], c_last);
772
773 let mut c_recovered = [0u8; AES_BLOCK];
775 c_recovered[..partial_len].copy_from_slice(c_last);
776 c_recovered[partial_len..].copy_from_slice(&d[partial_len..]);
777
778 let decrypted_recovered = aes_ecb_decrypt(key, &c_recovered);
780 let p_second_last = xor_bytes(&decrypted_recovered, prev_cipher);
781
782 let mut plaintext = if preceding_len > 0 {
784 aes_cbc_decrypt(key, &ciphertext[..preceding_len])?
785 } else {
786 Vec::new()
787 };
788
789 plaintext.extend_from_slice(&p_second_last);
790 plaintext.extend_from_slice(&p_last);
791
792 Ok(plaintext)
793}
794
795fn hmac_sha1(key: &[u8], data: &[u8]) -> Vec<u8> {
797 use hmac::{Hmac, Mac};
798 type HmacSha1 = Hmac<sha1::Sha1>;
799
800 let mut mac = <HmacSha1 as Mac>::new_from_slice(key).expect("HMAC-SHA1 accepts any key length");
801 mac.update(data);
802 mac.finalize().into_bytes().to_vec()
803}
804
805fn decrypt_aes_cts(base_key: &[u8], ciphertext: &[u8], etype: i32, usage: u32) -> Result<Vec<u8>> {
811 let expected_key_len = match etype {
812 ETYPE_AES128 => 16,
813 ETYPE_AES256 => 32,
814 _ => {
815 return Err(AuthError::crypto(format!(
816 "Unsupported encryption type {etype}; only AES etypes 17/18 are supported"
817 )));
818 }
819 };
820
821 if base_key.len() != expected_key_len {
822 return Err(AuthError::crypto(format!(
823 "Key length mismatch: expected {expected_key_len}, got {}",
824 base_key.len()
825 )));
826 }
827
828 if ciphertext.len() < CONFOUNDER_LEN + HMAC_LEN {
829 return Err(AuthError::crypto(
830 "Ciphertext too short for AES-CTS envelope",
831 ));
832 }
833
834 let ct_body = &ciphertext[..ciphertext.len() - HMAC_LEN];
835 let expected_hmac = &ciphertext[ciphertext.len() - HMAC_LEN..];
836
837 let ke = derive_key_aes(base_key, usage, 0xAA);
839 let ki = derive_key_aes(base_key, usage, 0x55);
840
841 let plaintext_with_confounder = aes_cts_decrypt(&ke, ct_body)?;
843
844 let computed_hmac = hmac_sha1(&ki, &plaintext_with_confounder);
846 if computed_hmac[..HMAC_LEN].ct_eq(expected_hmac).unwrap_u8() != 1 {
847 return Err(AuthError::crypto(
848 "Kerberos HMAC verification failed — wrong key or corrupted ticket",
849 ));
850 }
851
852 Ok(plaintext_with_confounder[CONFOUNDER_LEN..].to_vec())
854}
855
856fn parse_spnego_init_token(data: &[u8]) -> Result<SpnegoToken> {
868 let (app, _) = parse_der(data)?;
870 if app.class != 1 || app.tag_num != 0 {
871 return Err(AuthError::validation(
872 "Expected GSS-API InitialContextToken (APPLICATION 0)",
873 ));
874 }
875
876 let (oid_tlv, rest) = parse_der(app.value)?;
878 if !oid_matches(&oid_tlv, SPNEGO_OID_BYTES) {
879 return Err(AuthError::validation("Not an SPNEGO token"));
880 }
881
882 let (neg_init_wrapper, _) = parse_der(rest)?;
884 if neg_init_wrapper.class != 2 || neg_init_wrapper.tag_num != 0 {
885 return Err(AuthError::validation("Expected NegTokenInit ([CONTEXT 0])"));
886 }
887
888 let (neg_init_seq, _) = parse_der(neg_init_wrapper.value)?;
890 let fields = parse_der_contents(neg_init_seq.value)?;
891
892 let mech_types_f = get_ctx_field(&fields, 0)
894 .ok_or_else(|| AuthError::validation("NegTokenInit missing mechTypes"))?;
895 let (mech_seq, _) = parse_der(mech_types_f.value)?;
896 let mech_oids = parse_der_contents(mech_seq.value)?;
897
898 let has_krb5 = mech_oids.iter().any(|o| oid_matches(o, KRB5_OID_BYTES));
900 if !has_krb5 {
901 return Err(AuthError::validation(
902 "SPNEGO NegTokenInit does not offer Kerberos 5",
903 ));
904 }
905
906 let mech_token_f = get_ctx_field(&fields, 2)
908 .ok_or_else(|| AuthError::validation("NegTokenInit missing mechToken"))?;
909 let mech_token_tlv = unwrap_explicit(mech_token_f)?;
910
911 Ok(SpnegoToken {
912 mech_oid: oid::KERBEROS_V5.to_string(),
913 mech_token: mech_token_tlv.value.to_vec(),
914 state: SpnegoState::Initial,
915 })
916}
917
918fn parse_spnego_resp_token(data: &[u8]) -> Result<SpnegoToken> {
920 let (wrapper, _) = parse_der(data)?;
921 if wrapper.class != 2 || wrapper.tag_num != 1 {
922 return Err(AuthError::validation("Expected NegTokenResp ([CONTEXT 1])"));
923 }
924
925 let (seq, _) = parse_der(wrapper.value)?;
926 let fields = parse_der_contents(seq.value)?;
927
928 let mech_token = if let Some(f) = get_ctx_field(&fields, 2) {
930 unwrap_explicit(f)?.value.to_vec()
931 } else {
932 Vec::new()
933 };
934
935 Ok(SpnegoToken {
936 mech_oid: oid::KERBEROS_V5.to_string(),
937 mech_token,
938 state: SpnegoState::Continue,
939 })
940}
941
942#[derive(Debug, Clone)]
946pub struct KerberosConfig {
947 pub service_principal: String,
949
950 pub realm: String,
952
953 pub keytab_path: Option<String>,
955
956 pub kdc_addresses: Vec<String>,
958
959 pub max_clock_skew_secs: u64,
961
962 pub allow_delegation: bool,
964
965 pub replay_cache_max_entries: usize,
967}
968
969impl Default for KerberosConfig {
970 fn default() -> Self {
971 Self {
972 service_principal: String::new(),
973 realm: String::new(),
974 keytab_path: None,
975 kdc_addresses: Vec::new(),
976 max_clock_skew_secs: 300,
977 allow_delegation: false,
978 replay_cache_max_entries: 100_000,
979 }
980 }
981}
982
983impl KerberosConfig {
984 pub fn builder(
1004 service_principal: impl Into<String>,
1005 realm: impl Into<String>,
1006 ) -> KerberosConfigBuilder {
1007 KerberosConfigBuilder {
1008 config: KerberosConfig {
1009 service_principal: service_principal.into(),
1010 realm: realm.into(),
1011 ..Default::default()
1012 },
1013 }
1014 }
1015
1016 pub fn active_directory(
1029 service_principal: impl Into<String>,
1030 realm: impl Into<String>,
1031 ) -> Self {
1032 Self {
1033 service_principal: service_principal.into(),
1034 realm: realm.into(),
1035 allow_delegation: true,
1036 ..Default::default()
1037 }
1038 }
1039}
1040
1041pub struct KerberosConfigBuilder {
1045 config: KerberosConfig,
1046}
1047
1048impl KerberosConfigBuilder {
1049 pub fn keytab_path(mut self, path: impl Into<String>) -> Self {
1051 self.config.keytab_path = Some(path.into());
1052 self
1053 }
1054
1055 pub fn add_kdc(mut self, addr: impl Into<String>) -> Self {
1057 self.config.kdc_addresses.push(addr.into());
1058 self
1059 }
1060
1061 pub fn max_clock_skew_secs(mut self, secs: u64) -> Self {
1063 self.config.max_clock_skew_secs = secs;
1064 self
1065 }
1066
1067 pub fn allow_delegation(mut self, allow: bool) -> Self {
1069 self.config.allow_delegation = allow;
1070 self
1071 }
1072
1073 pub fn replay_cache_max_entries(mut self, max: usize) -> Self {
1075 self.config.replay_cache_max_entries = max;
1076 self
1077 }
1078
1079 pub fn build(self) -> KerberosConfig {
1081 self.config
1082 }
1083}
1084
1085pub mod oid {
1089 pub const SPNEGO: &str = "1.3.6.1.5.5.2";
1091 pub const KERBEROS_V5: &str = "1.2.840.113554.1.2.2";
1093}
1094
1095#[derive(Debug, Clone, Serialize, Deserialize)]
1097pub struct KerberosAuthResult {
1098 pub client_principal: String,
1100
1101 pub realm: String,
1103
1104 pub auth_time: u64,
1106
1107 pub end_time: u64,
1109
1110 pub is_delegated: bool,
1112
1113 pub flags: KerberosTicketFlags,
1115
1116 pub response_token: Option<String>,
1118}
1119
1120#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1122pub struct KerberosTicketFlags {
1123 pub forwardable: bool,
1124 pub forwarded: bool,
1125 pub proxiable: bool,
1126 pub proxy: bool,
1127 pub may_postdate: bool,
1128 pub postdated: bool,
1129 pub renewable: bool,
1130 pub pre_authent: bool,
1131 pub hw_authent: bool,
1132}
1133
1134#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1136pub enum SpnegoState {
1137 Initial,
1139 Continue,
1141 Completed,
1143 Rejected,
1145}
1146
1147#[derive(Debug, Clone)]
1149pub struct SpnegoToken {
1150 pub mech_oid: String,
1152 pub mech_token: Vec<u8>,
1154 pub state: SpnegoState,
1156}
1157
1158#[derive(Debug, Clone)]
1160pub struct KeytabEntry {
1161 pub principal: String,
1162 pub realm: String,
1163 pub kvno: u32,
1164 pub key_type: u32,
1165 pub key_data: Vec<u8>,
1166}
1167
1168#[derive(Debug)]
1172pub struct KerberosManager {
1173 config: KerberosConfig,
1174 replay_cache: Arc<RwLock<HashMap<Vec<u8>, u64>>>,
1176 keytab_entries: Arc<RwLock<Vec<KeytabEntry>>>,
1178}
1179
1180impl KerberosManager {
1181 pub fn new(config: KerberosConfig) -> Result<Self> {
1183 if config.service_principal.is_empty() {
1184 return Err(AuthError::config("Kerberos service principal must be set"));
1185 }
1186 if config.realm.is_empty() {
1187 return Err(AuthError::config("Kerberos realm must be set"));
1188 }
1189
1190 Ok(Self {
1191 config,
1192 replay_cache: Arc::new(RwLock::new(HashMap::new())),
1193 keytab_entries: Arc::new(RwLock::new(Vec::new())),
1194 })
1195 }
1196
1197 pub async fn load_keytab(&self, path: &str) -> Result<usize> {
1199 let data = tokio::fs::read(path)
1200 .await
1201 .map_err(|e| AuthError::config(format!("Failed to read keytab file: {e}")))?;
1202
1203 let entries = parse_keytab(&data)?;
1204 let count = entries.len();
1205
1206 let mut kt = self.keytab_entries.write().await;
1207 *kt = entries;
1208
1209 Ok(count)
1210 }
1211
1212 pub async fn authenticate(&self, negotiate_token: &str) -> Result<KerberosAuthResult> {
1218 let token_bytes = base64::engine::general_purpose::STANDARD
1219 .decode(negotiate_token.trim())
1220 .map_err(|e| AuthError::validation(format!("Invalid Negotiate token encoding: {e}")))?;
1221
1222 if token_bytes.is_empty() {
1223 return Err(AuthError::validation("Empty Negotiate token"));
1224 }
1225
1226 let spnego = self.parse_spnego_token(&token_bytes)?;
1228
1229 if spnego.mech_oid != oid::KERBEROS_V5 {
1230 return Err(AuthError::validation(format!(
1231 "Unsupported SPNEGO mechanism: {}",
1232 spnego.mech_oid
1233 )));
1234 }
1235
1236 let result = self.validate_ap_req(&spnego.mech_token).await?;
1238
1239 Ok(result)
1240 }
1241
1242 pub fn generate_challenge(&self) -> String {
1244 "Negotiate".to_string()
1245 }
1246
1247 fn parse_spnego_token(&self, data: &[u8]) -> Result<SpnegoToken> {
1249 if data.len() < 2 {
1250 return Err(AuthError::validation("SPNEGO token too short"));
1251 }
1252
1253 match data[0] {
1254 0x60 => parse_spnego_init_token(data),
1256 0xa1 => parse_spnego_resp_token(data),
1258 _ => Ok(SpnegoToken {
1260 mech_oid: oid::KERBEROS_V5.to_string(),
1261 mech_token: data.to_vec(),
1262 state: SpnegoState::Initial,
1263 }),
1264 }
1265 }
1266
1267 async fn validate_ap_req(&self, ap_req_bytes: &[u8]) -> Result<KerberosAuthResult> {
1276 let keytab = self.keytab_entries.read().await;
1277 if keytab.is_empty() {
1278 return Err(AuthError::config(
1279 "No keytab loaded — cannot validate Kerberos tickets",
1280 ));
1281 }
1282
1283 let ap_req = parse_ap_req(ap_req_bytes)?;
1285
1286 let ticket_etype = ap_req.ticket.enc_part.etype;
1288 let ticket_kvno = ap_req.ticket.enc_part.kvno;
1289 let ticket_sname = format!(
1290 "{}@{}",
1291 ap_req.ticket.sname.to_string_without_realm(),
1292 ap_req.ticket.realm
1293 );
1294
1295 let entry = keytab
1296 .iter()
1297 .find(|e| {
1298 e.principal == ticket_sname
1299 && e.key_type == ticket_etype as u32
1300 && ticket_kvno.is_none_or(|v| e.kvno == v)
1301 })
1302 .or_else(|| {
1303 keytab
1305 .iter()
1306 .find(|e| e.realm == ap_req.ticket.realm && e.key_type == ticket_etype as u32)
1307 })
1308 .ok_or_else(|| {
1309 AuthError::config(format!(
1310 "No keytab entry for principal={ticket_sname} etype={ticket_etype}"
1311 ))
1312 })?;
1313
1314 let ticket_plaintext = decrypt_aes_cts(
1316 &entry.key_data,
1317 ap_req.ticket.enc_part.cipher,
1318 ticket_etype,
1319 KEY_USAGE_TICKET,
1320 )?;
1321
1322 let ticket_part = parse_enc_ticket_part(&ticket_plaintext)?;
1323
1324 let now = SystemTime::now()
1326 .duration_since(UNIX_EPOCH)
1327 .map_err(|e| AuthError::internal(format!("Clock error: {e}")))?
1328 .as_secs();
1329
1330 if now > ticket_part.end_time + self.config.max_clock_skew_secs {
1331 return Err(AuthError::validation("Kerberos ticket has expired"));
1332 }
1333
1334 if let Some(start) = ticket_part.start_time {
1335 if now + self.config.max_clock_skew_secs < start {
1336 return Err(AuthError::validation("Kerberos ticket is not yet valid"));
1337 }
1338 }
1339
1340 let auth_etype = ap_req.authenticator.etype;
1342 let auth_plaintext = decrypt_aes_cts(
1343 &ticket_part.session_key_value,
1344 ap_req.authenticator.cipher,
1345 auth_etype,
1346 KEY_USAGE_AP_REQ_AUTH,
1347 )?;
1348
1349 let authenticator = parse_authenticator(&auth_plaintext)?;
1350
1351 if authenticator.crealm != ticket_part.crealm {
1353 return Err(AuthError::validation(
1354 "Authenticator crealm does not match ticket",
1355 ));
1356 }
1357
1358 if authenticator.cname.to_string_without_realm()
1359 != ticket_part.cname.to_string_without_realm()
1360 {
1361 return Err(AuthError::validation(
1362 "Authenticator cname does not match ticket",
1363 ));
1364 }
1365
1366 let time_diff = if now > authenticator.ctime {
1368 now - authenticator.ctime
1369 } else {
1370 authenticator.ctime - now
1371 };
1372
1373 if time_diff > self.config.max_clock_skew_secs {
1374 return Err(AuthError::validation(format!(
1375 "Authenticator clock skew too large: {time_diff}s (max {}s)",
1376 self.config.max_clock_skew_secs
1377 )));
1378 }
1379
1380 self.check_replay_authenticator(
1382 authenticator.ctime,
1383 authenticator.cusec,
1384 &authenticator.cname.to_string_without_realm(),
1385 )
1386 .await?;
1387
1388 let client_principal = format!(
1390 "{}@{}",
1391 ticket_part.cname.to_string_without_realm(),
1392 ticket_part.crealm
1393 );
1394 let is_delegated = test_bit_flag(&ticket_part.flags_raw, 2); let flags = KerberosTicketFlags {
1397 forwardable: test_bit_flag(&ticket_part.flags_raw, 1),
1398 forwarded: test_bit_flag(&ticket_part.flags_raw, 2),
1399 proxiable: test_bit_flag(&ticket_part.flags_raw, 3),
1400 proxy: test_bit_flag(&ticket_part.flags_raw, 4),
1401 may_postdate: test_bit_flag(&ticket_part.flags_raw, 5),
1402 postdated: test_bit_flag(&ticket_part.flags_raw, 6),
1403 renewable: test_bit_flag(&ticket_part.flags_raw, 8),
1404 pre_authent: test_bit_flag(&ticket_part.flags_raw, 10),
1405 hw_authent: test_bit_flag(&ticket_part.flags_raw, 11),
1406 };
1407
1408 if is_delegated && !self.config.allow_delegation {
1410 return Err(AuthError::validation(
1411 "Delegated (forwarded) tickets are not allowed by policy",
1412 ));
1413 }
1414
1415 Ok(KerberosAuthResult {
1416 client_principal,
1417 realm: ticket_part.crealm,
1418 auth_time: ticket_part.auth_time,
1419 end_time: ticket_part.end_time,
1420 is_delegated,
1421 flags,
1422 response_token: None,
1423 })
1424 }
1425
1426 async fn check_replay_authenticator(&self, ctime: u64, cusec: u32, cname: &str) -> Result<()> {
1428 let mut hasher = sha2::Sha256::new();
1430 hasher.update(ctime.to_be_bytes());
1431 hasher.update(cusec.to_be_bytes());
1432 hasher.update(cname.as_bytes());
1433 let hash = hasher.finalize().to_vec();
1434
1435 let now = SystemTime::now()
1436 .duration_since(UNIX_EPOCH)
1437 .map_err(|e| AuthError::internal(format!("Clock error: {e}")))?
1438 .as_secs();
1439
1440 let mut cache = self.replay_cache.write().await;
1441
1442 let cutoff = now.saturating_sub(self.config.max_clock_skew_secs * 2);
1444 cache.retain(|_, &mut ts| ts > cutoff);
1445
1446 if cache.contains_key(&hash) {
1447 return Err(AuthError::validation("Kerberos replay attack detected"));
1448 }
1449
1450 if cache.len() >= self.config.replay_cache_max_entries {
1451 return Err(AuthError::internal("Kerberos replay cache full"));
1452 }
1453
1454 cache.insert(hash, now);
1455 Ok(())
1456 }
1457
1458 pub async fn check_replay(&self, token_data: &[u8]) -> Result<()> {
1460 let hash = sha2::Sha256::digest(token_data).to_vec();
1461
1462 let now = SystemTime::now()
1463 .duration_since(UNIX_EPOCH)
1464 .map_err(|e| AuthError::internal(format!("Clock error: {e}")))?
1465 .as_secs();
1466
1467 let mut cache = self.replay_cache.write().await;
1468
1469 let cutoff = now.saturating_sub(self.config.max_clock_skew_secs * 2);
1470 cache.retain(|_, &mut ts| ts > cutoff);
1471
1472 if cache.contains_key(&hash) {
1473 return Err(AuthError::validation("Kerberos replay attack detected"));
1474 }
1475
1476 if cache.len() >= self.config.replay_cache_max_entries {
1477 return Err(AuthError::internal("Kerberos replay cache full"));
1478 }
1479
1480 cache.insert(hash, now);
1481 Ok(())
1482 }
1483
1484 #[allow(dead_code)]
1486 fn generate_response_token(&self) -> Result<Option<String>> {
1487 let rng = ring::rand::SystemRandom::new();
1488 let mut nonce = [0u8; 16];
1489 rng.fill(&mut nonce)
1490 .map_err(|_| AuthError::crypto("Failed to generate SPNEGO response nonce"))?;
1491
1492 let encoded = base64::engine::general_purpose::STANDARD.encode(nonce);
1493 Ok(Some(encoded))
1494 }
1495}
1496
1497fn parse_keytab(data: &[u8]) -> Result<Vec<KeytabEntry>> {
1505 if data.len() < 4 {
1506 return Err(AuthError::config("Keytab file too short"));
1507 }
1508
1509 let version = u16::from_be_bytes([data[0], data[1]]);
1511 if version != 0x0502 && version != 0x0501 {
1512 return Err(AuthError::config(format!(
1513 "Unsupported keytab version: 0x{version:04x}"
1514 )));
1515 }
1516
1517 let mut entries = Vec::new();
1518 let mut pos = 2;
1519
1520 while pos + 4 <= data.len() {
1521 let entry_len =
1522 i32::from_be_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]);
1523 pos += 4;
1524
1525 if entry_len <= 0 {
1526 pos += entry_len.unsigned_abs() as usize;
1528 continue;
1529 }
1530 let entry_len = entry_len as usize;
1531
1532 if pos + entry_len > data.len() {
1533 break;
1534 }
1535
1536 let entry_data = &data[pos..pos + entry_len];
1537 if let Ok(entry) = parse_keytab_entry(entry_data, version) {
1538 entries.push(entry);
1539 }
1540
1541 pos += entry_len;
1542 }
1543
1544 Ok(entries)
1545}
1546
1547fn parse_keytab_entry(data: &[u8], _version: u16) -> Result<KeytabEntry> {
1549 if data.len() < 8 {
1550 return Err(AuthError::config("Keytab entry too short"));
1551 }
1552
1553 let num_components = u16::from_be_bytes([data[0], data[1]]) as usize;
1555 let mut pos = 2;
1556
1557 if pos + 2 > data.len() {
1559 return Err(AuthError::config("Keytab entry truncated at realm length"));
1560 }
1561 let realm_len = u16::from_be_bytes([data[pos], data[pos + 1]]) as usize;
1562 pos += 2;
1563
1564 if pos + realm_len > data.len() {
1565 return Err(AuthError::config("Keytab entry truncated at realm data"));
1566 }
1567 let realm = String::from_utf8_lossy(&data[pos..pos + realm_len]).to_string();
1568 pos += realm_len;
1569
1570 let mut principal_parts = Vec::new();
1572 for _ in 0..num_components {
1573 if pos + 2 > data.len() {
1574 return Err(AuthError::config("Keytab entry truncated at component"));
1575 }
1576 let comp_len = u16::from_be_bytes([data[pos], data[pos + 1]]) as usize;
1577 pos += 2;
1578 if pos + comp_len > data.len() {
1579 return Err(AuthError::config(
1580 "Keytab entry truncated at component data",
1581 ));
1582 }
1583 principal_parts.push(String::from_utf8_lossy(&data[pos..pos + comp_len]).to_string());
1584 pos += comp_len;
1585 }
1586 let principal = format!("{}@{}", principal_parts.join("/"), realm);
1587
1588 pos += 8;
1590 if pos >= data.len() {
1591 return Err(AuthError::config("Keytab entry truncated at kvno"));
1592 }
1593
1594 let kvno = data.get(pos).copied().unwrap_or(0) as u32;
1596 pos += 1;
1597
1598 if pos + 2 > data.len() {
1600 return Err(AuthError::config("Keytab entry truncated at key type"));
1601 }
1602 let key_type = u16::from_be_bytes([data[pos], data[pos + 1]]) as u32;
1603 pos += 2;
1604
1605 if pos + 2 > data.len() {
1607 return Err(AuthError::config("Keytab entry truncated at key length"));
1608 }
1609 let key_len = u16::from_be_bytes([data[pos], data[pos + 1]]) as usize;
1610 pos += 2;
1611
1612 if pos + key_len > data.len() {
1613 return Err(AuthError::config("Keytab entry truncated at key data"));
1614 }
1615 let key_data = data[pos..pos + key_len].to_vec();
1616
1617 Ok(KeytabEntry {
1618 principal,
1619 realm,
1620 kvno,
1621 key_type,
1622 key_data,
1623 })
1624}
1625
1626#[cfg(test)]
1627mod tests {
1628 use super::*;
1629
1630 #[test]
1631 fn test_config_defaults() {
1632 let config = KerberosConfig::default();
1633 assert_eq!(config.max_clock_skew_secs, 300);
1634 assert!(!config.allow_delegation);
1635 }
1636
1637 #[test]
1638 fn test_manager_requires_principal() {
1639 let config = KerberosConfig::default();
1640 let err = KerberosManager::new(config).unwrap_err();
1641 assert!(err.to_string().contains("service principal"));
1642 }
1643
1644 #[test]
1645 fn test_manager_requires_realm() {
1646 let config = KerberosConfig {
1647 service_principal: "HTTP/server.example.com".into(),
1648 ..Default::default()
1649 };
1650 let err = KerberosManager::new(config).unwrap_err();
1651 assert!(err.to_string().contains("realm"));
1652 }
1653
1654 #[test]
1655 fn test_manager_creation() {
1656 let config = KerberosConfig {
1657 service_principal: "HTTP/server.example.com@EXAMPLE.COM".into(),
1658 realm: "EXAMPLE.COM".into(),
1659 ..Default::default()
1660 };
1661 let mgr = KerberosManager::new(config);
1662 assert!(mgr.is_ok());
1663 }
1664
1665 #[test]
1666 fn test_generate_challenge() {
1667 let config = KerberosConfig {
1668 service_principal: "HTTP/server.example.com@EXAMPLE.COM".into(),
1669 realm: "EXAMPLE.COM".into(),
1670 ..Default::default()
1671 };
1672 let mgr = KerberosManager::new(config).unwrap();
1673 assert_eq!(mgr.generate_challenge(), "Negotiate");
1674 }
1675
1676 #[tokio::test]
1677 async fn test_replay_detection() {
1678 let config = KerberosConfig {
1679 service_principal: "HTTP/server.example.com@EXAMPLE.COM".into(),
1680 realm: "EXAMPLE.COM".into(),
1681 ..Default::default()
1682 };
1683 let mgr = KerberosManager::new(config).unwrap();
1684
1685 let token_data = b"test_token_data";
1686
1687 mgr.check_replay(token_data).await.unwrap();
1689
1690 let err = mgr.check_replay(token_data).await.unwrap_err();
1692 assert!(err.to_string().contains("replay"));
1693 }
1694
1695 #[test]
1696 fn test_invalid_keytab() {
1697 let result = parse_keytab(&[0x00, 0x01]);
1698 assert!(result.is_err());
1699 }
1700
1701 #[test]
1702 fn test_spnego_state_variants() {
1703 assert_eq!(SpnegoState::Initial, SpnegoState::Initial);
1704 assert_ne!(SpnegoState::Initial, SpnegoState::Completed);
1705 }
1706
1707 #[test]
1710 fn test_parse_der_integer() {
1711 let data = [0x02, 0x01, 0x05];
1713 let (tlv, rest) = parse_der(&data).unwrap();
1714 assert!(rest.is_empty());
1715 assert_eq!(tlv.class, 0); assert_eq!(tlv.tag_num, 2); assert_eq!(parse_der_integer(tlv.value).unwrap(), 5);
1718 }
1719
1720 #[test]
1721 fn test_parse_der_sequence() {
1722 let data = [0x30, 0x06, 0x02, 0x01, 0x05, 0x02, 0x01, 0x0e];
1725 let (tlv, _) = parse_der(&data).unwrap();
1726 assert_eq!(tlv.tag_num, 16); assert!(tlv.constructed);
1728 let contents = parse_der_contents(tlv.value).unwrap();
1729 assert_eq!(contents.len(), 2);
1730 assert_eq!(parse_der_integer(contents[0].value).unwrap(), 5);
1731 assert_eq!(parse_der_integer(contents[1].value).unwrap(), 14);
1732 }
1733
1734 #[test]
1735 fn test_parse_der_context_tag() {
1736 let data = [0xa0, 0x03, 0x02, 0x01, 0x05];
1738 let (tlv, _) = parse_der(&data).unwrap();
1739 assert_eq!(tlv.class, 2); assert_eq!(tlv.tag_num, 0);
1741 let inner = unwrap_explicit(&tlv).unwrap();
1742 assert_eq!(parse_der_integer(inner.value).unwrap(), 5);
1743 }
1744
1745 #[test]
1746 fn test_parse_der_oid() {
1747 let data = [0x06, 0x06, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x02];
1749 let (tlv, _) = parse_der(&data).unwrap();
1750 assert!(oid_matches(&tlv, SPNEGO_OID_BYTES));
1751 assert!(!oid_matches(&tlv, KRB5_OID_BYTES));
1752 }
1753
1754 #[test]
1755 fn test_parse_der_truncated_fails() {
1756 assert!(parse_der(&[]).is_err());
1757 assert!(parse_der(&[0x02]).is_err()); assert!(parse_der(&[0x02, 0x05, 0x01]).is_err()); }
1760
1761 #[test]
1764 fn test_nfold_64bit() {
1765 let result = nfold(b"012345", 8);
1767 assert_eq!(result, vec![0xBE, 0x07, 0x26, 0x31, 0x27, 0x6B, 0x19, 0x55]);
1768 }
1769
1770 #[test]
1771 fn test_nfold_56bit() {
1772 let result = nfold(b"password", 7);
1774 assert_eq!(result, vec![0x78, 0xA0, 0x7B, 0x6C, 0xAF, 0x85, 0xFA]);
1775 }
1776
1777 #[test]
1778 fn test_nfold_64bit_long_input() {
1779 let result = nfold(b"Rough Consensus, and Running Code", 8);
1781 assert_eq!(result, vec![0xBB, 0x6E, 0xD3, 0x08, 0x70, 0xB7, 0xF0, 0xE0]);
1782 }
1783
1784 #[test]
1785 fn test_nfold_168bit() {
1786 let result = nfold(b"password", 21);
1788 assert_eq!(
1789 result,
1790 vec![
1791 0x59, 0xE4, 0xA8, 0xCA, 0x7C, 0x03, 0x85, 0xC3, 0xC3, 0x7B, 0x3F, 0x6D, 0x20, 0x00,
1792 0x24, 0x7C, 0xB6, 0xE6, 0xBD, 0x5B, 0x3E,
1793 ]
1794 );
1795 }
1796
1797 #[test]
1800 fn test_aes_ecb_roundtrip() {
1801 let key = [0x42u8; 16];
1802 let block = [0x01u8; 16];
1803 let encrypted = aes_ecb_encrypt(&key, &block);
1804 let decrypted = aes_ecb_decrypt(&key, &encrypted);
1805 assert_eq!(decrypted, block);
1806 }
1807
1808 #[test]
1809 fn test_aes_ecb_256_roundtrip() {
1810 let key = [0x42u8; 32];
1811 let block = [0xABu8; 16];
1812 let encrypted = aes_ecb_encrypt(&key, &block);
1813 let decrypted = aes_ecb_decrypt(&key, &encrypted);
1814 assert_eq!(decrypted, block);
1815 }
1816
1817 #[test]
1818 fn test_aes_cbc_decrypt_two_blocks() {
1819 let key = [0x00u8; 16];
1820 let p0 = [0u8; 16];
1822 let p1 = [0u8; 16];
1823 let c0 = aes_ecb_encrypt(&key, &p0);
1825 let p1_xor_c0: [u8; 16] = xor_bytes(&p1, &c0).try_into().unwrap();
1826 let c1 = aes_ecb_encrypt(&key, &p1_xor_c0);
1827 let mut ciphertext = Vec::new();
1828 ciphertext.extend_from_slice(&c0);
1829 ciphertext.extend_from_slice(&c1);
1830
1831 let plaintext = aes_cbc_decrypt(&key, &ciphertext).unwrap();
1832 let mut expected = Vec::new();
1833 expected.extend_from_slice(&p0);
1834 expected.extend_from_slice(&p1);
1835 assert_eq!(plaintext, expected);
1836 }
1837
1838 #[test]
1839 fn test_aes_cts_decrypt_single_block() {
1840 let key = [0x00u8; 16];
1841 let plaintext = [0x42u8; 16];
1842 let ciphertext = aes_ecb_encrypt(&key, &plaintext);
1844 let decrypted = aes_cts_decrypt(&key, &ciphertext).unwrap();
1845 assert_eq!(&decrypted[..], &plaintext[..]);
1846 }
1847
1848 #[test]
1849 fn test_derive_key_produces_correct_length() {
1850 let key_128 = [0x42u8; 16];
1851 let derived = derive_key_aes(&key_128, 2, 0xAA);
1852 assert_eq!(derived.len(), 16);
1853
1854 let key_256 = [0x42u8; 32];
1855 let derived = derive_key_aes(&key_256, 2, 0xAA);
1856 assert_eq!(derived.len(), 32);
1857 }
1858
1859 #[test]
1860 fn test_hmac_sha1_produces_20_bytes() {
1861 let result = hmac_sha1(b"key", b"data");
1862 assert_eq!(result.len(), 20);
1863 }
1864
1865 #[test]
1868 fn test_valid_keytab_v2_header() {
1869 let data = [0x05, 0x02, 0x00, 0x00];
1871 let entries = parse_keytab(&data).unwrap();
1872 assert!(entries.is_empty());
1873 }
1874
1875 #[test]
1878 fn test_ticket_flags_parsing() {
1879 let flags = [0x00, 0x40, 0x80, 0x00, 0x00]; assert!(test_bit_flag(&flags, 1)); assert!(!test_bit_flag(&flags, 2)); assert!(test_bit_flag(&flags, 8)); assert!(!test_bit_flag(&flags, 10)); }
1887
1888 #[test]
1891 fn test_kerberos_config_builder() {
1892 let config = KerberosConfig::builder("HTTP/srv@REALM", "REALM")
1893 .keytab_path("/etc/krb5.keytab")
1894 .add_kdc("kdc1:88")
1895 .add_kdc("kdc2:88")
1896 .max_clock_skew_secs(600)
1897 .build();
1898
1899 assert_eq!(config.service_principal, "HTTP/srv@REALM");
1900 assert_eq!(config.realm, "REALM");
1901 assert_eq!(config.keytab_path.as_deref(), Some("/etc/krb5.keytab"));
1902 assert_eq!(config.kdc_addresses, vec!["kdc1:88", "kdc2:88"]);
1903 assert_eq!(config.max_clock_skew_secs, 600);
1904 assert!(!config.allow_delegation);
1905 }
1906
1907 #[test]
1908 fn test_kerberos_config_active_directory() {
1909 let config = KerberosConfig::active_directory("HTTP/srv@AD.COM", "AD.COM");
1910 assert_eq!(config.service_principal, "HTTP/srv@AD.COM");
1911 assert_eq!(config.realm, "AD.COM");
1912 assert!(config.allow_delegation);
1913 let mgr = KerberosManager::new(config);
1915 assert!(mgr.is_ok());
1916 }
1917
1918 #[test]
1919 fn test_kerberos_builder_override() {
1920 let config = KerberosConfig::builder("HTTP/srv@REALM", "REALM")
1921 .allow_delegation(true)
1922 .replay_cache_max_entries(500)
1923 .build();
1924
1925 assert!(config.allow_delegation);
1926 assert_eq!(config.replay_cache_max_entries, 500);
1927 }
1928}