1use std::io::{Read, Write};
17use std::net::{SocketAddr, TcpStream};
18use std::time::{Duration, Instant, SystemTime};
19
20use rand::Rng;
21
22use super::ber::{
23 encode_application, encode_context, encode_generalizedtime, encode_generalstring,
24 encode_integer_i32, encode_integer_u64, encode_sequence, encode_tlv,
25};
26use super::common::{map_io_err, parse_generalized_time, system_time_to_us};
27use crate::time_src::{OffsetMicros, TimeSource, TimeSourceError};
28
29const KRB_ERROR_TAG: u8 = 0x7E; const SEQUENCE_TAG: u8 = 0x30;
32const STIME_TAG: u8 = 0xA4; const SUSEC_TAG: u8 = 0xA5; const GENERALIZED_TIME_TAG: u8 = 0x18;
35const INTEGER_TAG: u8 = 0x02;
36
37pub struct KerberosSource {
38 pub realm: Option<String>,
39 pub stealth_user: String,
40}
41
42impl TimeSource for KerberosSource {
43 fn name(&self) -> &'static str {
44 "kerberos"
45 }
46
47 fn fetch(
48 &self,
49 target: SocketAddr,
50 timeout: Duration,
51 ) -> Result<OffsetMicros, TimeSourceError> {
52 let realm = self
53 .realm
54 .as_deref()
55 .ok_or_else(|| TimeSourceError::Config("no realm configured".into()))?;
56 let krb_addr: SocketAddr = (target.ip(), 88).into();
57 fetch_kerberos(krb_addr, realm, &self.stealth_user, timeout)
58 }
59}
60
61fn fetch_kerberos(
62 addr: SocketAddr,
63 realm: &str,
64 stealth_user: &str,
65 timeout: Duration,
66) -> Result<OffsetMicros, TimeSourceError> {
67 let mut stream =
68 TcpStream::connect_timeout(&addr, timeout).map_err(|e| map_io_err(e, "connect"))?;
69 stream
70 .set_read_timeout(Some(timeout))
71 .map_err(|e| TimeSourceError::Protocol(e.to_string()))?;
72 stream
73 .set_write_timeout(Some(timeout))
74 .map_err(|e| TimeSourceError::Protocol(e.to_string()))?;
75
76 let t_send_sys = SystemTime::now();
77 let t_send = Instant::now();
78
79 let req = build_as_req(realm, stealth_user);
80 let len = (req.len() as u32).to_be_bytes();
82 stream
83 .write_all(&len)
84 .map_err(|e| TimeSourceError::Protocol(e.to_string()))?;
85 stream
86 .write_all(&req)
87 .map_err(|e| TimeSourceError::Protocol(e.to_string()))?;
88
89 let mut len_buf = [0u8; 4];
91 stream
92 .read_exact(&mut len_buf)
93 .map_err(|e| map_io_err(e, "read_length"))?;
94 let resp_len = u32::from_be_bytes(len_buf) as usize;
95
96 if resp_len > 65536 {
97 return Err(TimeSourceError::Protocol(format!(
98 "implausibly large KRB response: {} bytes",
99 resp_len
100 )));
101 }
102 let mut resp = vec![0u8; resp_len];
103 stream
104 .read_exact(&mut resp)
105 .map_err(|e| map_io_err(e, "read_response"))?;
106
107 let rtt = t_send.elapsed();
108
109 let t_mid_us = system_time_to_us(t_send_sys)? + (rtt.as_micros() as i64) / 2;
111
112 let server_us = parse_krb_error(&resp)?;
113 Ok(server_us - t_mid_us)
114}
115
116pub fn parse_krb_error(data: &[u8]) -> Result<i64, TimeSourceError> {
118 let mut pos = 0;
120 let tag = next_byte(data, &mut pos, "KRB-ERROR tag")?;
121 if tag != KRB_ERROR_TAG {
122 return Err(TimeSourceError::Protocol(format!(
123 "expected KRB-ERROR tag 0x{:02X}, got 0x{:02X}",
124 KRB_ERROR_TAG, tag
125 )));
126 }
127
128 skip_der_length(data, &mut pos)?;
130
131 let seq_tag = next_byte(data, &mut pos, "KRB-ERROR SEQUENCE tag")?;
133 if seq_tag != SEQUENCE_TAG {
134 return Err(TimeSourceError::Parse(format!(
135 "expected SEQUENCE tag 0x{:02X}, got 0x{:02X}",
136 SEQUENCE_TAG, seq_tag
137 )));
138 }
139 let seq_len = read_der_length(data, &mut pos)?;
140 let seq_end = pos
141 .checked_add(seq_len)
142 .ok_or_else(|| TimeSourceError::Parse("SEQUENCE overflow".into()))?;
143 if seq_end > data.len() {
144 return Err(TimeSourceError::Parse(
145 "KRB-ERROR SEQUENCE overruns buffer".into(),
146 ));
147 }
148
149 let mut stime_us: Option<i64> = None;
152 let mut susec: Option<u32> = None;
153
154 while pos < seq_end && (stime_us.is_none() || susec.is_none()) {
155 let field_tag = next_byte(data, &mut pos, "field tag")?;
156 let field_len = read_der_length(data, &mut pos)?;
157
158 let field_end = pos
159 .checked_add(field_len)
160 .ok_or_else(|| TimeSourceError::Parse("Field overflow".into()))?;
161 if field_end > data.len() {
162 return Err(TimeSourceError::Parse("DER field overruns buffer".into()));
163 }
164
165 let field_data = &data[pos..field_end];
166 pos = field_end;
167
168 match field_tag {
169 STIME_TAG => {
170 stime_us = Some(parse_context_generalizedtime(field_data)?);
171 }
172 SUSEC_TAG => {
173 susec = Some(parse_context_integer_u32(field_data)?);
174 }
175 _ => { }
176 }
177 }
178
179 let stime =
182 stime_us.ok_or_else(|| TimeSourceError::Parse("KRB-ERROR missing stime [4]".into()))?;
183 let sus = susec.unwrap_or(0);
184
185 Ok(stime + sus as i64)
188}
189
190fn parse_context_generalizedtime(b: &[u8]) -> Result<i64, TimeSourceError> {
192 let mut pos = 0;
193 let tag = next_byte(b, &mut pos, "GeneralizedTime tag")?;
194 if tag != GENERALIZED_TIME_TAG {
195 return Err(TimeSourceError::Parse(format!(
196 "expected GeneralizedTime 0x{:02X}, got 0x{:02X}",
197 GENERALIZED_TIME_TAG, tag
198 )));
199 }
200 let len = read_der_length(b, &mut pos)?;
201 let end_pos = pos
202 .checked_add(len)
203 .ok_or_else(|| TimeSourceError::Parse("GeneralizedTime overflow".into()))?;
204 if end_pos > b.len() {
205 return Err(TimeSourceError::Parse(
206 "GeneralizedTime overruns buffer".into(),
207 ));
208 }
209 let s = std::str::from_utf8(&b[pos..end_pos])
210 .map_err(|_| TimeSourceError::Parse("GeneralizedTime not UTF-8".into()))?;
211 let st = parse_generalized_time(s)?;
212 system_time_to_us(st)
213}
214
215fn parse_context_integer_u32(b: &[u8]) -> Result<u32, TimeSourceError> {
217 let mut pos = 0;
218 let tag = next_byte(b, &mut pos, "INTEGER tag")?;
219 if tag != INTEGER_TAG {
220 return Err(TimeSourceError::Parse(format!(
221 "expected INTEGER 0x{:02X}, got 0x{:02X}",
222 INTEGER_TAG, tag
223 )));
224 }
225 let len = read_der_length(b, &mut pos)?;
226 let end_pos = pos
227 .checked_add(len)
228 .ok_or_else(|| TimeSourceError::Parse("INTEGER overflow".into()))?;
229 if end_pos > b.len() || len > 4 {
230 return Err(TimeSourceError::Parse(format!(
231 "INTEGER len {} out of range",
232 len
233 )));
234 }
235 let mut val = 0u32;
236 for &byte in &b[pos..end_pos] {
237 val = (val << 8) | byte as u32;
238 }
239 Ok(val)
240}
241
242pub fn build_as_req(realm: &str, cname: &str) -> Vec<u8> {
249 let nonce: u32 = rand::thread_rng().gen();
250 let till = kerberos_time_plausible_future();
251
252 let pvno = encode_integer_u64(5);
254 let msg_type = encode_integer_u64(10); let cname_enc = der_principal_name(1, &[cname]); let sname_enc = der_principal_name(2, &["krbtgt", realm]); let realm_enc = encode_generalstring(realm);
261 let till_enc = encode_generalizedtime(&till);
262 let nonce_enc = encode_integer_u64(nonce as u64);
263 let etype_enc = der_etype_sequence(&[18, 17, 23]);
273
274 let req_body_inner = [
276 encode_context(0, &der_bitstring_kdc_options()), encode_context(1, &cname_enc),
278 encode_context(2, &realm_enc),
279 encode_context(3, &sname_enc),
280 encode_context(5, &till_enc),
281 encode_context(7, &nonce_enc),
282 encode_context(8, &etype_enc),
283 ]
284 .concat();
285 let req_body = encode_context(4, &encode_sequence(&req_body_inner));
286
287 let padata = build_pa_pac_request_field();
288
289 let kdc_req_inner = [
291 encode_context(1, &pvno),
292 encode_context(2, &msg_type),
293 padata,
294 req_body,
295 ]
296 .concat();
297 let kdc_req = encode_sequence(&kdc_req_inner);
298
299 encode_application(10, &kdc_req)
301}
302
303fn kerberos_time_plausible_future() -> String {
304 "20370913024805Z".to_string()
312}
313
314fn der_bitstring_kdc_options() -> Vec<u8> {
315 encode_tlv(0x03, &[0x00, 0x40, 0x81, 0x00, 0x10])
320}
321
322fn build_pa_pac_request_field() -> Vec<u8> {
323 let include_pac = encode_context(0, &encode_tlv(0x01, &[0xff])); let pac_req_val = encode_sequence(&include_pac);
328 let pa_item = encode_sequence(
330 &[
331 encode_context(1, &encode_integer_u64(128)),
332 encode_context(2, &encode_tlv(0x04, &pac_req_val)),
333 ]
334 .concat(),
335 );
336 encode_context(3, &encode_sequence(&pa_item))
338}
339
340fn der_principal_name(name_type: u32, names: &[&str]) -> Vec<u8> {
341 let nt = encode_context(0, &encode_integer_u64(name_type as u64));
342 let mut ns_inner = Vec::new();
343 for &name in names {
344 ns_inner.extend_from_slice(&encode_generalstring(name));
345 }
346 let ns = encode_context(1, &encode_sequence(&ns_inner));
347 encode_sequence(&[nt, ns].concat())
348}
349
350fn der_etype_sequence(etypes: &[i32]) -> Vec<u8> {
351 let inner: Vec<u8> = etypes.iter().flat_map(|&e| encode_integer_i32(e)).collect();
356 encode_sequence(&inner)
357}
358
359fn next_byte(data: &[u8], pos: &mut usize, ctx: &str) -> Result<u8, TimeSourceError> {
362 if *pos >= data.len() {
363 return Err(TimeSourceError::Parse(format!("unexpected end at {}", ctx)));
364 }
365 let b = data[*pos];
366 *pos += 1;
367 Ok(b)
368}
369
370fn read_der_length(data: &[u8], pos: &mut usize) -> Result<usize, TimeSourceError> {
371 let b = next_byte(data, pos, "DER length")?;
372 if b < 0x80 {
373 return Ok(b as usize);
374 }
375 let n = (b & 0x7F) as usize;
376 if n == 0 || n > 4 {
377 return Err(TimeSourceError::Parse(format!(
378 "unsupported DER length encoding: 0x{:02X}",
379 b
380 )));
381 }
382 let mut len = 0usize;
383 for _ in 0..n {
384 let byte = next_byte(data, pos, "DER length byte")?;
385 len = (len << 8) | byte as usize;
386 }
387 Ok(len)
388}
389
390fn skip_der_length(data: &[u8], pos: &mut usize) -> Result<(), TimeSourceError> {
391 read_der_length(data, pos)?;
392 Ok(())
393}
394
395#[cfg(test)]
396mod tests {
397 use super::*;
398 use crate::protocols::common::civil_to_days;
399
400 fn sample_krb_error() -> Vec<u8> {
404 let stime_str = "20240115103000Z";
406 let susec_val: u32 = 123456;
407
408 let pvno = encode_context(0, &encode_integer_u64(5));
409 let msg_type = encode_context(1, &encode_integer_u64(30)); let stime_field = encode_context(4, &encode_generalizedtime(stime_str));
411 let susec_field = encode_context(5, &encode_integer_u64(susec_val as u64));
412 let error_code = encode_context(6, &encode_integer_u64(6)); let inner = [pvno, msg_type, stime_field, susec_field, error_code].concat();
415 let seq = encode_sequence(&inner);
416 encode_tlv(0x7E, &seq)
417 }
418
419 #[test]
420 fn parse_krb_error_stime() {
421 let pkt = sample_krb_error();
422 let us = parse_krb_error(&pkt).unwrap();
423
424 let expected_secs: i64 = 1_705_314_600;
427 let expected_us = expected_secs * 1_000_000 + 123_456;
428 assert_eq!(us, expected_us);
429 }
430
431 #[test]
432 fn parse_krb_error_wrong_tag() {
433 let mut pkt = sample_krb_error();
434 pkt[0] = 0x30; assert!(matches!(
436 parse_krb_error(&pkt),
437 Err(TimeSourceError::Protocol(_))
438 ));
439 }
440
441 #[test]
442 fn civil_to_days_epoch() {
443 assert_eq!(civil_to_days(1970, 1, 1).unwrap(), 0);
444 }
445
446 #[test]
447 fn civil_to_days_2024_01_15() {
448 let days = civil_to_days(2024, 1, 15).unwrap();
450 assert_eq!(days, 19737);
451 }
452
453 #[test]
454 fn build_as_req_parseable() {
455 let req = build_as_req("CORP.LOCAL", "admnistrator");
456 assert_eq!(req[0], 0x6A);
458 assert!(req.len() > 50);
460 }
461
462 #[test]
463 fn encode_integer_u64_zero() {
464 let enc = encode_integer_u64(0);
465 assert_eq!(enc, vec![0x02, 0x01, 0x00]);
466 }
467
468 #[test]
469 fn encode_integer_u64_high_bit() {
470 let enc = encode_integer_u64(0xFF);
472 assert_eq!(enc, vec![0x02, 0x02, 0x00, 0xFF]);
473 }
474
475 #[test]
476 fn parse_generalized_time_known() {
477 let us = system_time_to_us(parse_generalized_time("20240115103000Z").unwrap()).unwrap();
479 assert_eq!(us, 1_705_314_600 * 1_000_000);
480 }
481
482 #[test]
483 fn parse_krb_error_rejects_post_sequence_injection() {
484 let valid = sample_krb_error();
487
488 let forged_stime = encode_context(4, &encode_generalizedtime("20990101000000Z"));
490 let forged_susec = encode_context(5, &encode_integer_u64(999_999u64));
491 let mut injected = valid.clone();
492 injected.extend_from_slice(&forged_stime);
493 injected.extend_from_slice(&forged_susec);
494
495 let us = parse_krb_error(&injected).unwrap();
497 let expected = 1_705_314_600i64 * 1_000_000 + 123_456;
498 assert_eq!(us, expected, "post-sequence tag injection must be ignored");
499 }
500
501 use proptest::prelude::*;
502
503 proptest! {
504 #[test]
505 fn parse_krb_error_never_panics(data in proptest::collection::vec(any::<u8>(), 0..512)) {
506 let _ = parse_krb_error(&data);
507 }
508 }
509}