tlsfingerprint

package module
v0.0.0-...-c742e47 Latest Latest
Warning

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

Go to latest
Published: Nov 11, 2025 License: MIT Imports: 12 Imported by: 0

README

tlsfingerprint

Package tlsfingerprint provides TLS client fingerprinting capabilities for Go. It can parse TLS ClientHello messages and generate both JA3 and JA4 fingerprints for identifying and categorizing TLS clients.

Overview

This library allows you to fingerprint incoming TLS connections directly on a socket before completing the TLS handshake. It extracts cipher suites, extensions, supported groups, ALPN protocols, signature algorithms, and other handshake parameters from TLS ClientHello messages. It supports both JA3 and JA4.

The package provides three levels of integration:

  • tlsfingerprint: Core fingerprinting functionality for raw connections
  • httpfingerprint: High-level HTTP server integration
  • fingerprintlistener: net.Listener wrapper for custom server implementations

Usage

HTTP Server Integration

The simplest way to use this library is with the httpfingerprint package:

package main

import (
	"encoding/json"
	"log"
	"net/http"

	"github.com/psanford/tlsfingerprint/httpfingerprint"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fp := httpfingerprint.FingerprintFromContext(r.Context())
		if fp == nil {
			http.Error(w, "no fingerprint", http.StatusInternalServerError)
			return
		}

		log.Printf("JA3: %s (%s)", fp.JA3Hash(), fp.JA3String())
		log.Printf("JA4: %s", fp.JA4String())
		json.NewEncoder(w).Encode(fp)
	})

	log.Fatal(httpfingerprint.ListenAndServeTLS(":8443", "cert.pem", "key.pem", nil))
}
Low-Level Connection Fingerprinting

For custom server implementations, use the core fingerprinting function:

package main

import (
	"crypto/tls"
	"log"
	"net"

	"github.com/psanford/tlsfingerprint"
)

func main() {
	ln, _ := net.Listen("tcp", ":8443")
	defer ln.Close()

	for {
		conn, _ := ln.Accept()
		go handleConnection(conn)
	}
}

func handleConnection(conn net.Conn) {
	defer conn.Close()

	fp, wrappedConn, err := tlsfingerprint.FingerprintConn(conn)
	if err != nil {
		log.Printf("Fingerprint error: %v", err)
		return
	}

	log.Printf("Client JA3: %s", fp.JA3Hash())
	log.Printf("Client JA4: %s", fp.JA4String())

	tlsConfig := &tls.Config{
		Certificates: loadCertificates(),
	}
	tlsConn := tls.Server(wrappedConn, tlsConfig)
	if err := tlsConn.Handshake(); err != nil {
		log.Printf("TLS handshake error: %v", err)
		return
	}

	// Handle tlsConn...
}
Listener Wrapper

The fingerprintlistener package wraps a net.Listener:

package main

import (
	"log"
	"net"

	"github.com/psanford/tlsfingerprint/fingerprintlistener"
)

func main() {
	ln, _ := net.Listen("tcp", ":8443")
	fpln := fingerprintlistener.NewListener(ln)

	for {
		conn, _ := fpln.Accept()
		if fpConn, ok := conn.(fingerprintlistener.Conn); ok {
			fp := fpConn.Fingerprint()
			log.Printf("JA3: %s", fp.JA3Hash())
			log.Printf("JA4: %s", fp.JA4String())
		}
	}
}

Examples

See the examples directory for complete working examples:

See Also

License

See LICENSE file.

Documentation

Overview

Package tlsfingerprint provides TLS client fingerprinting capabilities. It can parse TLS ClientHello messages and generate both JA3 and JA4 fingerprints for identifying and categorizing TLS clients.

The package supports extracting cipher suites, extensions, supported groups, ALPN protocols, signature algorithms, and other handshake parameters from TLS connections. It can compute both JA3 fingerprints (with MD5 hashes) and JA4 fingerprints (with SHA256 hashes) for fingerprinting purposes.

Example
package main

import (
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/tls"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"fmt"
	"log"
	"math/big"
	"net"
	"time"

	"github.com/psanford/tlsfingerprint"
)

func main() {
	cert, err := generateCertificate()
	if err != nil {
		log.Fatal(err)
	}

	ln, err := net.Listen("tcp", ":8443")
	if err != nil {
		log.Fatal(err)
	}
	defer ln.Close()

	for {
		conn, err := ln.Accept()
		if err != nil {
			log.Fatal(err)
		}
		go handleConnection(conn, cert)
	}
}

func handleConnection(conn net.Conn, cert tls.Certificate) {
	defer conn.Close()

	fp, wrappedConn, err := tlsfingerprint.FingerprintConn(conn)
	if err != nil {
		log.Printf("Fingerprint error: %v", err)
		return
	}

	fmt.Printf("JA3 Hash: %s\n", fp.JA3Hash())
	fmt.Printf("JA3 String: %s\n", fp.JA3String())
	fmt.Printf("JA4: %s\n", fp.JA4String())
	fmt.Printf("TLS Version: 0x%04x\n", fp.Version)
	fmt.Printf("Cipher Suites: %d\n", len(fp.CipherSuites))
	fmt.Printf("Extensions: %d\n", len(fp.Extensions))

	tlsConfig := &tls.Config{
		Certificates: []tls.Certificate{cert},
	}
	tlsConn := tls.Server(wrappedConn, tlsConfig)
	if err := tlsConn.Handshake(); err != nil {
		log.Printf("TLS handshake error: %v", err)
		return
	}
}

func generateCertificate() (tls.Certificate, error) {
	priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		return tls.Certificate{}, err
	}

	serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
	if err != nil {
		return tls.Certificate{}, err
	}

	template := x509.Certificate{
		SerialNumber: serialNumber,
		Subject: pkix.Name{
			Organization: []string{"Example"},
		},
		NotBefore:             time.Now(),
		NotAfter:              time.Now().Add(365 * 24 * time.Hour),
		KeyUsage:              x509.KeyUsageDigitalSignature,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
		BasicConstraintsValid: true,
	}

	derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
	if err != nil {
		return tls.Certificate{}, err
	}

	certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
	privBytes, err := x509.MarshalPKCS8PrivateKey(priv)
	if err != nil {
		return tls.Certificate{}, err
	}
	keyPEM := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privBytes})

	return tls.X509KeyPair(certPEM, keyPEM)
}

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Fingerprint

type Fingerprint struct {
	// Version is the negotiated TLS version, potentially from the supported_versions extension.
	Version uint16
	// CipherSuites is the list of cipher suites advertised by the client.
	CipherSuites []uint16
	// Extensions is the list of TLS extensions present in the ClientHello.
	Extensions []uint16
	// SupportedGroups is the list of elliptic curves and DH groups supported by the client.
	SupportedGroups []uint16
	// SupportedPoints is the list of EC point formats supported by the client.
	SupportedPoints []uint8
	// RawVersion is the TLS version from the ClientHello header (will never be > TLS 1.2).
	RawVersion uint16
	// ALPNProtocols is the list of ALPN protocols advertised by the client.
	ALPNProtocols []string
	// SignatureAlgorithms is the list of signature algorithms supported by the client.
	SignatureAlgorithms []uint16
	// HasSNI indicates whether the SNI extension is present.
	HasSNI bool
}

Fingerprint represents a TLS client fingerprint extracted from a ClientHello message. It contains the negotiated TLS version, cipher suites, extensions, and other parameters used to uniquely identify TLS clients.

func FingerprintConn

func FingerprintConn(conn net.Conn) (*Fingerprint, net.Conn, error)

FingerprintConn reads and parses the TLS ClientHello from a connection without consuming it. It returns the extracted Fingerprint, a wrapped connection that replays the buffered data, and any error encountered. The returned connection can be used for the TLS handshake as if nothing was read from it.

func ParseClientHello

func ParseClientHello(data []byte) (*Fingerprint, error)

ParseClientHello parses a TLS ClientHello message and extracts fingerprint information. The data parameter should contain the complete TLS record including the record header. It returns a Fingerprint containing the client's TLS parameters, or an error if parsing fails.

func (*Fingerprint) JA3Hash

func (f *Fingerprint) JA3Hash() string

JA3Hash computes the MD5 hash of the JA3 fingerprint string. This produces a 32-character hexadecimal string that uniquely identifies the client's TLS configuration in a compact form.

func (*Fingerprint) JA3String

func (f *Fingerprint) JA3String() string

JA3String generates the JA3 fingerprint string for this TLS fingerprint. The JA3 string is a comma-separated list of TLS parameters in the format: SSLVersion,Ciphers,Extensions,EllipticCurves,EllipticCurvePointFormats. GREASE values are filtered out per the JA3 specification.

func (*Fingerprint) JA4String

func (f *Fingerprint) JA4String() string

JA4String generates the JA4 fingerprint string for this TLS fingerprint. The JA4 string follows the format: [protocol][version][sni][cipher_count][extension_count][alpn]_[cipher_hash]_[extension_hash] For example: t13d1516h2_8daaf6152771_e5627efa2ab1

func (*Fingerprint) MarshalJSON

func (f *Fingerprint) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler for Fingerprint. It produces a JSON representation with human-readable names for cipher suites, extensions, and supported groups, along with JA3 and JA4 fingerprints.

Directories

Path Synopsis
examples
webserver command
Package fingerprintlistener provides a net.Listener wrapper that automatically extracts TLS fingerprints from incoming connections.
Package fingerprintlistener provides a net.Listener wrapper that automatically extracts TLS fingerprints from incoming connections.
Package httpfingerprint provides an HTTP server wrapper that automatically extracts TLS fingerprints from client connections and makes them available through the request context.
Package httpfingerprint provides an HTTP server wrapper that automatically extracts TLS fingerprints from client connections and makes them available through the request context.

Jump to

Keyboard shortcuts

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