Documentation
¶
Overview ¶
Package quic decodes the cleartext part of a QUIC handshake.
QUIC Initial packets are encrypted, but not secretly: the keys are derived from the client's Destination Connection ID, which travels in the clear in the first packet (RFC 9001 section 5.2). Anyone who can see the packet can derive the keys. The protection exists to stop middleboxes ossifying the wire format, not to hide the handshake — so a passive observer can recover the ClientHello and ServerHello exactly as it can over TCP.
This matters because ignoring QUIC does not merely lose some traffic, it loses it selectively. Google and Cloudflare advertise HTTP/3, browsers switch to it after the first visit, and those providers deployed hybrid post-quantum key exchange early. A TCP-only inventory therefore reports a more classical world than the one on the wire.
Like tlsparse, everything here is a pure function over bytes with no I/O.
Index ¶
Constants ¶
const ( Version1 uint32 = 0x00000001 // RFC 9000 Version2 uint32 = 0x6b3343cf // RFC 9369 )
QUIC versions whose Initial keys this package can derive.
const ( TypeInitial = 0x00 TypeZeroRTT = 0x01 TypeHandshake = 0x02 TypeRetry = 0x03 )
Long header packet types (RFC 9000 section 17.2).
Variables ¶
var ( // ErrNotQUIC means the bytes are not a QUIC long-header packet. ErrNotQUIC = errors.New("quic: not a long-header packet") // ErrTruncated means a field ran past the end of the datagram. ErrTruncated = errors.New("quic: truncated packet") // ErrUnsupportedVersion means the QUIC version has different initial // keys than this package knows how to derive. ErrUnsupportedVersion = errors.New("quic: unsupported version") )
Functions ¶
func IsLongHeader ¶
IsLongHeader reports whether b starts a long-header packet with the fixed bit set. It is the cheap test used to reject the great majority of UDP traffic without deriving anything.
func Open ¶
Open removes header protection and decrypts the packet payload.
The datagram is not modified: header protection is undone on a copy, because the same bytes are still owned by the capture buffer and because a caller may want to re-read the protected form.
The packet number is taken as the truncated value on the wire. QUIC transmits only the low bytes and expects the peer to reconstruct the full number from the largest one it has acknowledged (RFC 9000 section 17.1), which a passive observer cannot always do. It does not matter at the Initial level: a connection's Initial packet numbers start at zero and there are only ever a handful, so the truncated value is the full value. If that assumption were ever violated the AEAD would fail closed rather than return wrong plaintext, because the packet number is part of the nonce.
Types ¶
type CryptoFrame ¶
CryptoFrame is one run of handshake bytes at a given stream offset.
func CryptoFrames ¶
func CryptoFrames(payload []byte) []CryptoFrame
CryptoFrames extracts the CRYPTO frames from a decrypted packet payload.
Frames are not a stream: a ClientHello too large for one datagram is split across packets and arrives as several CRYPTO frames carrying offsets, in any order. The offsets are what the caller reassembles on, exactly as TCP sequence numbers are used for the same handshake over TCP — and for the same reason, since a post-quantum ClientHello does not fit in one Initial.
Padding is skipped rather than treated as an error: an Initial is padded to at least 1200 bytes, so most of what arrives here is padding.
type Keys ¶
type Keys struct {
// contains filtered or unexported fields
}
Keys are the Initial-level secrets for one direction.
func DeriveInitialKeys ¶
DeriveInitialKeys computes the Initial keys for one direction of a connection, from the Destination Connection ID the client chose for its very first packet.
The same connection ID derives both directions: the server's Initial packets are protected with keys from the client's *original* DCID, not from the connection ID the server later selects. Using the server's own connection ID is the obvious mistake and produces an authentication failure rather than anything diagnostic.
type LongHeader ¶
type LongHeader struct {
Type byte
Version uint32
DCID []byte
SCID []byte
Token []byte
// PNOffset is where the (still protected) packet number begins.
PNOffset int
// Length is the value of the Length field: packet number plus payload.
Length int
// End is where this packet ends within the datagram. A datagram may
// carry several packets back to back — typically an Initial followed by
// a Handshake — so this is where the next one starts.
End int
}
LongHeader is the cleartext part of a long-header packet.
func ParseLongHeader ¶
func ParseLongHeader(b []byte) (*LongHeader, error)
ParseLongHeader decodes the unprotected fields of a long-header packet.
Everything it reads is in the clear: the version, the connection IDs, the token and the length. Only the packet number and payload are protected, and the connection ID read here is what derives the keys for them.