1use std::collections::HashMap;
7use std::fs::File;
8use std::io::{BufRead, BufReader, Read};
9use std::path::Path;
10use thiserror::Error;
11
12#[derive(Debug, Error)]
14pub enum KeyLogError {
15 #[error("I/O error: {0}")]
17 Io(#[from] std::io::Error),
18
19 #[error("Invalid hex at line {line}: {message}")]
21 InvalidHex { line: usize, message: String },
22
23 #[error("Invalid format at line {line}: {message}")]
25 InvalidFormat { line: usize, message: String },
26
27 #[error("Unknown key type at line {line}: {key_type}")]
29 UnknownKeyType { line: usize, key_type: String },
30}
31
32#[derive(Debug, Clone, PartialEq, Eq)]
34pub enum KeyLogEntry {
35 ClientRandom {
38 client_random: [u8; 32],
39 master_secret: [u8; 48],
40 },
41
42 ClientHandshakeTrafficSecret {
45 client_random: [u8; 32],
46 secret: Vec<u8>,
47 },
48
49 ServerHandshakeTrafficSecret {
51 client_random: [u8; 32],
52 secret: Vec<u8>,
53 },
54
55 ClientTrafficSecret0 {
58 client_random: [u8; 32],
59 secret: Vec<u8>,
60 },
61
62 ServerTrafficSecret0 {
64 client_random: [u8; 32],
65 secret: Vec<u8>,
66 },
67
68 ExporterSecret {
70 client_random: [u8; 32],
71 secret: Vec<u8>,
72 },
73
74 EarlyExporterSecret {
77 client_random: [u8; 32],
78 secret: Vec<u8>,
79 },
80
81 ClientEarlyTrafficSecret {
84 client_random: [u8; 32],
85 secret: Vec<u8>,
86 },
87}
88
89impl KeyLogEntry {
90 pub fn client_random(&self) -> &[u8; 32] {
92 match self {
93 KeyLogEntry::ClientRandom { client_random, .. } => client_random,
94 KeyLogEntry::ClientHandshakeTrafficSecret { client_random, .. } => client_random,
95 KeyLogEntry::ServerHandshakeTrafficSecret { client_random, .. } => client_random,
96 KeyLogEntry::ClientTrafficSecret0 { client_random, .. } => client_random,
97 KeyLogEntry::ServerTrafficSecret0 { client_random, .. } => client_random,
98 KeyLogEntry::ExporterSecret { client_random, .. } => client_random,
99 KeyLogEntry::EarlyExporterSecret { client_random, .. } => client_random,
100 KeyLogEntry::ClientEarlyTrafficSecret { client_random, .. } => client_random,
101 }
102 }
103}
104
105#[derive(Debug, Clone, Default)]
110pub struct KeyLogEntries {
111 pub master_secret: Option<[u8; 48]>,
113
114 pub client_handshake_traffic_secret: Option<Vec<u8>>,
116
117 pub server_handshake_traffic_secret: Option<Vec<u8>>,
119
120 pub client_traffic_secret_0: Option<Vec<u8>>,
122
123 pub server_traffic_secret_0: Option<Vec<u8>>,
125
126 pub exporter_secret: Option<Vec<u8>>,
128
129 pub early_exporter_secret: Option<Vec<u8>>,
131
132 pub client_early_traffic_secret: Option<Vec<u8>>,
134}
135
136impl KeyLogEntries {
137 pub fn has_tls12_keys(&self) -> bool {
139 self.master_secret.is_some()
140 }
141
142 pub fn has_tls13_app_keys(&self) -> bool {
144 self.client_traffic_secret_0.is_some() && self.server_traffic_secret_0.is_some()
145 }
146
147 pub fn has_tls13_handshake_keys(&self) -> bool {
149 self.client_handshake_traffic_secret.is_some()
150 && self.server_handshake_traffic_secret.is_some()
151 }
152
153 fn add_entry(&mut self, entry: KeyLogEntry) {
155 match entry {
156 KeyLogEntry::ClientRandom { master_secret, .. } => {
157 self.master_secret = Some(master_secret);
158 }
159 KeyLogEntry::ClientHandshakeTrafficSecret { secret, .. } => {
160 self.client_handshake_traffic_secret = Some(secret);
161 }
162 KeyLogEntry::ServerHandshakeTrafficSecret { secret, .. } => {
163 self.server_handshake_traffic_secret = Some(secret);
164 }
165 KeyLogEntry::ClientTrafficSecret0 { secret, .. } => {
166 self.client_traffic_secret_0 = Some(secret);
167 }
168 KeyLogEntry::ServerTrafficSecret0 { secret, .. } => {
169 self.server_traffic_secret_0 = Some(secret);
170 }
171 KeyLogEntry::ExporterSecret { secret, .. } => {
172 self.exporter_secret = Some(secret);
173 }
174 KeyLogEntry::EarlyExporterSecret { secret, .. } => {
175 self.early_exporter_secret = Some(secret);
176 }
177 KeyLogEntry::ClientEarlyTrafficSecret { secret, .. } => {
178 self.client_early_traffic_secret = Some(secret);
179 }
180 }
181 }
182}
183
184#[derive(Debug, Clone)]
186pub struct KeyLog {
187 entries: HashMap<[u8; 32], KeyLogEntries>,
189
190 entry_count: usize,
192}
193
194impl KeyLog {
195 pub fn new() -> Self {
197 KeyLog {
198 entries: HashMap::new(),
199 entry_count: 0,
200 }
201 }
202
203 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, KeyLogError> {
205 let file = File::open(path)?;
206 let reader = BufReader::new(file);
207 Self::from_reader(reader)
208 }
209
210 pub fn parse(content: &str) -> Result<Self, KeyLogError> {
212 Self::from_reader(content.as_bytes())
213 }
214
215 pub fn from_reader<R: Read>(reader: R) -> Result<Self, KeyLogError> {
217 let reader = BufReader::new(reader);
218 let mut keylog = KeyLog::new();
219
220 for (line_num, line_result) in reader.lines().enumerate() {
221 let line = line_result?;
222 let line_num = line_num + 1; let line = line.trim();
226 if line.is_empty() || line.starts_with('#') {
227 continue;
228 }
229
230 let entry = parse_line(line, line_num)?;
231 keylog.add_entry(entry);
232 }
233
234 Ok(keylog)
235 }
236
237 fn add_entry(&mut self, entry: KeyLogEntry) {
239 let client_random = *entry.client_random();
240 self.entries
241 .entry(client_random)
242 .or_default()
243 .add_entry(entry);
244 self.entry_count += 1;
245 }
246
247 pub fn lookup(&self, client_random: &[u8; 32]) -> Option<&KeyLogEntries> {
249 self.entries.get(client_random)
250 }
251
252 pub fn lookup_slice(&self, client_random: &[u8]) -> Option<&KeyLogEntries> {
254 if client_random.len() != 32 {
255 return None;
256 }
257 let mut arr = [0u8; 32];
258 arr.copy_from_slice(client_random);
259 self.lookup(&arr)
260 }
261
262 pub fn session_count(&self) -> usize {
264 self.entries.len()
265 }
266
267 pub fn entry_count(&self) -> usize {
269 self.entry_count
270 }
271
272 pub fn is_empty(&self) -> bool {
274 self.entries.is_empty()
275 }
276
277 pub fn client_randoms(&self) -> impl Iterator<Item = &[u8; 32]> {
279 self.entries.keys()
280 }
281}
282
283impl Default for KeyLog {
284 fn default() -> Self {
285 Self::new()
286 }
287}
288
289fn parse_line(line: &str, line_num: usize) -> Result<KeyLogEntry, KeyLogError> {
291 let parts: Vec<&str> = line.split_whitespace().collect();
292
293 if parts.len() != 3 {
294 return Err(KeyLogError::InvalidFormat {
295 line: line_num,
296 message: format!("expected 3 space-separated fields, got {}", parts.len()),
297 });
298 }
299
300 let key_type = parts[0];
301 let client_random_hex = parts[1];
302 let secret_hex = parts[2];
303
304 let client_random = parse_hex_32(client_random_hex, line_num)?;
306
307 match key_type {
308 "CLIENT_RANDOM" => {
309 let master_secret = parse_hex_48(secret_hex, line_num)?;
311 Ok(KeyLogEntry::ClientRandom {
312 client_random,
313 master_secret,
314 })
315 }
316 "CLIENT_HANDSHAKE_TRAFFIC_SECRET" => {
317 let secret = parse_hex_vec(secret_hex, line_num)?;
318 Ok(KeyLogEntry::ClientHandshakeTrafficSecret {
319 client_random,
320 secret,
321 })
322 }
323 "SERVER_HANDSHAKE_TRAFFIC_SECRET" => {
324 let secret = parse_hex_vec(secret_hex, line_num)?;
325 Ok(KeyLogEntry::ServerHandshakeTrafficSecret {
326 client_random,
327 secret,
328 })
329 }
330 "CLIENT_TRAFFIC_SECRET_0" => {
331 let secret = parse_hex_vec(secret_hex, line_num)?;
332 Ok(KeyLogEntry::ClientTrafficSecret0 {
333 client_random,
334 secret,
335 })
336 }
337 "SERVER_TRAFFIC_SECRET_0" => {
338 let secret = parse_hex_vec(secret_hex, line_num)?;
339 Ok(KeyLogEntry::ServerTrafficSecret0 {
340 client_random,
341 secret,
342 })
343 }
344 "EXPORTER_SECRET" => {
345 let secret = parse_hex_vec(secret_hex, line_num)?;
346 Ok(KeyLogEntry::ExporterSecret {
347 client_random,
348 secret,
349 })
350 }
351 "EARLY_EXPORTER_SECRET" => {
352 let secret = parse_hex_vec(secret_hex, line_num)?;
353 Ok(KeyLogEntry::EarlyExporterSecret {
354 client_random,
355 secret,
356 })
357 }
358 "CLIENT_EARLY_TRAFFIC_SECRET" => {
359 let secret = parse_hex_vec(secret_hex, line_num)?;
360 Ok(KeyLogEntry::ClientEarlyTrafficSecret {
361 client_random,
362 secret,
363 })
364 }
365 _ => Err(KeyLogError::UnknownKeyType {
366 line: line_num,
367 key_type: key_type.to_string(),
368 }),
369 }
370}
371
372fn parse_hex_32(hex: &str, line: usize) -> Result<[u8; 32], KeyLogError> {
374 if hex.len() != 64 {
375 return Err(KeyLogError::InvalidHex {
376 line,
377 message: format!("expected 64 hex chars for client_random, got {}", hex.len()),
378 });
379 }
380
381 let bytes = parse_hex_vec(hex, line)?;
382 let mut arr = [0u8; 32];
383 arr.copy_from_slice(&bytes);
384 Ok(arr)
385}
386
387fn parse_hex_48(hex: &str, line: usize) -> Result<[u8; 48], KeyLogError> {
389 if hex.len() != 96 {
390 return Err(KeyLogError::InvalidHex {
391 line,
392 message: format!("expected 96 hex chars for master_secret, got {}", hex.len()),
393 });
394 }
395
396 let bytes = parse_hex_vec(hex, line)?;
397 let mut arr = [0u8; 48];
398 arr.copy_from_slice(&bytes);
399 Ok(arr)
400}
401
402fn parse_hex_vec(hex: &str, line: usize) -> Result<Vec<u8>, KeyLogError> {
404 if !hex.len().is_multiple_of(2) {
405 return Err(KeyLogError::InvalidHex {
406 line,
407 message: "hex string has odd length".to_string(),
408 });
409 }
410
411 let mut bytes = Vec::with_capacity(hex.len() / 2);
412 let mut chars = hex.chars();
413
414 while let (Some(h), Some(l)) = (chars.next(), chars.next()) {
415 let high = hex_digit(h).ok_or_else(|| KeyLogError::InvalidHex {
416 line,
417 message: format!("invalid hex character: {h}"),
418 })?;
419 let low = hex_digit(l).ok_or_else(|| KeyLogError::InvalidHex {
420 line,
421 message: format!("invalid hex character: {l}"),
422 })?;
423 bytes.push((high << 4) | low);
424 }
425
426 Ok(bytes)
427}
428
429fn hex_digit(c: char) -> Option<u8> {
431 match c {
432 '0'..='9' => Some(c as u8 - b'0'),
433 'a'..='f' => Some(c as u8 - b'a' + 10),
434 'A'..='F' => Some(c as u8 - b'A' + 10),
435 _ => None,
436 }
437}
438
439#[cfg(test)]
440mod tests {
441 use super::*;
442
443 #[test]
444 fn test_parse_client_random() {
445 let content = "CLIENT_RANDOM 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f";
446
447 let keylog = KeyLog::parse(content).unwrap();
448
449 assert_eq!(keylog.session_count(), 1);
450 assert_eq!(keylog.entry_count(), 1);
451
452 let client_random: [u8; 32] = [
453 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab,
454 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67,
455 0x89, 0xab, 0xcd, 0xef,
456 ];
457
458 let entries = keylog.lookup(&client_random).unwrap();
459 assert!(entries.has_tls12_keys());
460 assert!(!entries.has_tls13_app_keys());
461
462 let master_secret = entries.master_secret.unwrap();
463 assert_eq!(master_secret[0], 0x00);
464 assert_eq!(master_secret[47], 0x2f);
465 }
466
467 #[test]
468 fn test_parse_tls13_secrets() {
469 let content = r#"
470# TLS 1.3 session
471CLIENT_HANDSHAKE_TRAFFIC_SECRET 0000000000000000000000000000000000000000000000000000000000000001 aabbccdd00112233445566778899aabbccddeeff00112233445566778899aabb
472SERVER_HANDSHAKE_TRAFFIC_SECRET 0000000000000000000000000000000000000000000000000000000000000001 11223344556677889900aabbccddeeff00112233445566778899aabbccddeeff
473CLIENT_TRAFFIC_SECRET_0 0000000000000000000000000000000000000000000000000000000000000001 deadbeefcafebabe0102030405060708090a0b0c0d0e0f101112131415161718
474SERVER_TRAFFIC_SECRET_0 0000000000000000000000000000000000000000000000000000000000000001 cafebabe12345678deadbeef87654321abcdef01234567890abcdef012345678
475"#;
476
477 let keylog = KeyLog::parse(content).unwrap();
478
479 assert_eq!(keylog.session_count(), 1);
480 assert_eq!(keylog.entry_count(), 4);
481
482 let client_random: [u8; 32] = {
483 let mut arr = [0u8; 32];
484 arr[31] = 0x01;
485 arr
486 };
487
488 let entries = keylog.lookup(&client_random).unwrap();
489 assert!(!entries.has_tls12_keys());
490 assert!(entries.has_tls13_app_keys());
491 assert!(entries.has_tls13_handshake_keys());
492
493 assert!(entries.client_handshake_traffic_secret.is_some());
494 assert!(entries.server_handshake_traffic_secret.is_some());
495 assert!(entries.client_traffic_secret_0.is_some());
496 assert!(entries.server_traffic_secret_0.is_some());
497 }
498
499 #[test]
500 fn test_parse_multiple_sessions() {
501 let content = r#"
502CLIENT_RANDOM aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f
503CLIENT_RANDOM bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f000102030405060708090a0b0c0d0e0f
504CLIENT_RANDOM cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc 202122232425262728292a2b2c2d2e2f000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
505"#;
506
507 let keylog = KeyLog::parse(content).unwrap();
508
509 assert_eq!(keylog.session_count(), 3);
510 assert_eq!(keylog.entry_count(), 3);
511
512 let cr_a = [0xaa; 32];
514 let cr_b = [0xbb; 32];
515 let cr_c = [0xcc; 32];
516 let cr_missing = [0xdd; 32];
517
518 assert!(keylog.lookup(&cr_a).is_some());
519 assert!(keylog.lookup(&cr_b).is_some());
520 assert!(keylog.lookup(&cr_c).is_some());
521 assert!(keylog.lookup(&cr_missing).is_none());
522 }
523
524 #[test]
525 fn test_skip_comments_and_empty_lines() {
526 let content = r#"
527# This is a comment
528 # Indented comment
529
530CLIENT_RANDOM 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f
531
532# Another comment
533"#;
534
535 let keylog = KeyLog::parse(content).unwrap();
536 assert_eq!(keylog.session_count(), 1);
537 }
538
539 #[test]
540 fn test_lookup_slice() {
541 let content = "CLIENT_RANDOM 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f";
542
543 let keylog = KeyLog::parse(content).unwrap();
544
545 let client_random: [u8; 32] = [
546 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab,
547 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67,
548 0x89, 0xab, 0xcd, 0xef,
549 ];
550
551 assert!(keylog.lookup_slice(&client_random).is_some());
553
554 assert!(keylog.lookup_slice(&[0x01, 0x23]).is_none());
556 }
557
558 #[test]
559 fn test_invalid_hex_length() {
560 let content = "CLIENT_RANDOM 0123456789abcdef 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f";
562
563 let result = KeyLog::parse(content);
564 assert!(matches!(
565 result,
566 Err(KeyLogError::InvalidHex { line: 1, .. })
567 ));
568 }
569
570 #[test]
571 fn test_invalid_hex_char() {
572 let content = "CLIENT_RANDOM zzzz456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f";
573
574 let result = KeyLog::parse(content);
575 assert!(matches!(
576 result,
577 Err(KeyLogError::InvalidHex { line: 1, .. })
578 ));
579 }
580
581 #[test]
582 fn test_unknown_key_type() {
583 let content = "UNKNOWN_TYPE 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef deadbeef";
584
585 let result = KeyLog::parse(content);
586 assert!(matches!(
587 result,
588 Err(KeyLogError::UnknownKeyType {
589 line: 1,
590 key_type,
591 }) if key_type == "UNKNOWN_TYPE"
592 ));
593 }
594
595 #[test]
596 fn test_invalid_format_wrong_fields() {
597 let content = "CLIENT_RANDOM only_two_fields";
598
599 let result = KeyLog::parse(content);
600 assert!(matches!(
601 result,
602 Err(KeyLogError::InvalidFormat { line: 1, .. })
603 ));
604 }
605
606 #[test]
607 fn test_case_insensitive_hex() {
608 let content = "CLIENT_RANDOM AABBCCDD00112233445566778899aabbccddeeff00112233445566778899AABB 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f";
609
610 let keylog = KeyLog::parse(content).unwrap();
611 assert_eq!(keylog.session_count(), 1);
612 }
613
614 #[test]
615 fn test_client_randoms_iterator() {
616 let content = r#"
617CLIENT_RANDOM aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f
618CLIENT_RANDOM bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f000102030405060708090a0b0c0d0e0f
619"#;
620
621 let keylog = KeyLog::parse(content).unwrap();
622
623 let randoms: Vec<_> = keylog.client_randoms().collect();
624 assert_eq!(randoms.len(), 2);
625 }
626
627 #[test]
628 fn test_empty_keylog() {
629 let content = "# Just comments\n\n";
630 let keylog = KeyLog::parse(content).unwrap();
631 assert!(keylog.is_empty());
632 assert_eq!(keylog.session_count(), 0);
633 assert_eq!(keylog.entry_count(), 0);
634 }
635
636 #[test]
637 fn test_sha384_traffic_secret() {
638 let content = "CLIENT_TRAFFIC_SECRET_0 0000000000000000000000000000000000000000000000000000000000000001 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f";
640
641 let keylog = KeyLog::parse(content).unwrap();
642
643 let client_random: [u8; 32] = {
644 let mut arr = [0u8; 32];
645 arr[31] = 0x01;
646 arr
647 };
648
649 let entries = keylog.lookup(&client_random).unwrap();
650 let secret = entries.client_traffic_secret_0.as_ref().unwrap();
651 assert_eq!(secret.len(), 48);
652 }
653}