Documentation
¶
Overview ¶
Package tlsdecode decodes the cleartext portion of a TLS handshake — the ClientHello and ServerHello records that every TLS connection emits in the clear before encryption is negotiated. This is the workhorse pcap-and-paste tool for SOC blue-team analysis (JA3 / JA4 fingerprinting, plaintext SNI extraction, ALPN inspection), threat-intel triage (cipher-suite weakness scanning, version downgrade detection), and offensive recon (server preference fingerprinting, client-stack identification).
Wrap-vs-native judgement ¶
Native. The TLS record layer and handshake messages are fully published in RFC 5246 (TLS 1.2), RFC 8446 (TLS 1.3), and the IANA TLS registries. Every field is a fixed-length integer, length-prefixed byte string, or list. Extension dispatch is a switch over a 2-byte type. Pasting a hex blob extracted from a Wireshark TLS frame, a tcpdump-of- 443 capture, or a tshark `tls.handshake` field is enough — no cryptography (the cleartext portion is the entire scope), no key material, no live network attach.
What this package covers ¶
- TLS record layer envelope: ContentType (Handshake / ChangeCipherSpec / Alert / ApplicationData), Version (major + minor, with TLS 1.0..1.3 name lookup), Length. Multiple back-to-back records in one buffer are supported.
- Handshake message dispatch: ClientHello, ServerHello, HelloRetryRequest, NewSessionTicket, EndOfEarlyData, EncryptedExtensions, Certificate, CertificateRequest, CertificateVerify, Finished, KeyUpdate, MessageHash. Bodies for non-ClientHello / non-ServerHello messages are surfaced as raw hex.
- ClientHello body: legacy_version, random (32 bytes), legacy_session_id, cipher_suites (with IANA name lookup for ~80 suites covering all current TLS 1.3 suites + the most-deployed TLS 1.2 suites including ECDHE-RSA-AES, ECDHE-ECDSA-AES, ChaCha20-Poly1305, and the deprecated-but-still-seen RSA / 3DES / CBC legacy suites), compression_methods, extensions.
- ServerHello body: same field layout as ClientHello but with single selected cipher suite + compression method.
- Extension dispatch with type-name lookup for ~30 IANA- registered extensions. Deep decode for the operationally-important ones:
- server_name (type 0): extracts the SNI value (only host_name name_type 0 is in scope).
- supported_groups (type 10): list of named curves / DH groups with name lookup.
- signature_algorithms (type 13): list of SignatureScheme codes with name lookup.
- application_layer_protocol_negotiation (type 16, ALPN): list of protocol strings (h2, http/1.1, etc.).
- supported_versions (type 43): the canonical TLS 1.3 version-negotiation extension.
- key_share (type 51): list of (group, key) pairs with the group name surfaced.
- **JA3 fingerprint** (per the Salesforce / John Althouse spec): the comma-separated string "version,cipher_suites,extensions,curves, ec_point_formats" with hyphens between list members, plus its MD5 hash. The standard JA3 client fingerprint identifies the TLS client stack (browser, library, malware family) across thousands of distinct signatures.
- **JA4 fingerprint** (FoxIO LLC, the modern successor to JA3): protocol + TLS version + SNI flag + cipher/extension counts + ALPN, then truncated SHA-256 of the sorted cipher list and of the sorted extensions + signature_algorithms. GREASE values are ignored throughout. Verified byte-for-byte against the FoxIO worked example.
- **JA4S fingerprint** (FoxIO, server side): from the ServerHello — protocol + TLS version + extension count + chosen ALPN, the negotiated cipher, and the truncated SHA-256 of the server's extensions in WIRE ORDER (not sorted — the server's extension order is itself the fingerprint signal). Pairs with the client JA4 to fingerprint both ends of a session. Verified byte-for-byte against FoxIO snapshot outputs.
Certificate handshake message ¶
The TLS 1.2 (and earlier) Certificate handshake message is decoded: the certificate_list is walked and each DER certificate is chained through internal/x509decode to surface subject / issuer / validity / SAN / fingerprints / CA flag — so a captured handshake yields the server's cert chain in one decode. In TLS 1.3 the Certificate message is encrypted, so the plaintext form seen in a passive capture is the pre-1.3 layout decoded here; a body that doesn't match it is reported with a note, never mis-parsed.
What this package does NOT cover (deliberately out of scope) ¶
- Encrypted ApplicationData / CCS / Alert bodies: the record envelope is decoded but the post-handshake ciphertext is opaque without key material. (The JA4X X.509 fingerprint is computed for each cert in the chain by internal/x509decode, and the JA4H HTTP-client fingerprint by internal/httpmsg — so the whole JA4+ family JA4 / JA4S / JA4X / JA4H is now covered across the stack.)
- TLS 1.3 inner handshake (EncryptedExtensions onward) is encrypted on the wire and requires session-key material that this Spec deliberately does not handle.
- DTLS (Datagram TLS over UDP) — slight envelope differences (record sequence number); deferred.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func QUICHandshakeJA4 ¶ added in v0.527.0
QUICHandshakeJA4 computes the JA4 (for a ClientHello) or JA4S (for a ServerHello) fingerprint of a bare TLS handshake message lifted out of a QUIC Initial packet's reassembled CRYPTO stream, using the QUIC protocol prefix "q" (JA4 §"q"/"t"/"d" rule). QUIC carries the TLS handshake directly in CRYPTO frames with no TLS record envelope (RFC 9001 §4.1), so handshake[0] is the 1-byte handshake type and handshake[1:4] the 3-byte length. Returns the fingerprint and the message kind ("ClientHello" / "ServerHello"). The JA4 computation is identical to the TLS-over-TCP path bar the leading protocol character — verified end-to-end against FoxIO's QUIC snapshots (see internal/quic).
Types ¶
type CertEntry ¶ added in v0.473.0
type CertEntry struct {
Length int `json:"length"`
Certificate *x509decode.Certificate `json:"x509,omitempty"`
DERHex string `json:"der_hex,omitempty"`
DecodeError string `json:"decode_error,omitempty"`
}
CertEntry is one certificate from the chain, decoded via internal/x509decode.
type Certificate ¶ added in v0.473.0
type Certificate struct {
CertificateCount int `json:"certificate_count"`
Certificates []*CertEntry `json:"certificates,omitempty"`
Notes []string `json:"notes,omitempty"`
}
Certificate is a decoded TLS Certificate handshake message (the TLS 1.2 / TLS 1.0-1.1 form: a 3-byte certificate_list length followed by 3-byte-length-prefixed DER certificates). In TLS 1.3 the Certificate message is encrypted (sent after the handshake keys are derived), so the plaintext form visible in a passive capture is the pre-1.3 layout decoded here; a body that does not match it (e.g. a decrypted 1.3 message with its request-context prefix and per-certificate extensions) is reported with a note rather than mis-parsed.
type CipherSuite ¶
type CipherSuite struct {
Value uint16 `json:"value"`
Hex string `json:"hex"`
Name string `json:"name,omitempty"`
}
CipherSuite is one TLS cipher suite reference.
type ClientHello ¶
type ClientHello struct {
VersionMajor int `json:"version_major"`
VersionMinor int `json:"version_minor"`
VersionName string `json:"version_name"`
RandomHex string `json:"random_hex"`
SessionIDHex string `json:"session_id_hex"`
CipherSuites []CipherSuite `json:"cipher_suites"`
CompressionMethods []int `json:"compression_methods"`
Extensions []*Extension `json:"extensions,omitempty"`
ServerName string `json:"server_name,omitempty"`
ALPNProtocols []string `json:"alpn_protocols,omitempty"`
SupportedVersions []string `json:"supported_versions,omitempty"`
SupportedGroups []string `json:"supported_groups,omitempty"`
SignatureAlgorithms []string `json:"signature_algorithms,omitempty"`
JA3 string `json:"ja3,omitempty"`
JA3Hash string `json:"ja3_hash,omitempty"`
JA4 string `json:"ja4,omitempty"`
}
ClientHello is the decoded TLS ClientHello body.
type Extension ¶
type Extension struct {
Type int `json:"type"`
TypeName string `json:"type_name"`
Length int `json:"length"`
DataHex string `json:"data_hex,omitempty"`
}
Extension is one TLS extension.
type Frame ¶
type Frame struct {
Records []*Record `json:"records"`
}
Frame is a list of decoded TLS records — TCP segments often pack multiple back-to-back records, so the top-level result is always a slice.
func DecodeBytes ¶
DecodeBytes parses raw TLS record bytes — one or more back-to-back records.
type Handshake ¶
type Handshake struct {
MessageType int `json:"message_type"`
MessageName string `json:"message_name"`
Length int `json:"length"`
ClientHello *ClientHello `json:"client_hello,omitempty"`
ServerHello *ServerHello `json:"server_hello,omitempty"`
Certificate *Certificate `json:"certificate,omitempty"`
BodyHex string `json:"body_hex,omitempty"`
}
Handshake is one TLS handshake message inside a record.
type Record ¶
type Record struct {
ContentType int `json:"content_type"`
ContentName string `json:"content_name"`
VersionMajor int `json:"version_major"`
VersionMinor int `json:"version_minor"`
VersionName string `json:"version_name"`
Length int `json:"length"`
Handshakes []*Handshake `json:"handshakes,omitempty"`
BodyHex string `json:"body_hex,omitempty"`
}
Record is one TLS record-layer frame.
type ServerHello ¶
type ServerHello struct {
VersionMajor int `json:"version_major"`
VersionMinor int `json:"version_minor"`
VersionName string `json:"version_name"`
RandomHex string `json:"random_hex"`
SessionIDHex string `json:"session_id_hex"`
CipherSuite CipherSuite `json:"cipher_suite"`
CompressionMethod int `json:"compression_method"`
Extensions []*Extension `json:"extensions,omitempty"`
NegotiatedALPN string `json:"negotiated_alpn,omitempty"`
NegotiatedVersion string `json:"negotiated_version,omitempty"`
JA4S string `json:"ja4s,omitempty"`
}
ServerHello is the decoded TLS ServerHello body. Same shape as ClientHello but with a single selected cipher suite + compression method.