tlsdecode

package
v0.643.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 9, 2026 License: AGPL-3.0 Imports: 8 Imported by: 0

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

func QUICHandshakeJA4(handshake []byte) (fingerprint, kind string, err error)

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"`
	KeyShareGroups      []string      `json:"key_share_groups,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 Decode

func Decode(hexBlob string) (*Frame, error)

Decode parses a hex-encoded buffer of one or more TLS records.

func DecodeBytes

func DecodeBytes(b []byte) (*Frame, error)

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL