Add scan_tls TLS handshake metadata scanner module#275
Conversation
|
Hi. I really appreciate this, but I am no longer actively maintaining this project. Would you like to take it over? Failing that, I cannot accept pull requests that do not include tests for the new code. You describe a testing procedure, but I really need tests to cover the new functionality. How would you like to proceed? Do you want to take over this project? Or would you like to join its maintenance? And do you have the ability to write a test to test your contribution? Simson |
There was a problem hiding this comment.
Pull request overview
This PR introduces a new built-in (but opt-in) scanner module, scan_tls, that parses TLS ClientHello/ServerHello from reassembled TCP streams to extract handshake metadata (e.g., SNI, version, and JA3) and append it to a JSON output file in the tcpflow output directory.
Changes:
- Added new TLS scanner implementation (
src/scan_tls.cpp) and associated parsing/data definitions (src/scan_tls.h). - Registered
scan_tlswith the built-in scanner list and exported its symbol (src/tcpflow.cpp,src/tcpflow.h). - Wired the new module into the build (
src/Makefile.am).
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/tcpflow.h | Declares the scan_tls scanner symbol for linkage with the scanner system. |
| src/tcpflow.cpp | Registers scan_tls in the built-in scanner list. |
| src/scan_tls.h | Defines TLS record/handshake structs and metadata containers for parsed results. |
| src/scan_tls.cpp | Implements TLS handshake parsing, JA3 computation, and JSON output appending. |
| src/Makefile.am | Adds the new TLS scanner sources to the build. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| #include <iostream> | ||
| #include <fstream> | ||
| #include <sstream> | ||
| #include <string> | ||
| #include <vector> | ||
| #include <cstring> | ||
| #include <ctime> | ||
| #include <mutex> | ||
|
|
||
| #ifdef HAVE_OPENSSL_MD5_H | ||
| #include <openssl/evp.h> | ||
| #endif |
| if (off + 2 <= eend) { | ||
| uint16_t curves_bytes = u16be(buf + off); | ||
| for (uint16_t i = 2; i + 1 < curves_bytes + 2 && off + i + 2 <= eend; i += 2) { | ||
| uint16_t curve = u16be(buf + off + i); | ||
| if (!is_grease(curve)) ch.elliptic_curves.push_back(curve); | ||
| } | ||
| } |
| if (off + 1 <= eend) { | ||
| uint8_t fmt_len = buf[off]; | ||
| for (uint8_t i = 1; i <= fmt_len && off + i < eend; i++) { | ||
| ch.ec_point_formats.push_back(buf[off + i]); | ||
| } | ||
| } |
| uint16_t record_len = u16be(data + 3); | ||
| if ((size_t)(5 + record_len) > len) return; | ||
|
|
||
| uint8_t msg_type = data[5]; | ||
| uint32_t msg_len = u24be(data + 6); | ||
| size_t body_off = 9; | ||
|
|
||
| if (body_off + msg_len > len) return; |
| time_t now = time(nullptr); | ||
| char ts[32]; | ||
| struct tm *gmt = gmtime(&now); | ||
| strftime(ts, sizeof(ts), "%Y-%m-%dT%H:%M:%SZ", gmt); | ||
|
|
||
| std::lock_guard<std::mutex> lock(tls_output_mutex); | ||
| std::ofstream f(outpath, std::ios::app); | ||
| if (!f) return; |
| f << '{'; | ||
| f << "\"flow\":\"" << flow_key << "\","; | ||
| f << "\"timestamp\":\"" << ts << "\""; | ||
|
|
||
| if (ch) { | ||
| f << ",\"sni\":\"" << ch->sni << "\""; | ||
| f << ",\"tls_version\":\"" << ch->tls_version << "\""; | ||
| f << ",\"ja3\":\"" << ch->ja3 << "\""; | ||
| f << ",\"ja3_hash\":\"" << ch->ja3_hash << "\""; | ||
| f << ",\"cipher_suites_offered\":" << ch->cipher_suites.size(); | ||
| } | ||
|
|
||
| if (sh) { | ||
| f << ",\"negotiated_version\":\"" << sh->negotiated_version << "\""; | ||
| f << ",\"selected_cipher\":\"" << sh->selected_cipher << "\""; | ||
| } |
| if (sp.phase == scanner_params::PHASE_STARTUP) { | ||
| sp.info->name = "tls"; | ||
| sp.info->author = "tcpflow scan_tls"; | ||
| sp.info->flags = scanner_info::SCANNER_DISABLED; // enable with -e tls | ||
| return; |
Summary
This PR adds
scan_tls, a new scanner module that extracts TLS handshakemetadata from reassembled TCP streams. It parses ClientHello and ServerHello
messages and records, per connection:
Output is appended to
tls_sni.jsonin the tcpflow output directory.Motivation
TLS now carries the majority of network traffic, but tcpflow had no built-in
way to surface handshake metadata. This is useful for traffic analysis,
fingerprinting, and inventorying TLS endpoints without decrypting payloads.
The module follows the existing scanner pattern (scan_http, scan_netviz, etc.)
so it integrates cleanly with the current architecture.
Changes
src/scan_tls.cpp,src/scan_tls.hsrc/Makefile.amTesting
Built locally with the standard autotools flow:
Ran against pcaps containing TLS traffic and confirmed
tls_sni.jsonisgenerated with correct SNI, cipher suites, version and JA3 values.
Notes
JA3 follows the original spec:
legacy_versionis kept raw for fingerprintcompliance, with a separate
display_versionfield for TLS 1.3 detection.