profile

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Sep 7, 2026 License: Apache-2.0 Imports: 22 Imported by: 0

Documentation

Overview

Package profile describes one browser build completely enough to impersonate it: the ClientHello, the HTTP/2 preamble, and the headers with their order.

A profile is data, not code. It serialises to JSON, so a profile measured on one machine can be committed, reviewed, shipped and used on another — which is the difference between "we support Chrome 151" and "we supported Chrome 151 on the day someone hand-wrote this table".

The authoritative field is ClientHello: a real browser's ClientHello, captured off the wire. utls turns those bytes back into a live handshake, regenerating everything that must be fresh per connection — GREASE values, key shares, the ECH payload — so the result matches the browser without being a recording of one connection.

Index

Constants

This section is empty.

Variables

View Source
var Default = NewRegistry()

Default is the registry the package-level functions use.

View Source
var ErrNoHandshake = fmt.Errorf("profile: neither client_hello nor base is set")

ErrNoHandshake reports a profile that names neither a captured ClientHello nor a base profile, and so cannot produce a handshake.

Functions

func DefaultDir

func DefaultDir() string

DefaultDir is where profiles measured on this machine are kept.

Under the user's config directory rather than beside the binary, because a profile is this machine's measurement of this machine's browser: it does not belong to an install that a package manager may replace, and it should survive one.

TLSFORGE_PROFILES moves it, which is how a run in a container or a test says where to look without touching a real one.

func HostPlatform

func HostPlatform() string

HostPlatform is this machine, spelled the way a profile file is named.

func Names

func Names() []string

Names lists the default registry's profiles.

func Register

func Register(p *Profile) error

Register adds a profile to the default registry.

func Split

func Split(name string) (version, platform string)

Split separates a name into the version and the platform it names, if it names one. Only a platform this project knows counts: "chrome_151" is a version whose last word happens to be a number, not a platform called 151.

Types

type Field

type Field struct {
	Name  string `json:"name"`
	Value string `json:"value"`
}

Field is one header. A slice of these rather than a map, because order is fingerprinted and a map has none.

type Group

type Group struct {
	// Name is what to ask for to get this machine's platform.
	Name     string
	Variants []Variant
	// Local says this machine has something under this name, which is then the
	// copy that answers.
	Local bool
}

Group is one measured profile and the platforms it was measured on.

A version measured on three platforms is one profile with three spellings rather than three profiles: the handshake is the same on all of them, and only the user-agent and the platform hint differ.

type HTTP1

type HTTP1 struct {
	HeaderOrder []string `json:"header_order"`
	Headers     []Field  `json:"headers,omitempty"`
}

HTTP1 is the part of a request fingerprint HTTP/2 cannot describe. HeaderOrder preserves wire spelling as well as order. Headers contains protocol-only values, or values that differ from the shared Headers block.

type HTTP2

type HTTP2 struct {
	Settings          []Setting  `json:"settings"`
	ConnectionFlow    uint32     `json:"connection_flow"`
	PseudoHeaderOrder []string   `json:"pseudo_header_order"`
	Priorities        []Priority `json:"priorities,omitempty"`
	HeaderPriority    *Priority  `json:"header_priority,omitempty"`
	StreamID          uint32     `json:"stream_id,omitempty"`
}

HTTP2 is the connection preamble a client sends before its first request.

type Priority

type Priority struct {
	StreamID  uint32 `json:"stream_id"`
	Exclusive bool   `json:"exclusive"`
	DependsOn uint32 `json:"depends_on"`
	Weight    uint8  `json:"weight"`
}

Priority is an HTTP/2 priority, either standalone or carried on HEADERS.

Weight is the value as it appears ON THE WIRE, which is one less than the weight people quote: Chrome's "weight 256" is a 255 here. Storing the wire value keeps the round-trip through a capture exact.

type Profile

type Profile struct {
	Name      string `json:"name"`
	UserAgent string `json:"user_agent,omitempty"`

	// ClientHello is a captured ClientHello record, the whole TLS fingerprint.
	// When empty, Base names a stock profile to borrow instead.
	ClientHello []byte `json:"client_hello,omitempty"`

	// Base names a profile from the tls-client catalogue, used when no captured
	// ClientHello is present. It is also what a hand-written profile builds on.
	Base string `json:"base,omitempty"`

	HTTP1   *HTTP1  `json:"http1,omitempty"`
	HTTP2   HTTP2   `json:"http2"`
	Headers []Field `json:"headers,omitempty"`

	// Notes is free text carried into the JSON so a committed profile can say
	// where it came from — which browser build, measured when, on what OS.
	Notes string `json:"notes,omitempty"`
	// contains filtered or unexported fields
}

Profile is a complete browser identity.

func FromCapture

func FromCapture(name string, c *capture.Capture) (*Profile, error)

FromCapture turns a browser measurement into a reusable profile.

This is the function that makes the library's claim checkable rather than asserted: nothing in the resulting profile was written by hand, so nothing in it can be a plausible guess about what a browser sends.

func Get

func Get(name string) (*Profile, error)

Get resolves a name against the default registry.

func Load

func Load(data []byte) (*Profile, error)

Load reads a profile from JSON.

func (*Profile) ClientProfile

func (p *Profile) ClientProfile() (profiles.ClientProfile, error)

ClientProfile converts to the form the transport consumes.

func (*Profile) Clone

func (p *Profile) Clone() *Profile

Clone returns an independent copy of the profile.

func (*Profile) Header

func (p *Profile) Header(name string) (string, bool)

Header returns a default header's value.

func (*Profile) HeaderOrder

func (p *Profile) HeaderOrder() []string

HeaderOrder is the header names in profile order, which is what the transport needs to reproduce the browser's ordering.

func (*Profile) Save

func (p *Profile) Save() ([]byte, error)

Save writes a profile as indented JSON, the form meant to be committed and reviewed. Indented and newline-terminated so a profile update shows up in a diff as the fields that changed rather than as one very long line.

func (*Profile) ShufflesExtensions

func (p *Profile) ShufflesExtensions() bool

ShufflesExtensions reports whether this browser family randomises the TLS extension order on each connection. Chromium browsers do; Firefox and Safari do not. The user agent is a fallback for custom captured profile names.

func (*Profile) Source

func (p *Profile) Source() string

Source is the file this profile was read from, or empty for one that ships inside the binary or came from the catalogue.

func (*Profile) Spec

func (p *Profile) Spec() (tls.ClientHelloSpec, error)

Spec builds a fresh ClientHelloSpec.

Fresh on every call, deliberately. utls extensions are pointers with mutable state, and a spec shared between connections is a spec whose key share is reused — which is both a bug and a fingerprint, since no browser reuses one.

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

Registry resolves a profile name.

Four sources, in order: profiles registered at runtime, profiles kept in this machine's own directory, profiles measured and committed here, and the tls-client catalogue. The order matters: someone who captures their own Chrome and keeps it under "chrome_151" should get theirs, not the one that shipped, because theirs is the browser a server will be comparing against.

func NewRegistry

func NewRegistry() *Registry

NewRegistry returns a registry over the built-in sources and this machine's own profile directory.

func (*Registry) Dir

func (r *Registry) Dir() string

Dir is where this registry looks for profiles kept on this machine.

func (*Registry) Get

func (r *Registry) Get(name string) (*Profile, error)

Get resolves a name to a profile.

func (*Registry) HasHandshake

func (r *Registry) HasHandshake(name string) bool

HasHandshake reports whether a name resolves to a profile built from a real captured ClientHello rather than to a catalogue entry. It is what lets a caller tell "this is my browser" from "this is close to some browser".

func (*Registry) KeptHere

func (r *Registry) KeptHere() []string

KeptHere lists the profiles in this machine's own directory, which are the ones that win over anything shipped.

func (*Registry) Measured

func (r *Registry) Measured() []Group

Measured lists the profiles taken from a real browser, grouped by version.

Both sources at once, not one instead of the other: a machine that has measured Chrome 151 on its own platform still resolves the shipped profile for the others, and a listing that showed only the local one would be saying less than is true.

func (*Registry) Names

func (r *Registry) Names() []string

Names lists every profile that can be resolved, measured ones first.

func (*Registry) Register

func (r *Registry) Register(p *Profile) error

Register adds or replaces a profile.

func (*Registry) SetDir

func (r *Registry) SetDir(dir string)

SetDir points the registry at another directory.

type Setting

type Setting struct {
	ID    uint16 `json:"id"`
	Value uint32 `json:"value"`
}

Setting is one HTTP/2 setting. Order matters, so this is a list.

type Variant

type Variant struct {
	Platform string
	// Local says this one came from this machine's directory, which is the copy
	// that will be used.
	Local bool
}

Variant is one platform a version was measured on.

Jump to

Keyboard shortcuts

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