1use 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#[allow(dead_code)]
32pub const TLS_PORT: u16 = 443;
33
34const TLS_PORTS: &[u16] = &[
36 443, 8443, 993, 995, 465, 636, 853, 5061, 14433, ];
46
47#[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 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 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 match parse_tls_plaintext(data) {
84 Ok((remaining, record)) => {
85 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 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 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 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 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 FieldDescriptor::new("tls.handshake_type", DataKind::UInt8).set_nullable(true),
157 FieldDescriptor::new("tls.handshake_version", DataKind::UInt16).set_nullable(true),
158 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 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 FieldDescriptor::new("tls.compression_methods", DataKind::String).set_nullable(true),
170 FieldDescriptor::new("tls.selected_compression", DataKind::UInt8).set_nullable(true),
171 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 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 FieldDescriptor::new("tls.is_heartbeat", DataKind::Bool).set_nullable(true),
186 FieldDescriptor::new("tls.heartbeat_type", DataKind::UInt8).set_nullable(true),
187 FieldDescriptor::new("tls.is_change_cipher_spec", DataKind::Bool).set_nullable(true),
189 FieldDescriptor::new("tls.has_app_data", DataKind::Bool).set_nullable(true),
191 FieldDescriptor::new("tls.app_data_length", DataKind::UInt32).set_nullable(true),
192 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 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
211fn 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 fields.push(("client_random", FieldValue::OwnedBytes(ch.random.to_vec())));
223
224 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 let cipher_count = ch.ciphers.len();
239 fields.push((
240 "cipher_suite_count",
241 FieldValue::UInt16(cipher_count as u16),
242 ));
243
244 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 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 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 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 fields.push(("server_random", FieldValue::OwnedBytes(sh.random.to_vec())));
298
299 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 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 fields.push(("selected_compression", FieldValue::UInt8(sh.compression.0)));
322
323 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 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 fields.push(("handshake_type", FieldValue::UInt8(2)));
393 }
394 }
395}
396
397fn 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 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 for (name_type, name_data) in sni_list {
415 if name_type.0 == 0 {
416 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 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
496fn 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
528fn compute_ja3(ch: &tls_parser::TlsClientHelloContents) -> String {
532 let version = ch.version.0;
534
535 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 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
591fn compute_ja3s(sh: &tls_parser::TlsServerHelloContents) -> String {
595 let version = sh.version.0;
597
598 let cipher = sh.cipher.0;
600
601 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
627fn is_grease_value(val: u16) -> bool {
629 val & 0x0f0f == 0x0a0a
631}
632
633fn 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
646fn 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
657fn 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
667fn 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
691fn 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
734fn 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
744fn 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 fn create_tls_client_hello() -> Vec<u8> {
791 let mut packet = Vec::new();
792
793 packet.push(22); packet.push(0x03); packet.push(0x03); let length_pos = packet.len();
800 packet.push(0x00); packet.push(0x00); let handshake_start = packet.len();
804
805 packet.push(1); packet.push(0x00); packet.push(0x00);
809 packet.push(0x00);
810
811 let hello_start = packet.len();
812
813 packet.push(0x03); packet.push(0x03); packet.extend_from_slice(&[0x01; 32]);
819
820 packet.push(0x00);
822
823 packet.push(0x00);
825 packet.push(0x06);
826 packet.extend_from_slice(&0xC02Fu16.to_be_bytes()); packet.extend_from_slice(&0xC030u16.to_be_bytes()); packet.extend_from_slice(&0x1301u16.to_be_bytes()); packet.push(0x01);
832 packet.push(0x00);
833
834 let extensions_len_pos = packet.len();
836 packet.push(0x00); packet.push(0x00);
838
839 let extensions_start = packet.len();
840
841 packet.extend_from_slice(&0u16.to_be_bytes()); let hostname = b"www.example.com";
845 let sni_ext_len = 2 + 1 + 2 + hostname.len(); packet.extend_from_slice(&(sni_ext_len as u16).to_be_bytes()); let sni_list_len = 1 + 2 + hostname.len(); packet.extend_from_slice(&(sni_list_len as u16).to_be_bytes());
851 packet.push(0x00); packet.extend_from_slice(&(hostname.len() as u16).to_be_bytes());
853 packet.extend_from_slice(hostname);
854
855 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 fn create_tls_server_hello() -> Vec<u8> {
874 let mut packet = Vec::new();
875
876 packet.push(22); packet.push(0x03); packet.push(0x03); let length_pos = packet.len();
882 packet.push(0x00);
883 packet.push(0x00);
884
885 let handshake_start = packet.len();
886
887 packet.push(2); packet.push(0x00);
890 packet.push(0x00);
891 packet.push(0x00);
892
893 let hello_start = packet.len();
894
895 packet.push(0x03); packet.push(0x03); packet.extend_from_slice(&[0x02; 32]);
901
902 packet.push(0x00);
904
905 packet.extend_from_slice(&0xC02Fu16.to_be_bytes()); packet.push(0x00);
910
911 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 fn create_tls_alert() -> Vec<u8> {
926 vec![
927 21, 0x03, 0x03, 0x00, 0x02, 0x02, 0x28, ]
933 }
934
935 #[test]
936 fn test_can_parse_tls_by_port() {
937 let parser = TlsProtocol;
938
939 let ctx1 = ParseContext::new(1);
941 assert!(parser.can_parse(&ctx1).is_none());
942
943 let mut ctx2 = ParseContext::new(1);
945 ctx2.insert_hint("dst_port", 443);
946 assert!(parser.can_parse(&ctx2).is_some());
947
948 let mut ctx3 = ParseContext::new(1);
950 ctx3.insert_hint("src_port", 443);
951 assert!(parser.can_parse(&ctx3).is_some());
952
953 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 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 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 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 assert!(result.get("ja3").is_some());
1108 assert!(result.get("ja3_hash").is_some());
1109
1110 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 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 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]; 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}