pcapsql_core/tls/
keylog.rs

1//! SSLKEYLOGFILE parser for TLS decryption.
2//!
3//! Parses the NSS Key Log format used by browsers (Chrome, Firefox), curl,
4//! and other TLS implementations for exporting session keys.
5
6use std::collections::HashMap;
7use std::fs::File;
8use std::io::{BufRead, BufReader, Read};
9use std::path::Path;
10use thiserror::Error;
11
12/// Errors that can occur when parsing SSLKEYLOGFILE.
13#[derive(Debug, Error)]
14pub enum KeyLogError {
15    /// I/O error reading the file
16    #[error("I/O error: {0}")]
17    Io(#[from] std::io::Error),
18
19    /// Invalid hex string in key log
20    #[error("Invalid hex at line {line}: {message}")]
21    InvalidHex { line: usize, message: String },
22
23    /// Invalid line format
24    #[error("Invalid format at line {line}: {message}")]
25    InvalidFormat { line: usize, message: String },
26
27    /// Unknown key type
28    #[error("Unknown key type at line {line}: {key_type}")]
29    UnknownKeyType { line: usize, key_type: String },
30}
31
32/// A single entry from an SSLKEYLOGFILE.
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub enum KeyLogEntry {
35    /// TLS 1.2 and earlier: CLIENT_RANDOM <client_random> <master_secret>
36    /// The master_secret is always 48 bytes.
37    ClientRandom {
38        client_random: [u8; 32],
39        master_secret: [u8; 48],
40    },
41
42    /// TLS 1.3: CLIENT_HANDSHAKE_TRAFFIC_SECRET <client_random> <secret>
43    /// Secret length depends on cipher suite hash (32 for SHA-256, 48 for SHA-384).
44    ClientHandshakeTrafficSecret {
45        client_random: [u8; 32],
46        secret: Vec<u8>,
47    },
48
49    /// TLS 1.3: SERVER_HANDSHAKE_TRAFFIC_SECRET <client_random> <secret>
50    ServerHandshakeTrafficSecret {
51        client_random: [u8; 32],
52        secret: Vec<u8>,
53    },
54
55    /// TLS 1.3: CLIENT_TRAFFIC_SECRET_0 <client_random> <secret>
56    /// This is the initial application data secret.
57    ClientTrafficSecret0 {
58        client_random: [u8; 32],
59        secret: Vec<u8>,
60    },
61
62    /// TLS 1.3: SERVER_TRAFFIC_SECRET_0 <client_random> <secret>
63    ServerTrafficSecret0 {
64        client_random: [u8; 32],
65        secret: Vec<u8>,
66    },
67
68    /// TLS 1.3: EXPORTER_SECRET <client_random> <secret>
69    ExporterSecret {
70        client_random: [u8; 32],
71        secret: Vec<u8>,
72    },
73
74    /// TLS 1.3: EARLY_EXPORTER_SECRET <client_random> <secret>
75    /// Used for 0-RTT early data.
76    EarlyExporterSecret {
77        client_random: [u8; 32],
78        secret: Vec<u8>,
79    },
80
81    /// CLIENT_EARLY_TRAFFIC_SECRET <client_random> <secret>
82    /// Used for 0-RTT early data.
83    ClientEarlyTrafficSecret {
84        client_random: [u8; 32],
85        secret: Vec<u8>,
86    },
87}
88
89impl KeyLogEntry {
90    /// Get the client_random for this entry.
91    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/// Collection of key log entries for a single TLS session.
106///
107/// A TLS 1.3 session may have multiple entries (handshake secrets, traffic secrets).
108/// A TLS 1.2 session typically has just one CLIENT_RANDOM entry.
109#[derive(Debug, Clone, Default)]
110pub struct KeyLogEntries {
111    /// TLS 1.2 master secret (from CLIENT_RANDOM entry)
112    pub master_secret: Option<[u8; 48]>,
113
114    /// TLS 1.3 client handshake traffic secret
115    pub client_handshake_traffic_secret: Option<Vec<u8>>,
116
117    /// TLS 1.3 server handshake traffic secret
118    pub server_handshake_traffic_secret: Option<Vec<u8>>,
119
120    /// TLS 1.3 client application traffic secret (initial)
121    pub client_traffic_secret_0: Option<Vec<u8>>,
122
123    /// TLS 1.3 server application traffic secret (initial)
124    pub server_traffic_secret_0: Option<Vec<u8>>,
125
126    /// TLS 1.3 exporter secret
127    pub exporter_secret: Option<Vec<u8>>,
128
129    /// TLS 1.3 early exporter secret (0-RTT)
130    pub early_exporter_secret: Option<Vec<u8>>,
131
132    /// TLS 1.3 client early traffic secret (0-RTT)
133    pub client_early_traffic_secret: Option<Vec<u8>>,
134}
135
136impl KeyLogEntries {
137    /// Check if this has TLS 1.2 keys.
138    pub fn has_tls12_keys(&self) -> bool {
139        self.master_secret.is_some()
140    }
141
142    /// Check if this has TLS 1.3 application keys.
143    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    /// Check if this has TLS 1.3 handshake keys.
148    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    /// Add an entry to this collection.
154    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/// Parsed SSLKEYLOGFILE indexed by client_random for fast lookup.
185#[derive(Debug, Clone)]
186pub struct KeyLog {
187    /// Entries indexed by client_random (32 bytes).
188    entries: HashMap<[u8; 32], KeyLogEntries>,
189
190    /// Total number of entries parsed.
191    entry_count: usize,
192}
193
194impl KeyLog {
195    /// Create an empty KeyLog.
196    pub fn new() -> Self {
197        KeyLog {
198            entries: HashMap::new(),
199            entry_count: 0,
200        }
201    }
202
203    /// Parse a KeyLog from file.
204    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    /// Parse a KeyLog from a string.
211    pub fn parse(content: &str) -> Result<Self, KeyLogError> {
212        Self::from_reader(content.as_bytes())
213    }
214
215    /// Parse a KeyLog from any reader.
216    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; // 1-indexed for error messages
223
224            // Skip empty lines and comments
225            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    /// Add an entry to the keylog.
238    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    /// Look up entries by client_random.
248    pub fn lookup(&self, client_random: &[u8; 32]) -> Option<&KeyLogEntries> {
249        self.entries.get(client_random)
250    }
251
252    /// Look up entries by client_random slice (converts to array).
253    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    /// Get the number of unique sessions (client_randoms) in the keylog.
263    pub fn session_count(&self) -> usize {
264        self.entries.len()
265    }
266
267    /// Get the total number of entries parsed.
268    pub fn entry_count(&self) -> usize {
269        self.entry_count
270    }
271
272    /// Check if the keylog is empty.
273    pub fn is_empty(&self) -> bool {
274        self.entries.is_empty()
275    }
276
277    /// Iterate over all client_randoms.
278    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
289/// Parse a single line from SSLKEYLOGFILE.
290fn 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    // Parse client_random (always 32 bytes = 64 hex chars)
305    let client_random = parse_hex_32(client_random_hex, line_num)?;
306
307    match key_type {
308        "CLIENT_RANDOM" => {
309            // TLS 1.2 master secret is always 48 bytes
310            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
372/// Parse a hex string into a 32-byte array.
373fn 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
387/// Parse a hex string into a 48-byte array.
388fn 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
402/// Parse a hex string into a Vec<u8>.
403fn 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
429/// Convert a hex character to its value.
430fn 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        // Verify each session
513        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        // Lookup with slice
552        assert!(keylog.lookup_slice(&client_random).is_some());
553
554        // Wrong length returns None
555        assert!(keylog.lookup_slice(&[0x01, 0x23]).is_none());
556    }
557
558    #[test]
559    fn test_invalid_hex_length() {
560        // Client random too short
561        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        // SHA-384 produces 48-byte secrets
639        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}