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 ¶
- Variables
- func DefaultDir() string
- func HostPlatform() string
- func Names() []string
- func Register(p *Profile) error
- func Split(name string) (version, platform string)
- type Field
- type Group
- type HTTP1
- type HTTP2
- type Priority
- type Profile
- func (p *Profile) ClientProfile() (profiles.ClientProfile, error)
- func (p *Profile) Clone() *Profile
- func (p *Profile) Header(name string) (string, bool)
- func (p *Profile) HeaderOrder() []string
- func (p *Profile) Save() ([]byte, error)
- func (p *Profile) ShufflesExtensions() bool
- func (p *Profile) Source() string
- func (p *Profile) Spec() (tls.ClientHelloSpec, error)
- type Registry
- func (r *Registry) Dir() string
- func (r *Registry) Get(name string) (*Profile, error)
- func (r *Registry) HasHandshake(name string) bool
- func (r *Registry) KeptHere() []string
- func (r *Registry) Measured() []Group
- func (r *Registry) Names() []string
- func (r *Registry) Register(p *Profile) error
- func (r *Registry) SetDir(dir string)
- type Setting
- type Variant
Constants ¶
This section is empty.
Variables ¶
var Default = NewRegistry()
Default is the registry the package-level functions use.
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.
Types ¶
type Field ¶
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 ¶
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 (*Profile) ClientProfile ¶
func (p *Profile) ClientProfile() (profiles.ClientProfile, error)
ClientProfile converts to the form the transport consumes.
func (*Profile) HeaderOrder ¶
HeaderOrder is the header names in profile order, which is what the transport needs to reproduce the browser's ordering.
func (*Profile) Save ¶
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 ¶
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 ¶
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) HasHandshake ¶
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 ¶
KeptHere lists the profiles in this machine's own directory, which are the ones that win over anything shipped.
func (*Registry) Measured ¶
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.