clienthellod
, read as "client-hello-D", is a TLS ClientHello/QUIC Initial Packet reflection service. It can be used to parses TLS ClientHello messages and QUIC Initial Packets into human-readable and highly programmable formats such as JSON.
Is is a part of the TLS fingerprintability research project which spans tlsfingerprint.io and quic.tlsfingerprint.io. It parses the ClientHello messages sent by TLS clients and QUIC Client Initial Packets sent by QUIC clients and display the parsed information in a human-readable format with high programmability.
See tlsfingerprint.io and quic.tlsfingerprint.io for more details about the project.
clienthellod
comes as a Go library, which can be used to parse both TLS and QUIC protocols.
tlsFingerprinter := clienthellod.NewTLSFingerprinter()
quicFingerprinter := clienthellod.NewQUICFingerprinter()
tcpLis, err := net.Listen("tcp", ":443")
defer tcpLis.Close()
conn, err := tcpLis.Accept()
if err != nil {
panic(err)
}
defer conn.Close()
ch, err := clienthellod.ReadClientHello(conn) // reads ClientHello from the connection
if err != nil {
panic(err)
}
err := ch.ParseClientHello() // parses ClientHello's fields
if err != nil {
panic(err)
}
jsonB, err = json.MarshalIndent(ch, "", " ")
if err != nil {
panic(err)
}
fmt.Println(string(jsonB))
fmt.Println("ClientHello ID: " + ch.HexID) // prints ClientHello's original fingerprint ID calculated using observed TLS extension order
fmt.Println("ClientHello NormID: " + ch.NormHexID) // prints ClientHello's normalized fingerprint ID calculated using sorted TLS extension list
ch, err := clienthellod.UnmarshalClientHello(raw)
if err != nil {
panic(err)
}
// err := ch.ParseClientHello() // no need to call again, UnmarshalClientHello automatically calls ParseClientHello
udpConn, err := net.ListenUDP("udp", ":443")
defer udpConn.Close()
buf := make([]byte, 65535)
n, addr, err := udpConn.ReadFromUDP(buf)
if err != nil {
panic(err)
}
ci, err := clienthellod.UnmarshalQUICClientInitialPacket(buf[:n]) // decodes QUIC Client Initial Packet
if err != nil {
panic(err)
}
jsonB, err = json.MarshalIndent(cip, "", " ")
if err != nil {
panic(err)
}
fmt.Println(string(jsonB)) // including fingerprint IDs of: ClientInitialPacket, QUIC Header, QUIC ClientHello, QUIC Transport Parameters' combination
Implementations including Chrome/Chromium sends oversized Client Hello which does not fit into one single QUIC packet, in which case multiple QUIC Initial Packets are sent.
gci := GatherClientInitials() // Each GatherClientInitials reassembles one QUIC Client Initial Packets stream. Use a QUIC Fingerprinter for multiple potential senders, which automatically demultiplexes the packets based on the source address.
udpConn, err := net.ListenUDP("udp", ":443")
defer udpConn.Close()
for {
buf := make([]byte, 65535)
n, addr, err := udpConn.ReadFromUDP(buf)
if err != nil {
panic(err)
}
if addr != knownSenderAddr {
continue
}
ci, err := clienthellod.UnmarshalQUICClientInitialPacket(buf[:n]) // decodes QUIC Client Initial Packet
if err != nil {
panic(err)
}
err = gci.AddPacket(ci)
if err != nil {
panic(err)
}
}
We also provide clienthellod as a Caddy Module in modcaddy
, which you can use with Caddy to capture ClientHello messages and QUIC Client Initial Packets. See modcaddy for more details.
This project is developed and distributed under Apache-2.0 license.