Documentation
¶
Overview ¶
Package quic decrypts QUIC Initial packets far enough to recover the TLS ClientHello inside them.
This exists because roughly a third of web traffic is now HTTP/3, and a sensor that only fingerprints TLS over TCP is blind to all of it. QUIC encrypts its Initial packets, but not secretly: the keys are derived from the Destination Connection ID, which travels in the clear precisely so that middleboxes, load balancers, and observers can do this. Decrypting one is a key schedule and an AEAD open, not an attack.
Index ¶
Constants ¶
const DefaultMaxPending = 8192
DefaultMaxPending is how many in-progress QUIC handshakes are tracked at once.
const MaxCryptoBytes = 16384
MaxCryptoBytes bounds how much handshake data will be buffered for one connection. A ClientHello with a hybrid post-quantum key share runs to a few kilobytes; anything past this is not a handshake we need to see.
const MinInitialDatagram = 1200
MinInitialDatagram is the padding floor RFC 9000 section 14.1 imposes on a client's Initial datagram, so that a server never amplifies traffic toward an unvalidated address.
const Version1 uint32 = 0x00000001
Version1 is the only QUIC version handled here (RFC 9000). Draft versions and QUIC v2 use different initial salts, so treating them as v1 would derive wrong keys and produce garbage rather than a clean failure.
Variables ¶
var ( // ErrNotInitial means the datagram is not a QUIC v1 client Initial packet. // This is the overwhelmingly common outcome on a real network, where most // UDP is DNS, QUIC short-header packets, or something else entirely, so it // carries no diagnostic weight. ErrNotInitial = errors.New("quic: not a v1 Initial packet") // ErrMalformed means the packet claims to be an Initial but does not parse. ErrMalformed = errors.New("quic: malformed Initial packet") )
Errors reported by ParseInitial.
var ErrTruncated = errors.New("quic: truncated")
ErrTruncated means the buffer ended in the middle of a field.
Functions ¶
func BuildClientInitial ¶
func BuildClientInitial(dcid, scid []byte, cryptoOffset uint64, crypto []byte, size int) ([]byte, error)
BuildClientInitial constructs a client Initial packet carrying handshake bytes in a CRYPTO frame.
This is the exact inverse of ParseInitial: seal with the AEAD, then apply header protection. It exists so the synthetic capture generator can produce genuine QUIC traffic rather than a plausible-looking imitation, and so the tests can round-trip through both directions instead of only agreeing with themselves.
size is the target datagram length; the payload is padded to reach it.
Types ¶
type CryptoFrame ¶
CryptoFrame is one fragment of the TLS handshake stream.
type InitialPacket ¶
type InitialPacket struct {
Version uint32
DCID []byte
SCID []byte
Frames []CryptoFrame
}
InitialPacket is a decrypted client Initial.
func ParseInitial ¶
func ParseInitial(datagram []byte) (*InitialPacket, error)
ParseInitial decrypts every client Initial packet in a datagram and returns the CRYPTO frames they carry.
A datagram can coalesce several QUIC packets, so this walks the whole thing rather than stopping after the first. Non-Initial packets and packets that fail authentication are skipped, because a passive observer sees plenty of both and neither is an error worth reporting.
type Reassembler ¶
type Reassembler struct {
// contains filtered or unexported fields
}
Reassembler turns a stream of UDP datagrams into TLS client fingerprints.
It mirrors fingerprint.Reassembler, which handles the same job over TCP, and deliberately so: a client that speaks both HTTP/2 and HTTP/3 should produce fingerprints that can be compared, and the only difference in the output is the transport character at the front of the JA4.
func NewReassembler ¶
func NewReassembler(max int) *Reassembler
NewReassembler returns a ready Reassembler. max <= 0 selects the default.
func (*Reassembler) Feed ¶
func (r *Reassembler) Feed(key model.FlowKey, datagram []byte) *fingerprint.Result
Feed supplies one UDP datagram.
It returns a non-nil result on the datagram that completes the ClientHello. Callers may feed every UDP payload unconditionally: anything that is not a QUIC Initial is rejected by a length check and a header bit before any cryptography happens.
func (*Reassembler) Forget ¶
func (r *Reassembler) Forget(key model.FlowKey)
Forget drops any partial state for a flow.
func (*Reassembler) Pending ¶
func (r *Reassembler) Pending() int
Pending reports how many partial handshakes are buffered.