pcapsql_core/protocol/
tls.rs

1//! TLS protocol parser.
2//!
3//! Parses TLS (Transport Layer Security) records using the `tls-parser` crate
4//! from the Rusticata project, which is designed for passive network analysis.
5//!
6//! ## Features
7//!
8//! - Full TLS 1.0-1.3 handshake parsing
9//! - Complete IANA cipher suite coverage
10//! - Extension parsing (SNI, ALPN, supported_versions, signature_algorithms, etc.)
11//! - JA3/JA3S fingerprinting for threat hunting
12//! - Alert message decoding
13//! - Foundation fields for future TLS decryption support
14//!
15//! ## Decryption Foundation
16//!
17//! This parser extracts `client_random`, `server_random`, and `session_id` fields
18//! which are essential for TLS decryption when used with SSLKEYLOGFILE.
19
20use compact_str::CompactString;
21use smallvec::SmallVec;
22use tls_parser::{
23    parse_tls_extensions, parse_tls_plaintext, TlsCipherSuite, TlsExtension, TlsMessage,
24    TlsMessageHandshake, TlsVersion,
25};
26
27use super::{FieldValue, ParseContext, ParseResult, Protocol};
28use crate::schema::{DataKind, FieldDescriptor};
29
30/// TLS/HTTPS port.
31#[allow(dead_code)]
32pub const TLS_PORT: u16 = 443;
33
34/// Common TLS ports for priority matching.
35const TLS_PORTS: &[u16] = &[
36    443,   // HTTPS
37    8443,  // HTTPS alternate
38    993,   // IMAPS
39    995,   // POP3S
40    465,   // SMTPS (submission)
41    636,   // LDAPS
42    853,   // DNS over TLS
43    5061,  // SIPS
44    14433, // Test port (used by testdata/tls/)
45];
46
47/// TLS protocol parser using tls-parser crate.
48#[derive(Debug, Clone, Copy)]
49pub struct TlsProtocol;
50
51impl Protocol for TlsProtocol {
52    fn name(&self) -> &'static str {
53        "tls"
54    }
55
56    fn display_name(&self) -> &'static str {
57        "TLS"
58    }
59
60    fn can_parse(&self, context: &ParseContext) -> Option<u32> {
61        let src_port = context.hint("src_port");
62        let dst_port = context.hint("dst_port");
63
64        // Check for known TLS ports
65        for &port in TLS_PORTS {
66            if src_port == Some(port as u64) || dst_port == Some(port as u64) {
67                return Some(50);
68            }
69        }
70
71        None
72    }
73
74    fn parse<'a>(&self, data: &'a [u8], _context: &ParseContext) -> ParseResult<'a> {
75        // TLS record header is 5 bytes minimum
76        if data.len() < 5 {
77            return ParseResult::error("TLS record too short".to_string(), data);
78        }
79
80        let mut fields = SmallVec::new();
81
82        // Use tls-parser for zero-copy parsing
83        match parse_tls_plaintext(data) {
84            Ok((remaining, record)) => {
85                // Record header fields
86                fields.push(("record_type", FieldValue::UInt8(record.hdr.record_type.0)));
87                fields.push(("record_version", FieldValue::UInt16(record.hdr.version.0)));
88                fields.push((
89                    "version",
90                    FieldValue::OwnedString(CompactString::new(format_tls_version(
91                        record.hdr.version,
92                    ))),
93                ));
94
95                // Process messages in the record
96                for msg in &record.msg {
97                    match msg {
98                        TlsMessage::Handshake(handshake) => {
99                            parse_handshake(handshake, &mut fields);
100                        }
101                        TlsMessage::Alert(alert) => {
102                            fields.push(("alert_level", FieldValue::UInt8(alert.severity.0)));
103                            fields.push(("alert_description", FieldValue::UInt8(alert.code.0)));
104                            fields.push((
105                                "alert_description_str",
106                                FieldValue::OwnedString(CompactString::new(
107                                    format_alert_description(alert.code.0),
108                                )),
109                            ));
110                        }
111                        TlsMessage::Heartbeat(hb) => {
112                            fields.push(("is_heartbeat", FieldValue::Bool(true)));
113                            fields.push(("heartbeat_type", FieldValue::UInt8(hb.heartbeat_type.0)));
114                        }
115                        TlsMessage::ChangeCipherSpec => {
116                            fields.push(("is_change_cipher_spec", FieldValue::Bool(true)));
117                        }
118                        TlsMessage::ApplicationData(app_data) => {
119                            // Application data is encrypted, just note its presence and size
120                            fields.push(("has_app_data", FieldValue::Bool(true)));
121                            fields.push((
122                                "app_data_length",
123                                FieldValue::UInt32(app_data.blob.len() as u32),
124                            ));
125                        }
126                    }
127                }
128
129                ParseResult::success(fields, remaining, SmallVec::new())
130            }
131            Err(nom::Err::Incomplete(_)) => {
132                // Partial record - extract what we can from the header
133                let record_type = data[0];
134                fields.push(("record_type", FieldValue::UInt8(record_type)));
135
136                let version = TlsVersion(u16::from_be_bytes([data[1], data[2]]));
137                fields.push(("record_version", FieldValue::UInt16(version.0)));
138                fields.push((
139                    "version",
140                    FieldValue::OwnedString(CompactString::new(format_tls_version(version))),
141                ));
142
143                ParseResult::partial(fields, &data[5..], "TLS record incomplete".to_string())
144            }
145            Err(e) => ParseResult::error(format!("TLS parse error: {e:?}"), data),
146        }
147    }
148
149    fn schema_fields(&self) -> Vec<FieldDescriptor> {
150        vec![
151            // Record layer
152            FieldDescriptor::new("tls.record_type", DataKind::UInt8).set_nullable(true),
153            FieldDescriptor::new("tls.record_version", DataKind::UInt16).set_nullable(true),
154            FieldDescriptor::new("tls.version", DataKind::String).set_nullable(true),
155            // Handshake
156            FieldDescriptor::new("tls.handshake_type", DataKind::UInt8).set_nullable(true),
157            FieldDescriptor::new("tls.handshake_version", DataKind::UInt16).set_nullable(true),
158            // Decryption foundation fields
159            FieldDescriptor::new("tls.client_random", DataKind::FixedBinary(32)).set_nullable(true),
160            FieldDescriptor::new("tls.server_random", DataKind::FixedBinary(32)).set_nullable(true),
161            FieldDescriptor::new("tls.session_id", DataKind::Binary).set_nullable(true),
162            FieldDescriptor::new("tls.session_id_length", DataKind::UInt8).set_nullable(true),
163            // Cipher suites
164            FieldDescriptor::new("tls.cipher_suites", DataKind::String).set_nullable(true),
165            FieldDescriptor::new("tls.cipher_suite_count", DataKind::UInt16).set_nullable(true),
166            FieldDescriptor::new("tls.selected_cipher", DataKind::String).set_nullable(true),
167            FieldDescriptor::new("tls.selected_cipher_id", DataKind::UInt16).set_nullable(true),
168            // Compression
169            FieldDescriptor::new("tls.compression_methods", DataKind::String).set_nullable(true),
170            FieldDescriptor::new("tls.selected_compression", DataKind::UInt8).set_nullable(true),
171            // Extensions
172            FieldDescriptor::new("tls.sni", DataKind::String).set_nullable(true),
173            FieldDescriptor::new("tls.alpn", DataKind::String).set_nullable(true),
174            FieldDescriptor::new("tls.supported_versions", DataKind::String).set_nullable(true),
175            FieldDescriptor::new("tls.signature_algorithms", DataKind::String).set_nullable(true),
176            FieldDescriptor::new("tls.supported_groups", DataKind::String).set_nullable(true),
177            FieldDescriptor::new("tls.ec_point_formats", DataKind::String).set_nullable(true),
178            FieldDescriptor::new("tls.extensions_length", DataKind::UInt16).set_nullable(true),
179            FieldDescriptor::new("tls.extension_types", DataKind::String).set_nullable(true),
180            // Alerts
181            FieldDescriptor::new("tls.alert_level", DataKind::UInt8).set_nullable(true),
182            FieldDescriptor::new("tls.alert_description", DataKind::UInt8).set_nullable(true),
183            FieldDescriptor::new("tls.alert_description_str", DataKind::String).set_nullable(true),
184            // Heartbeat
185            FieldDescriptor::new("tls.is_heartbeat", DataKind::Bool).set_nullable(true),
186            FieldDescriptor::new("tls.heartbeat_type", DataKind::UInt8).set_nullable(true),
187            // Change cipher spec
188            FieldDescriptor::new("tls.is_change_cipher_spec", DataKind::Bool).set_nullable(true),
189            // Application data
190            FieldDescriptor::new("tls.has_app_data", DataKind::Bool).set_nullable(true),
191            FieldDescriptor::new("tls.app_data_length", DataKind::UInt32).set_nullable(true),
192            // JA3 fingerprinting
193            FieldDescriptor::new("tls.ja3", DataKind::String).set_nullable(true),
194            FieldDescriptor::new("tls.ja3_hash", DataKind::String).set_nullable(true),
195            FieldDescriptor::new("tls.ja3s", DataKind::String).set_nullable(true),
196            FieldDescriptor::new("tls.ja3s_hash", DataKind::String).set_nullable(true),
197            // Certificate info (basic)
198            FieldDescriptor::new("tls.certificate_count", DataKind::UInt16).set_nullable(true),
199        ]
200    }
201
202    fn child_protocols(&self) -> &[&'static str] {
203        &[]
204    }
205
206    fn dependencies(&self) -> &'static [&'static str] {
207        &["tcp"]
208    }
209}
210
211/// Parse a TLS handshake message.
212fn parse_handshake(
213    handshake: &TlsMessageHandshake,
214    fields: &mut SmallVec<[(&'static str, FieldValue); 16]>,
215) {
216    match handshake {
217        TlsMessageHandshake::ClientHello(ch) => {
218            fields.push(("handshake_type", FieldValue::UInt8(1)));
219            fields.push(("handshake_version", FieldValue::UInt16(ch.version.0)));
220
221            // Decryption foundation: client_random (32 bytes)
222            fields.push(("client_random", FieldValue::OwnedBytes(ch.random.to_vec())));
223
224            // Session ID (for session resumption tracking)
225            if let Some(session_id) = ch.session_id {
226                fields.push((
227                    "session_id_length",
228                    FieldValue::UInt8(session_id.len() as u8),
229                ));
230                if !session_id.is_empty() {
231                    fields.push(("session_id", FieldValue::OwnedBytes(session_id.to_vec())));
232                }
233            } else {
234                fields.push(("session_id_length", FieldValue::UInt8(0)));
235            }
236
237            // Cipher suites - full IANA coverage via tls-parser
238            let cipher_count = ch.ciphers.len();
239            fields.push((
240                "cipher_suite_count",
241                FieldValue::UInt16(cipher_count as u16),
242            ));
243
244            // Build cipher suite list (limit to first 50 for reasonable output)
245            let cipher_names: Vec<String> = ch
246                .ciphers
247                .iter()
248                .take(50)
249                .map(|c| cipher_suite_name(c.0))
250                .collect();
251            if !cipher_names.is_empty() {
252                fields.push((
253                    "cipher_suites",
254                    FieldValue::OwnedString(CompactString::new(cipher_names.join(";"))),
255                ));
256            }
257
258            // Compression methods
259            let comp_names: Vec<String> = ch
260                .comp
261                .iter()
262                .map(|c| format_compression_method(c.0))
263                .collect();
264            if !comp_names.is_empty() {
265                fields.push((
266                    "compression_methods",
267                    FieldValue::OwnedString(CompactString::new(comp_names.join(";"))),
268                ));
269            }
270
271            // Parse extensions
272            if let Some(ext_data) = ch.ext {
273                fields.push((
274                    "extensions_length",
275                    FieldValue::UInt16(ext_data.len() as u16),
276                ));
277                parse_extensions(ext_data, fields, true);
278
279                // Compute JA3 fingerprint
280                let ja3_string = compute_ja3(ch);
281                let ja3_hash = format!("{:x}", md5::compute(&ja3_string));
282                fields.push((
283                    "ja3",
284                    FieldValue::OwnedString(CompactString::new(ja3_string)),
285                ));
286                fields.push((
287                    "ja3_hash",
288                    FieldValue::OwnedString(CompactString::new(ja3_hash)),
289                ));
290            }
291        }
292        TlsMessageHandshake::ServerHello(sh) => {
293            fields.push(("handshake_type", FieldValue::UInt8(2)));
294            fields.push(("handshake_version", FieldValue::UInt16(sh.version.0)));
295
296            // Decryption foundation: server_random (32 bytes)
297            fields.push(("server_random", FieldValue::OwnedBytes(sh.random.to_vec())));
298
299            // Session ID
300            if let Some(session_id) = sh.session_id {
301                fields.push((
302                    "session_id_length",
303                    FieldValue::UInt8(session_id.len() as u8),
304                ));
305                if !session_id.is_empty() {
306                    fields.push(("session_id", FieldValue::OwnedBytes(session_id.to_vec())));
307                }
308            } else {
309                fields.push(("session_id_length", FieldValue::UInt8(0)));
310            }
311
312            // Selected cipher suite - essential for decryption
313            let cipher_name = cipher_suite_name(sh.cipher.0);
314            fields.push((
315                "selected_cipher",
316                FieldValue::OwnedString(CompactString::new(cipher_name)),
317            ));
318            fields.push(("selected_cipher_id", FieldValue::UInt16(sh.cipher.0)));
319
320            // Selected compression
321            fields.push(("selected_compression", FieldValue::UInt8(sh.compression.0)));
322
323            // Parse extensions
324            if let Some(ext_data) = sh.ext {
325                fields.push((
326                    "extensions_length",
327                    FieldValue::UInt16(ext_data.len() as u16),
328                ));
329                parse_extensions(ext_data, fields, false);
330
331                // Compute JA3S fingerprint
332                let ja3s_string = compute_ja3s(sh);
333                let ja3s_hash = format!("{:x}", md5::compute(&ja3s_string));
334                fields.push((
335                    "ja3s",
336                    FieldValue::OwnedString(CompactString::new(ja3s_string)),
337                ));
338                fields.push((
339                    "ja3s_hash",
340                    FieldValue::OwnedString(CompactString::new(ja3s_hash)),
341                ));
342            }
343        }
344        TlsMessageHandshake::Certificate(cert) => {
345            fields.push(("handshake_type", FieldValue::UInt8(11)));
346            fields.push((
347                "certificate_count",
348                FieldValue::UInt16(cert.cert_chain.len() as u16),
349            ));
350        }
351        TlsMessageHandshake::ServerKeyExchange(_) => {
352            fields.push(("handshake_type", FieldValue::UInt8(12)));
353        }
354        TlsMessageHandshake::CertificateRequest(_) => {
355            fields.push(("handshake_type", FieldValue::UInt8(13)));
356        }
357        TlsMessageHandshake::ServerDone(_) => {
358            fields.push(("handshake_type", FieldValue::UInt8(14)));
359        }
360        TlsMessageHandshake::CertificateVerify(_) => {
361            fields.push(("handshake_type", FieldValue::UInt8(15)));
362        }
363        TlsMessageHandshake::ClientKeyExchange(_) => {
364            fields.push(("handshake_type", FieldValue::UInt8(16)));
365        }
366        TlsMessageHandshake::Finished(_) => {
367            fields.push(("handshake_type", FieldValue::UInt8(20)));
368        }
369        TlsMessageHandshake::CertificateStatus(_) => {
370            fields.push(("handshake_type", FieldValue::UInt8(22)));
371        }
372        TlsMessageHandshake::NextProtocol(_) => {
373            fields.push(("handshake_type", FieldValue::UInt8(67)));
374        }
375        TlsMessageHandshake::KeyUpdate(_) => {
376            fields.push(("handshake_type", FieldValue::UInt8(24)));
377        }
378        TlsMessageHandshake::HelloRetryRequest(_) => {
379            fields.push(("handshake_type", FieldValue::UInt8(6)));
380        }
381        TlsMessageHandshake::EndOfEarlyData => {
382            fields.push(("handshake_type", FieldValue::UInt8(5)));
383        }
384        TlsMessageHandshake::NewSessionTicket(_) => {
385            fields.push(("handshake_type", FieldValue::UInt8(4)));
386        }
387        TlsMessageHandshake::HelloRequest => {
388            fields.push(("handshake_type", FieldValue::UInt8(0)));
389        }
390        TlsMessageHandshake::ServerHelloV13Draft18(_) => {
391            // Draft TLS 1.3 server hello
392            fields.push(("handshake_type", FieldValue::UInt8(2)));
393        }
394    }
395}
396
397/// Parse TLS extensions and extract relevant fields.
398fn parse_extensions(
399    ext_data: &[u8],
400    fields: &mut SmallVec<[(&'static str, FieldValue); 16]>,
401    _is_client: bool,
402) {
403    if let Ok((_, extensions)) = parse_tls_extensions(ext_data) {
404        // Collect extension types for analysis
405        let mut ext_types: Vec<u16> = Vec::new();
406
407        for ext in &extensions {
408            let ext_type = get_extension_type(ext);
409            ext_types.push(ext_type);
410
411            match ext {
412                TlsExtension::SNI(sni_list) => {
413                    // Extract first hostname from SNI list
414                    for (name_type, name_data) in sni_list {
415                        if name_type.0 == 0 {
416                            // Hostname type
417                            if let Ok(hostname) = std::str::from_utf8(name_data) {
418                                fields.push((
419                                    "sni",
420                                    FieldValue::OwnedString(CompactString::new(hostname)),
421                                ));
422                                break;
423                            }
424                        }
425                    }
426                }
427                TlsExtension::ALPN(alpn_list) => {
428                    let protocols: Vec<&str> = alpn_list
429                        .iter()
430                        .filter_map(|p| std::str::from_utf8(p).ok())
431                        .collect();
432                    if !protocols.is_empty() {
433                        fields.push((
434                            "alpn",
435                            FieldValue::OwnedString(CompactString::new(protocols.join(";"))),
436                        ));
437                    }
438                }
439                TlsExtension::SupportedVersions(versions) => {
440                    let vers: Vec<String> =
441                        versions.iter().map(|v| format_tls_version(*v)).collect();
442                    if !vers.is_empty() {
443                        fields.push((
444                            "supported_versions",
445                            FieldValue::OwnedString(CompactString::new(vers.join(";"))),
446                        ));
447                    }
448                }
449                TlsExtension::SignatureAlgorithms(algs) => {
450                    let alg_names: Vec<String> = algs
451                        .iter()
452                        .map(|a| format_signature_algorithm(*a))
453                        .collect();
454                    if !alg_names.is_empty() {
455                        fields.push((
456                            "signature_algorithms",
457                            FieldValue::OwnedString(CompactString::new(alg_names.join(";"))),
458                        ));
459                    }
460                }
461                TlsExtension::EllipticCurves(curves) => {
462                    let curve_names: Vec<String> =
463                        curves.iter().map(|c| format_named_group(c.0)).collect();
464                    if !curve_names.is_empty() {
465                        fields.push((
466                            "supported_groups",
467                            FieldValue::OwnedString(CompactString::new(curve_names.join(";"))),
468                        ));
469                    }
470                }
471                TlsExtension::EcPointFormats(formats) => {
472                    let format_names: Vec<String> =
473                        formats.iter().map(|f| format_ec_point_format(*f)).collect();
474                    if !format_names.is_empty() {
475                        fields.push((
476                            "ec_point_formats",
477                            FieldValue::OwnedString(CompactString::new(format_names.join(";"))),
478                        ));
479                    }
480                }
481                _ => {}
482            }
483        }
484
485        // Store extension types list
486        if !ext_types.is_empty() {
487            let types_str: Vec<String> = ext_types.iter().map(|t| t.to_string()).collect();
488            fields.push((
489                "extension_types",
490                FieldValue::OwnedString(CompactString::new(types_str.join(","))),
491            ));
492        }
493    }
494}
495
496/// Get the numeric extension type.
497fn get_extension_type(ext: &TlsExtension) -> u16 {
498    match ext {
499        TlsExtension::SNI(_) => 0,
500        TlsExtension::MaxFragmentLength(_) => 1,
501        TlsExtension::StatusRequest(_) => 5,
502        TlsExtension::EllipticCurves(_) => 10,
503        TlsExtension::EcPointFormats(_) => 11,
504        TlsExtension::SignatureAlgorithms(_) => 13,
505        TlsExtension::Heartbeat(_) => 15,
506        TlsExtension::ALPN(_) => 16,
507        TlsExtension::SignedCertificateTimestamp(_) => 18,
508        TlsExtension::Padding(_) => 21,
509        TlsExtension::EncryptThenMac => 22,
510        TlsExtension::ExtendedMasterSecret => 23,
511        TlsExtension::SessionTicket(_) => 35,
512        TlsExtension::PreSharedKey(_) => 41,
513        TlsExtension::EarlyData(_) => 42,
514        TlsExtension::SupportedVersions(_) => 43,
515        TlsExtension::Cookie(_) => 44,
516        TlsExtension::PskExchangeModes(_) => 45,
517        TlsExtension::OidFilters(_) => 48,
518        TlsExtension::PostHandshakeAuth => 49,
519        TlsExtension::KeyShare(_) | TlsExtension::KeyShareOld(_) => 51,
520        TlsExtension::RenegotiationInfo(_) => 65281,
521        TlsExtension::EncryptedServerName { .. } => 65486,
522        TlsExtension::Grease(grease, _) => *grease,
523        TlsExtension::Unknown(t, _) => t.0,
524        _ => 65535,
525    }
526}
527
528/// Compute JA3 fingerprint string for Client Hello.
529///
530/// JA3 format: SSLVersion,Ciphers,Extensions,EllipticCurves,EllipticCurvePointFormats
531fn compute_ja3(ch: &tls_parser::TlsClientHelloContents) -> String {
532    // SSL/TLS Version
533    let version = ch.version.0;
534
535    // Cipher suites (excluding GREASE values)
536    let ciphers: Vec<String> = ch
537        .ciphers
538        .iter()
539        .filter(|c| !is_grease_value(c.0))
540        .map(|c| c.0.to_string())
541        .collect();
542
543    // Parse extensions to get types, curves, and point formats
544    let mut ext_types: Vec<u16> = Vec::new();
545    let mut curves: Vec<u16> = Vec::new();
546    let mut point_formats: Vec<u8> = Vec::new();
547
548    if let Some(ext_data) = ch.ext {
549        if let Ok((_, extensions)) = parse_tls_extensions(ext_data) {
550            for ext in &extensions {
551                let ext_type = get_extension_type(ext);
552                if !is_grease_value(ext_type) {
553                    ext_types.push(ext_type);
554                }
555
556                match ext {
557                    TlsExtension::EllipticCurves(c) => {
558                        curves.extend(c.iter().filter(|v| !is_grease_value(v.0)).map(|v| v.0));
559                    }
560                    TlsExtension::EcPointFormats(f) => {
561                        point_formats.extend(f.iter());
562                    }
563                    _ => {}
564                }
565            }
566        }
567    }
568
569    format!(
570        "{},{},{},{},{}",
571        version,
572        ciphers.join("-"),
573        ext_types
574            .iter()
575            .map(|t| t.to_string())
576            .collect::<Vec<_>>()
577            .join("-"),
578        curves
579            .iter()
580            .map(|c| c.to_string())
581            .collect::<Vec<_>>()
582            .join("-"),
583        point_formats
584            .iter()
585            .map(|p| p.to_string())
586            .collect::<Vec<_>>()
587            .join("-"),
588    )
589}
590
591/// Compute JA3S fingerprint string for Server Hello.
592///
593/// JA3S format: SSLVersion,Cipher,Extensions
594fn compute_ja3s(sh: &tls_parser::TlsServerHelloContents) -> String {
595    // SSL/TLS Version
596    let version = sh.version.0;
597
598    // Selected cipher
599    let cipher = sh.cipher.0;
600
601    // Extension types (excluding GREASE)
602    let mut ext_types: Vec<u16> = Vec::new();
603
604    if let Some(ext_data) = sh.ext {
605        if let Ok((_, extensions)) = parse_tls_extensions(ext_data) {
606            for ext in &extensions {
607                let ext_type = get_extension_type(ext);
608                if !is_grease_value(ext_type) {
609                    ext_types.push(ext_type);
610                }
611            }
612        }
613    }
614
615    format!(
616        "{},{},{}",
617        version,
618        cipher,
619        ext_types
620            .iter()
621            .map(|t| t.to_string())
622            .collect::<Vec<_>>()
623            .join("-"),
624    )
625}
626
627/// Check if a value is a GREASE value (used to prevent ossification).
628fn is_grease_value(val: u16) -> bool {
629    // GREASE values are: 0x0a0a, 0x1a1a, 0x2a2a, ..., 0xfafa
630    val & 0x0f0f == 0x0a0a
631}
632
633/// Format TLS version from TlsVersion.
634fn format_tls_version(version: TlsVersion) -> String {
635    match version.0 {
636        0x0300 => "SSL 3.0".to_string(),
637        0x0301 => "TLS 1.0".to_string(),
638        0x0302 => "TLS 1.1".to_string(),
639        0x0303 => "TLS 1.2".to_string(),
640        0x0304 => "TLS 1.3".to_string(),
641        v if is_grease_value(v) => "GREASE".to_string(),
642        v => format!("Unknown (0x{v:04x})"),
643    }
644}
645
646/// Get cipher suite name from ID using tls-parser's IANA database.
647fn cipher_suite_name(id: u16) -> String {
648    if is_grease_value(id) {
649        return format!("GREASE (0x{id:04x})");
650    }
651
652    TlsCipherSuite::from_id(id)
653        .map(|cs| cs.name.to_string())
654        .unwrap_or_else(|| format!("0x{id:04X}"))
655}
656
657/// Format compression method.
658fn format_compression_method(method: u8) -> String {
659    match method {
660        0 => "null".to_string(),
661        1 => "DEFLATE".to_string(),
662        64 => "LZS".to_string(),
663        _ => format!("0x{method:02x}"),
664    }
665}
666
667/// Format signature algorithm.
668fn format_signature_algorithm(alg: u16) -> String {
669    match alg {
670        0x0201 => "rsa_pkcs1_sha1".to_string(),
671        0x0203 => "ecdsa_sha1".to_string(),
672        0x0401 => "rsa_pkcs1_sha256".to_string(),
673        0x0403 => "ecdsa_secp256r1_sha256".to_string(),
674        0x0501 => "rsa_pkcs1_sha384".to_string(),
675        0x0503 => "ecdsa_secp384r1_sha384".to_string(),
676        0x0601 => "rsa_pkcs1_sha512".to_string(),
677        0x0603 => "ecdsa_secp521r1_sha512".to_string(),
678        0x0804 => "rsa_pss_rsae_sha256".to_string(),
679        0x0805 => "rsa_pss_rsae_sha384".to_string(),
680        0x0806 => "rsa_pss_rsae_sha512".to_string(),
681        0x0807 => "ed25519".to_string(),
682        0x0808 => "ed448".to_string(),
683        0x0809 => "rsa_pss_pss_sha256".to_string(),
684        0x080a => "rsa_pss_pss_sha384".to_string(),
685        0x080b => "rsa_pss_pss_sha512".to_string(),
686        v if is_grease_value(v) => "GREASE".to_string(),
687        _ => format!("0x{alg:04x}"),
688    }
689}
690
691/// Format named group (elliptic curve).
692fn format_named_group(group: u16) -> String {
693    match group {
694        1 => "sect163k1".to_string(),
695        2 => "sect163r1".to_string(),
696        3 => "sect163r2".to_string(),
697        4 => "sect193r1".to_string(),
698        5 => "sect193r2".to_string(),
699        6 => "sect233k1".to_string(),
700        7 => "sect233r1".to_string(),
701        8 => "sect239k1".to_string(),
702        9 => "sect283k1".to_string(),
703        10 => "sect283r1".to_string(),
704        11 => "sect409k1".to_string(),
705        12 => "sect409r1".to_string(),
706        13 => "sect571k1".to_string(),
707        14 => "sect571r1".to_string(),
708        15 => "secp160k1".to_string(),
709        16 => "secp160r1".to_string(),
710        17 => "secp160r2".to_string(),
711        18 => "secp192k1".to_string(),
712        19 => "secp192r1".to_string(),
713        20 => "secp224k1".to_string(),
714        21 => "secp224r1".to_string(),
715        22 => "secp256k1".to_string(),
716        23 => "secp256r1".to_string(),
717        24 => "secp384r1".to_string(),
718        25 => "secp521r1".to_string(),
719        26 => "brainpoolP256r1".to_string(),
720        27 => "brainpoolP384r1".to_string(),
721        28 => "brainpoolP512r1".to_string(),
722        29 => "x25519".to_string(),
723        30 => "x448".to_string(),
724        256 => "ffdhe2048".to_string(),
725        257 => "ffdhe3072".to_string(),
726        258 => "ffdhe4096".to_string(),
727        259 => "ffdhe6144".to_string(),
728        260 => "ffdhe8192".to_string(),
729        v if is_grease_value(v) => "GREASE".to_string(),
730        _ => format!("0x{group:04x}"),
731    }
732}
733
734/// Format EC point format.
735fn format_ec_point_format(fmt: u8) -> String {
736    match fmt {
737        0 => "uncompressed".to_string(),
738        1 => "ansiX962_compressed_prime".to_string(),
739        2 => "ansiX962_compressed_char2".to_string(),
740        _ => format!("0x{fmt:02x}"),
741    }
742}
743
744/// Format TLS alert description.
745fn format_alert_description(code: u8) -> &'static str {
746    match code {
747        0 => "close_notify",
748        10 => "unexpected_message",
749        20 => "bad_record_mac",
750        21 => "decryption_failed",
751        22 => "record_overflow",
752        30 => "decompression_failure",
753        40 => "handshake_failure",
754        41 => "no_certificate",
755        42 => "bad_certificate",
756        43 => "unsupported_certificate",
757        44 => "certificate_revoked",
758        45 => "certificate_expired",
759        46 => "certificate_unknown",
760        47 => "illegal_parameter",
761        48 => "unknown_ca",
762        49 => "access_denied",
763        50 => "decode_error",
764        51 => "decrypt_error",
765        60 => "export_restriction",
766        70 => "protocol_version",
767        71 => "insufficient_security",
768        80 => "internal_error",
769        86 => "inappropriate_fallback",
770        90 => "user_canceled",
771        100 => "no_renegotiation",
772        109 => "missing_extension",
773        110 => "unsupported_extension",
774        111 => "certificate_unobtainable",
775        112 => "unrecognized_name",
776        113 => "bad_certificate_status_response",
777        114 => "bad_certificate_hash_value",
778        115 => "unknown_psk_identity",
779        116 => "certificate_required",
780        120 => "no_application_protocol",
781        _ => "unknown",
782    }
783}
784
785#[cfg(test)]
786mod tests {
787    use super::*;
788
789    /// Create a minimal TLS Client Hello with SNI.
790    fn create_tls_client_hello() -> Vec<u8> {
791        let mut packet = Vec::new();
792
793        // TLS Record Header
794        packet.push(22); // Content type: Handshake
795        packet.push(0x03); // Version major (TLS 1.2)
796        packet.push(0x03); // Version minor
797
798        // We'll calculate length later
799        let length_pos = packet.len();
800        packet.push(0x00); // Length high byte (placeholder)
801        packet.push(0x00); // Length low byte (placeholder)
802
803        let handshake_start = packet.len();
804
805        // Handshake Header
806        packet.push(1); // Type: Client Hello
807        packet.push(0x00); // Length (3 bytes, placeholder)
808        packet.push(0x00);
809        packet.push(0x00);
810
811        let hello_start = packet.len();
812
813        // Client Hello body
814        packet.push(0x03); // Version major
815        packet.push(0x03); // Version minor
816
817        // Random (32 bytes)
818        packet.extend_from_slice(&[0x01; 32]);
819
820        // Session ID (0 length)
821        packet.push(0x00);
822
823        // Cipher Suites (6 bytes = 3 suites)
824        packet.push(0x00);
825        packet.push(0x06);
826        packet.extend_from_slice(&0xC02Fu16.to_be_bytes()); // TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
827        packet.extend_from_slice(&0xC030u16.to_be_bytes()); // TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
828        packet.extend_from_slice(&0x1301u16.to_be_bytes()); // TLS_AES_128_GCM_SHA256
829
830        // Compression methods (1 = null)
831        packet.push(0x01);
832        packet.push(0x00);
833
834        // Extensions
835        let extensions_len_pos = packet.len();
836        packet.push(0x00); // Extensions length (placeholder)
837        packet.push(0x00);
838
839        let extensions_start = packet.len();
840
841        // SNI Extension (type 0)
842        packet.extend_from_slice(&0u16.to_be_bytes()); // Extension type
843
844        let hostname = b"www.example.com";
845        let sni_ext_len = 2 + 1 + 2 + hostname.len(); // list_len + type + name_len + name
846        packet.extend_from_slice(&(sni_ext_len as u16).to_be_bytes()); // Extension length
847
848        // SNI List
849        let sni_list_len = 1 + 2 + hostname.len(); // type + name_len + name
850        packet.extend_from_slice(&(sni_list_len as u16).to_be_bytes());
851        packet.push(0x00); // Name type = hostname
852        packet.extend_from_slice(&(hostname.len() as u16).to_be_bytes());
853        packet.extend_from_slice(hostname);
854
855        // Fix up lengths
856        let extensions_len = packet.len() - extensions_start;
857        packet[extensions_len_pos] = (extensions_len >> 8) as u8;
858        packet[extensions_len_pos + 1] = (extensions_len & 0xFF) as u8;
859
860        let hello_len = packet.len() - hello_start;
861        packet[handshake_start + 1] = ((hello_len >> 16) & 0xFF) as u8;
862        packet[handshake_start + 2] = ((hello_len >> 8) & 0xFF) as u8;
863        packet[handshake_start + 3] = (hello_len & 0xFF) as u8;
864
865        let record_len = packet.len() - handshake_start;
866        packet[length_pos] = (record_len >> 8) as u8;
867        packet[length_pos + 1] = (record_len & 0xFF) as u8;
868
869        packet
870    }
871
872    /// Create a minimal TLS Server Hello.
873    fn create_tls_server_hello() -> Vec<u8> {
874        let mut packet = Vec::new();
875
876        // TLS Record Header
877        packet.push(22); // Handshake
878        packet.push(0x03); // Version major
879        packet.push(0x03); // Version minor
880
881        let length_pos = packet.len();
882        packet.push(0x00);
883        packet.push(0x00);
884
885        let handshake_start = packet.len();
886
887        // Handshake Header
888        packet.push(2); // Server Hello
889        packet.push(0x00);
890        packet.push(0x00);
891        packet.push(0x00);
892
893        let hello_start = packet.len();
894
895        // Server Hello body
896        packet.push(0x03); // Version major
897        packet.push(0x03); // Version minor
898
899        // Random (32 bytes)
900        packet.extend_from_slice(&[0x02; 32]);
901
902        // Session ID (0 length)
903        packet.push(0x00);
904
905        // Selected cipher suite
906        packet.extend_from_slice(&0xC02Fu16.to_be_bytes()); // TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
907
908        // Compression method
909        packet.push(0x00);
910
911        // Fix up lengths
912        let hello_len = packet.len() - hello_start;
913        packet[handshake_start + 1] = ((hello_len >> 16) & 0xFF) as u8;
914        packet[handshake_start + 2] = ((hello_len >> 8) & 0xFF) as u8;
915        packet[handshake_start + 3] = (hello_len & 0xFF) as u8;
916
917        let record_len = packet.len() - handshake_start;
918        packet[length_pos] = (record_len >> 8) as u8;
919        packet[length_pos + 1] = (record_len & 0xFF) as u8;
920
921        packet
922    }
923
924    /// Create a TLS Alert record.
925    fn create_tls_alert() -> Vec<u8> {
926        vec![
927            21, // Record type: Alert
928            0x03, 0x03, // Version: TLS 1.2
929            0x00, 0x02, // Length: 2
930            0x02, // Level: Fatal
931            0x28, // Description: Handshake Failure (40)
932        ]
933    }
934
935    #[test]
936    fn test_can_parse_tls_by_port() {
937        let parser = TlsProtocol;
938
939        // Without hint
940        let ctx1 = ParseContext::new(1);
941        assert!(parser.can_parse(&ctx1).is_none());
942
943        // With dst_port 443
944        let mut ctx2 = ParseContext::new(1);
945        ctx2.insert_hint("dst_port", 443);
946        assert!(parser.can_parse(&ctx2).is_some());
947
948        // With src_port 443
949        let mut ctx3 = ParseContext::new(1);
950        ctx3.insert_hint("src_port", 443);
951        assert!(parser.can_parse(&ctx3).is_some());
952
953        // With other TLS ports
954        let mut ctx4 = ParseContext::new(1);
955        ctx4.insert_hint("dst_port", 8443);
956        assert!(parser.can_parse(&ctx4).is_some());
957    }
958
959    #[test]
960    fn test_parse_tls_client_hello() {
961        let packet = create_tls_client_hello();
962
963        let parser = TlsProtocol;
964        let mut context = ParseContext::new(1);
965        context.insert_hint("dst_port", 443);
966
967        let result = parser.parse(&packet, &context);
968
969        assert!(result.is_ok());
970        assert_eq!(result.get("record_type"), Some(&FieldValue::UInt8(22)));
971        assert_eq!(
972            result.get("version"),
973            Some(&FieldValue::OwnedString(CompactString::new("TLS 1.2")))
974        );
975        assert_eq!(result.get("handshake_type"), Some(&FieldValue::UInt8(1)));
976
977        // Verify client_random is extracted (decryption foundation)
978        if let Some(FieldValue::OwnedBytes(random)) = result.get("client_random") {
979            assert_eq!(random.len(), 32);
980            assert_eq!(random[0], 0x01);
981        } else {
982            panic!("client_random not found or wrong type");
983        }
984    }
985
986    #[test]
987    fn test_parse_tls_server_hello() {
988        let packet = create_tls_server_hello();
989
990        let parser = TlsProtocol;
991        let mut context = ParseContext::new(1);
992        context.insert_hint("src_port", 443);
993
994        let result = parser.parse(&packet, &context);
995
996        assert!(result.is_ok());
997        assert_eq!(result.get("handshake_type"), Some(&FieldValue::UInt8(2)));
998        assert_eq!(
999            result.get("selected_cipher"),
1000            Some(&FieldValue::OwnedString(CompactString::new(
1001                "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
1002            )))
1003        );
1004        assert_eq!(
1005            result.get("selected_cipher_id"),
1006            Some(&FieldValue::UInt16(0xC02F))
1007        );
1008
1009        // Verify server_random is extracted (decryption foundation)
1010        if let Some(FieldValue::OwnedBytes(random)) = result.get("server_random") {
1011            assert_eq!(random.len(), 32);
1012            assert_eq!(random[0], 0x02);
1013        } else {
1014            panic!("server_random not found or wrong type");
1015        }
1016    }
1017
1018    #[test]
1019    fn test_extract_sni() {
1020        let packet = create_tls_client_hello();
1021
1022        let parser = TlsProtocol;
1023        let mut context = ParseContext::new(1);
1024        context.insert_hint("dst_port", 443);
1025
1026        let result = parser.parse(&packet, &context);
1027
1028        assert!(result.is_ok());
1029        assert_eq!(
1030            result.get("sni"),
1031            Some(&FieldValue::OwnedString(CompactString::new(
1032                "www.example.com"
1033            )))
1034        );
1035    }
1036
1037    #[test]
1038    fn test_parse_tls_alert() {
1039        let packet = create_tls_alert();
1040
1041        let parser = TlsProtocol;
1042        let context = ParseContext::new(1);
1043
1044        let result = parser.parse(&packet, &context);
1045
1046        assert!(result.is_ok());
1047        assert_eq!(result.get("record_type"), Some(&FieldValue::UInt8(21)));
1048        assert_eq!(result.get("alert_level"), Some(&FieldValue::UInt8(2)));
1049        assert_eq!(
1050            result.get("alert_description"),
1051            Some(&FieldValue::UInt8(40))
1052        );
1053        assert_eq!(
1054            result.get("alert_description_str"),
1055            Some(&FieldValue::OwnedString(CompactString::new(
1056                "handshake_failure"
1057            )))
1058        );
1059    }
1060
1061    #[test]
1062    fn test_tls_version_detection() {
1063        assert_eq!(format_tls_version(TlsVersion(0x0300)), "SSL 3.0");
1064        assert_eq!(format_tls_version(TlsVersion(0x0301)), "TLS 1.0");
1065        assert_eq!(format_tls_version(TlsVersion(0x0302)), "TLS 1.1");
1066        assert_eq!(format_tls_version(TlsVersion(0x0303)), "TLS 1.2");
1067        assert_eq!(format_tls_version(TlsVersion(0x0304)), "TLS 1.3");
1068    }
1069
1070    #[test]
1071    fn test_cipher_suite_names() {
1072        // Test that common cipher suites are properly named via tls-parser
1073        assert_eq!(cipher_suite_name(0x1301), "TLS_AES_128_GCM_SHA256");
1074        assert_eq!(cipher_suite_name(0x1302), "TLS_AES_256_GCM_SHA384");
1075        assert_eq!(
1076            cipher_suite_name(0xC02F),
1077            "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
1078        );
1079        assert_eq!(
1080            cipher_suite_name(0xC030),
1081            "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
1082        );
1083    }
1084
1085    #[test]
1086    fn test_grease_detection() {
1087        assert!(is_grease_value(0x0a0a));
1088        assert!(is_grease_value(0x1a1a));
1089        assert!(is_grease_value(0x2a2a));
1090        assert!(is_grease_value(0xfafa));
1091        assert!(!is_grease_value(0x0001));
1092        assert!(!is_grease_value(0xC02F));
1093    }
1094
1095    #[test]
1096    fn test_ja3_computation() {
1097        let packet = create_tls_client_hello();
1098
1099        let parser = TlsProtocol;
1100        let mut context = ParseContext::new(1);
1101        context.insert_hint("dst_port", 443);
1102
1103        let result = parser.parse(&packet, &context);
1104
1105        assert!(result.is_ok());
1106        // Verify JA3 fields are present
1107        assert!(result.get("ja3").is_some());
1108        assert!(result.get("ja3_hash").is_some());
1109
1110        // JA3 hash should be 32 hex characters (MD5)
1111        if let Some(FieldValue::OwnedString(hash)) = result.get("ja3_hash") {
1112            assert_eq!(hash.len(), 32);
1113        }
1114    }
1115
1116    #[test]
1117    fn test_tls_schema_fields() {
1118        let parser = TlsProtocol;
1119        let fields = parser.schema_fields();
1120
1121        assert!(!fields.is_empty());
1122
1123        let field_names: Vec<&str> = fields.iter().map(|f| f.name).collect();
1124
1125        // Original fields
1126        assert!(field_names.contains(&"tls.record_type"));
1127        assert!(field_names.contains(&"tls.version"));
1128        assert!(field_names.contains(&"tls.sni"));
1129        assert!(field_names.contains(&"tls.cipher_suites"));
1130
1131        // New fields
1132        assert!(field_names.contains(&"tls.client_random"));
1133        assert!(field_names.contains(&"tls.server_random"));
1134        assert!(field_names.contains(&"tls.session_id"));
1135        assert!(field_names.contains(&"tls.ja3"));
1136        assert!(field_names.contains(&"tls.ja3_hash"));
1137        assert!(field_names.contains(&"tls.alpn"));
1138        assert!(field_names.contains(&"tls.supported_versions"));
1139        assert!(field_names.contains(&"tls.alert_level"));
1140    }
1141
1142    #[test]
1143    fn test_tls_too_short() {
1144        let short_packet = vec![0x16, 0x03, 0x03]; // Only 3 bytes
1145
1146        let parser = TlsProtocol;
1147        let context = ParseContext::new(1);
1148
1149        let result = parser.parse(&short_packet, &context);
1150
1151        assert!(!result.is_ok());
1152    }
1153}