1use std::io::{Read, Write};
17use std::net::{SocketAddr, TcpStream};
18use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
19
20use rand::Rng;
21
22use super::common::{parse_generalized_time, system_time_to_us};
23use crate::time_src::{OffsetMicros, TimeSource, TimeSourceError};
24
25const KRB_ERROR_TAG: u8 = 0x7E; const SEQUENCE_TAG: u8 = 0x30;
28const STIME_TAG: u8 = 0xA4; const SUSEC_TAG: u8 = 0xA5; const GENERALIZED_TIME_TAG: u8 = 0x18;
31const INTEGER_TAG: u8 = 0x02;
32
33pub struct KerberosSource {
34 pub realm: Option<String>,
35 pub stealth_user: String,
36}
37
38impl TimeSource for KerberosSource {
39 fn name(&self) -> &'static str {
40 "kerberos"
41 }
42
43 fn fetch(
44 &self,
45 target: SocketAddr,
46 timeout: Duration,
47 ) -> Result<OffsetMicros, TimeSourceError> {
48 let realm = self
49 .realm
50 .as_deref()
51 .ok_or_else(|| TimeSourceError::Config("no realm configured".into()))?;
52 let krb_addr: SocketAddr = (target.ip(), 88).into();
53 fetch_kerberos(krb_addr, realm, &self.stealth_user, timeout)
54 }
55}
56
57fn fetch_kerberos(
58 addr: SocketAddr,
59 realm: &str,
60 stealth_user: &str,
61 timeout: Duration,
62) -> Result<OffsetMicros, TimeSourceError> {
63 let mut stream = TcpStream::connect_timeout(&addr, timeout).map_err(map_io_err)?;
64 stream
65 .set_read_timeout(Some(timeout))
66 .map_err(|e| TimeSourceError::Protocol(e.to_string()))?;
67 stream
68 .set_write_timeout(Some(timeout))
69 .map_err(|e| TimeSourceError::Protocol(e.to_string()))?;
70
71 let t_send_sys = SystemTime::now();
72 let t_send = Instant::now();
73
74 let req = build_as_req(realm, stealth_user);
75 let len = (req.len() as u32).to_be_bytes();
77 stream
78 .write_all(&len)
79 .map_err(|e| TimeSourceError::Protocol(e.to_string()))?;
80 stream
81 .write_all(&req)
82 .map_err(|e| TimeSourceError::Protocol(e.to_string()))?;
83
84 let mut len_buf = [0u8; 4];
86 stream.read_exact(&mut len_buf).map_err(map_io_err)?;
87 let resp_len = u32::from_be_bytes(len_buf) as usize;
88
89 if resp_len > 65536 {
90 return Err(TimeSourceError::Protocol(format!(
91 "implausibly large KRB response: {} bytes",
92 resp_len
93 )));
94 }
95 let mut resp = vec![0u8; resp_len];
96 stream.read_exact(&mut resp).map_err(map_io_err)?;
97
98 let rtt = t_send.elapsed();
99
100 let t_mid_us = system_time_to_us(t_send_sys)? + (rtt.as_micros() as i64) / 2;
102
103 let server_us = parse_krb_error(&resp)?;
104 Ok(server_us - t_mid_us)
105}
106
107pub fn parse_krb_error(data: &[u8]) -> Result<i64, TimeSourceError> {
109 let mut pos = 0;
111 let tag = next_byte(data, &mut pos, "KRB-ERROR tag")?;
112 if tag != KRB_ERROR_TAG {
113 return Err(TimeSourceError::Protocol(format!(
114 "expected KRB-ERROR tag 0x{:02X}, got 0x{:02X}",
115 KRB_ERROR_TAG, tag
116 )));
117 }
118
119 skip_der_length(data, &mut pos)?;
121
122 let seq_tag = next_byte(data, &mut pos, "KRB-ERROR SEQUENCE tag")?;
124 if seq_tag != SEQUENCE_TAG {
125 return Err(TimeSourceError::Parse(format!(
126 "expected SEQUENCE tag 0x{:02X}, got 0x{:02X}",
127 SEQUENCE_TAG, seq_tag
128 )));
129 }
130 let seq_len = read_der_length(data, &mut pos)?;
131 let seq_end = pos
132 .checked_add(seq_len)
133 .ok_or_else(|| TimeSourceError::Parse("SEQUENCE overflow".into()))?;
134 if seq_end > data.len() {
135 return Err(TimeSourceError::Parse(
136 "KRB-ERROR SEQUENCE overruns buffer".into(),
137 ));
138 }
139
140 let mut stime_us: Option<i64> = None;
143 let mut susec: Option<u32> = None;
144
145 while pos < seq_end && (stime_us.is_none() || susec.is_none()) {
146 let field_tag = next_byte(data, &mut pos, "field tag")?;
147 let field_len = read_der_length(data, &mut pos)?;
148
149 let field_end = pos
150 .checked_add(field_len)
151 .ok_or_else(|| TimeSourceError::Parse("Field overflow".into()))?;
152 if field_end > data.len() {
153 return Err(TimeSourceError::Parse("DER field overruns buffer".into()));
154 }
155
156 let field_data = &data[pos..field_end];
157 pos = field_end;
158
159 match field_tag {
160 STIME_TAG => {
161 stime_us = Some(parse_context_generalizedtime(field_data)?);
162 }
163 SUSEC_TAG => {
164 susec = Some(parse_context_integer_u32(field_data)?);
165 }
166 _ => { }
167 }
168 }
169
170 let stime =
173 stime_us.ok_or_else(|| TimeSourceError::Parse("KRB-ERROR missing stime [4]".into()))?;
174 let sus = susec.unwrap_or(0);
175
176 Ok(stime + sus as i64)
179}
180
181fn parse_context_generalizedtime(b: &[u8]) -> Result<i64, TimeSourceError> {
183 let mut pos = 0;
184 let tag = next_byte(b, &mut pos, "GeneralizedTime tag")?;
185 if tag != GENERALIZED_TIME_TAG {
186 return Err(TimeSourceError::Parse(format!(
187 "expected GeneralizedTime 0x{:02X}, got 0x{:02X}",
188 GENERALIZED_TIME_TAG, tag
189 )));
190 }
191 let len = read_der_length(b, &mut pos)?;
192 let end_pos = pos
193 .checked_add(len)
194 .ok_or_else(|| TimeSourceError::Parse("GeneralizedTime overflow".into()))?;
195 if end_pos > b.len() {
196 return Err(TimeSourceError::Parse(
197 "GeneralizedTime overruns buffer".into(),
198 ));
199 }
200 let s = std::str::from_utf8(&b[pos..end_pos])
201 .map_err(|_| TimeSourceError::Parse("GeneralizedTime not UTF-8".into()))?;
202 let st = parse_generalized_time(s)?;
203 system_time_to_us(st)
204}
205
206fn parse_context_integer_u32(b: &[u8]) -> Result<u32, TimeSourceError> {
208 let mut pos = 0;
209 let tag = next_byte(b, &mut pos, "INTEGER tag")?;
210 if tag != INTEGER_TAG {
211 return Err(TimeSourceError::Parse(format!(
212 "expected INTEGER 0x{:02X}, got 0x{:02X}",
213 INTEGER_TAG, tag
214 )));
215 }
216 let len = read_der_length(b, &mut pos)?;
217 let end_pos = pos
218 .checked_add(len)
219 .ok_or_else(|| TimeSourceError::Parse("INTEGER overflow".into()))?;
220 if end_pos > b.len() || len > 4 {
221 return Err(TimeSourceError::Parse(format!(
222 "INTEGER len {} out of range",
223 len
224 )));
225 }
226 let mut val = 0u32;
227 for &byte in &b[pos..end_pos] {
228 val = (val << 8) | byte as u32;
229 }
230 Ok(val)
231}
232
233pub fn build_as_req(realm: &str, cname: &str) -> Vec<u8> {
242 let nonce: u32 = rand::thread_rng().gen();
243 let till = kerberos_time_plausible_future();
244
245 let pvno = der_integer(5);
247 let msg_type = der_integer(10); let cname_enc = der_principal_name(0, &[cname]); let sname_enc = der_principal_name(2, &["krbtgt", realm]); let realm_enc = der_generalstring(realm);
254 let till_enc = der_generalizedtime(&till);
255 let nonce_enc = der_integer(nonce as u64);
256 let etype_enc = der_etype_sequence(&[17, 18, 23]); let req_body_inner = [
260 der_context(0, &der_bitstring_zero()), der_context(1, &cname_enc),
262 der_context(2, &realm_enc),
263 der_context(3, &sname_enc),
264 der_context(5, &till_enc),
265 der_context(7, &nonce_enc),
266 der_context(8, &etype_enc),
267 ]
268 .concat();
269 let req_body = der_context(4, &der_sequence(&req_body_inner));
270
271 let kdc_req_inner = [der_context(1, &pvno), der_context(2, &msg_type), req_body].concat();
273 let kdc_req = der_sequence(&kdc_req_inner);
274
275 der_application(10, &kdc_req)
277}
278
279fn kerberos_time_plausible_future() -> String {
282 let mut rng = rand::thread_rng();
283 let offset_secs: i64 = 36000 + rng.gen_range(-1800..=1800); let now = SystemTime::now()
285 .duration_since(UNIX_EPOCH)
286 .unwrap_or(Duration::from_secs(0))
287 .as_secs() as i64;
288 format_unix_as_kerberos_time((now + offset_secs) as u64)
289}
290
291fn format_unix_as_kerberos_time(unix_secs: u64) -> String {
292 let days = (unix_secs / 86400) as i64;
293 let secs_in_day = unix_secs % 86400;
294 let hour = secs_in_day / 3600;
295 let min = (secs_in_day % 3600) / 60;
296 let sec = secs_in_day % 60;
297
298 let (year, month, day) = days_to_civil(days);
299 format!(
300 "{:04}{:02}{:02}{:02}{:02}{:02}Z",
301 year, month, day, hour, min, sec
302 )
303}
304
305fn days_to_civil(z: i64) -> (i64, u32, u32) {
307 let z = z + 719468;
308 let era = if z >= 0 { z } else { z - 146096 } / 146097;
309 let doe = (z - era * 146097) as u64;
310 let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
311 let y = yoe as i64 + era * 400;
312 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
313 let mp = (5 * doy + 2) / 153;
314 let d = (doy - (153 * mp + 2) / 5 + 1) as u32;
315 let m = (if mp < 10 { mp + 3 } else { mp - 9 }) as u32;
316 let y = if m <= 2 { y + 1 } else { y };
317 (y, m, d)
318}
319
320fn der_tlv(tag: u8, value: &[u8]) -> Vec<u8> {
323 let mut out = vec![tag];
324 encode_der_length(&mut out, value.len());
325 out.extend_from_slice(value);
326 out
327}
328
329fn encode_der_length(buf: &mut Vec<u8>, len: usize) {
330 if len < 128 {
331 buf.push(len as u8);
332 } else if len < 256 {
333 buf.push(0x81);
334 buf.push(len as u8);
335 } else {
336 buf.push(0x82);
337 buf.push((len >> 8) as u8);
338 buf.push((len & 0xFF) as u8);
339 }
340}
341
342fn der_sequence(inner: &[u8]) -> Vec<u8> {
343 der_tlv(0x30, inner)
344}
345fn der_context(n: u8, inner: &[u8]) -> Vec<u8> {
346 der_tlv(0xA0 | n, inner)
347}
348fn der_application(n: u8, inner: &[u8]) -> Vec<u8> {
349 der_tlv(0x60 | n, inner)
350}
351
352fn der_integer(v: u64) -> Vec<u8> {
353 let mut bytes = v.to_be_bytes().to_vec();
355 while bytes.len() > 1 && bytes[0] == 0 && (bytes[1] & 0x80) == 0 {
356 bytes.remove(0);
357 }
358 if bytes[0] & 0x80 != 0 {
359 bytes.insert(0, 0);
360 }
361 der_tlv(0x02, &bytes)
362}
363
364fn der_generalstring(s: &str) -> Vec<u8> {
365 der_tlv(0x1B, s.as_bytes())
366}
367fn der_generalizedtime(s: &str) -> Vec<u8> {
368 der_tlv(0x18, s.as_bytes())
369}
370
371fn der_bitstring_zero() -> Vec<u8> {
372 der_tlv(0x03, &[0x00, 0x00, 0x00, 0x00, 0x00])
374}
375
376fn der_principal_name(name_type: u32, names: &[&str]) -> Vec<u8> {
377 let nt = der_context(0, &der_integer(name_type as u64));
378 let mut ns_inner = Vec::new();
379 for &name in names {
380 ns_inner.extend_from_slice(&der_generalstring(name));
381 }
382 let ns = der_context(1, &der_sequence(&ns_inner));
383 der_sequence(&[nt, ns].concat())
384}
385
386fn der_etype_sequence(etypes: &[i32]) -> Vec<u8> {
387 let inner: Vec<u8> = etypes.iter().flat_map(|&e| der_integer(e as u64)).collect();
388 der_sequence(&inner)
389}
390
391fn next_byte(data: &[u8], pos: &mut usize, ctx: &str) -> Result<u8, TimeSourceError> {
394 if *pos >= data.len() {
395 return Err(TimeSourceError::Parse(format!("unexpected end at {}", ctx)));
396 }
397 let b = data[*pos];
398 *pos += 1;
399 Ok(b)
400}
401
402fn read_der_length(data: &[u8], pos: &mut usize) -> Result<usize, TimeSourceError> {
403 let b = next_byte(data, pos, "DER length")?;
404 if b < 0x80 {
405 return Ok(b as usize);
406 }
407 let n = (b & 0x7F) as usize;
408 if n == 0 || n > 4 {
409 return Err(TimeSourceError::Parse(format!(
410 "unsupported DER length encoding: 0x{:02X}",
411 b
412 )));
413 }
414 let mut len = 0usize;
415 for _ in 0..n {
416 let byte = next_byte(data, pos, "DER length byte")?;
417 len = (len << 8) | byte as usize;
418 }
419 Ok(len)
420}
421
422fn skip_der_length(data: &[u8], pos: &mut usize) -> Result<(), TimeSourceError> {
423 read_der_length(data, pos)?;
424 Ok(())
425}
426
427fn map_io_err(e: std::io::Error) -> TimeSourceError {
428 use std::io::ErrorKind::*;
429 match e.kind() {
430 TimedOut | WouldBlock => TimeSourceError::Timeout,
431 ConnectionRefused => TimeSourceError::Refused,
432 _ => TimeSourceError::Protocol(e.to_string()),
433 }
434}
435
436#[cfg(test)]
437mod tests {
438 use super::*;
439 use crate::protocols::common::civil_to_days;
440
441 fn sample_krb_error() -> Vec<u8> {
445 let stime_str = "20240115103000Z";
447 let susec_val: u32 = 123456;
448
449 let pvno = der_context(0, &der_integer(5));
450 let msg_type = der_context(1, &der_integer(30)); let stime_field = der_context(4, &der_generalizedtime(stime_str));
452 let susec_field = der_context(5, &der_integer(susec_val as u64));
453 let error_code = der_context(6, &der_integer(6)); let inner = [pvno, msg_type, stime_field, susec_field, error_code].concat();
456 let seq = der_sequence(&inner);
457 der_tlv(0x7E, &seq)
458 }
459
460 #[test]
461 fn parse_krb_error_stime() {
462 let pkt = sample_krb_error();
463 let us = parse_krb_error(&pkt).unwrap();
464
465 let expected_secs: i64 = 1_705_314_600;
468 let expected_us = expected_secs * 1_000_000 + 123_456;
469 assert_eq!(us, expected_us);
470 }
471
472 #[test]
473 fn parse_krb_error_wrong_tag() {
474 let mut pkt = sample_krb_error();
475 pkt[0] = 0x30; assert!(matches!(
477 parse_krb_error(&pkt),
478 Err(TimeSourceError::Protocol(_))
479 ));
480 }
481
482 #[test]
483 fn civil_to_days_epoch() {
484 assert_eq!(civil_to_days(1970, 1, 1).unwrap(), 0);
485 }
486
487 #[test]
488 fn civil_to_days_2024_01_15() {
489 let days = civil_to_days(2024, 1, 15).unwrap();
491 assert_eq!(days, 19737);
492 }
493
494 #[test]
495 fn build_as_req_parseable() {
496 let req = build_as_req("CORP.LOCAL", "admnistrator");
497 assert_eq!(req[0], 0x6A);
499 assert!(req.len() > 50);
501 }
502
503 #[test]
504 fn der_integer_zero() {
505 let enc = der_integer(0);
506 assert_eq!(enc, vec![0x02, 0x01, 0x00]);
507 }
508
509 #[test]
510 fn der_integer_high_bit() {
511 let enc = der_integer(0xFF);
513 assert_eq!(enc, vec![0x02, 0x02, 0x00, 0xFF]);
514 }
515
516 #[test]
517 fn parse_generalized_time_known() {
518 let us = system_time_to_us(parse_generalized_time("20240115103000Z").unwrap()).unwrap();
520 assert_eq!(us, 1_705_314_600 * 1_000_000);
521 }
522
523 #[test]
524 fn parse_krb_error_rejects_post_sequence_injection() {
525 let valid = sample_krb_error();
528
529 let forged_stime = der_context(4, &der_generalizedtime("20990101000000Z"));
531 let forged_susec = der_context(5, &der_integer(999_999u64));
532 let mut injected = valid.clone();
533 injected.extend_from_slice(&forged_stime);
534 injected.extend_from_slice(&forged_susec);
535
536 let us = parse_krb_error(&injected).unwrap();
538 let expected = 1_705_314_600i64 * 1_000_000 + 123_456;
539 assert_eq!(us, expected, "post-sequence tag injection must be ignored");
540 }
541}