mirror of
https://github.com/SagerNet/sing-box.git
synced 2024-11-27 02:51:36 +00:00
358 lines
10 KiB
Go
358 lines
10 KiB
Go
|
// Copyright (c) 2018, Open Systems AG. All rights reserved.
|
||
|
//
|
||
|
// Use of this source code is governed by a BSD-style license
|
||
|
// that can be found in the LICENSE file in the root of the source
|
||
|
// tree.
|
||
|
|
||
|
package ja3
|
||
|
|
||
|
import (
|
||
|
"encoding/binary"
|
||
|
"strconv"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
// Constants used for parsing
|
||
|
recordLayerHeaderLen int = 5
|
||
|
handshakeHeaderLen int = 6
|
||
|
randomDataLen int = 32
|
||
|
sessionIDHeaderLen int = 1
|
||
|
cipherSuiteHeaderLen int = 2
|
||
|
compressMethodHeaderLen int = 1
|
||
|
extensionsHeaderLen int = 2
|
||
|
extensionHeaderLen int = 4
|
||
|
sniExtensionHeaderLen int = 5
|
||
|
ecExtensionHeaderLen int = 2
|
||
|
ecpfExtensionHeaderLen int = 1
|
||
|
versionExtensionHeaderLen int = 1
|
||
|
signatureAlgorithmsExtensionHeaderLen int = 2
|
||
|
contentType uint8 = 22
|
||
|
handshakeType uint8 = 1
|
||
|
sniExtensionType uint16 = 0
|
||
|
sniNameDNSHostnameType uint8 = 0
|
||
|
ecExtensionType uint16 = 10
|
||
|
ecpfExtensionType uint16 = 11
|
||
|
versionExtensionType uint16 = 43
|
||
|
signatureAlgorithmsExtensionType uint16 = 13
|
||
|
|
||
|
// Versions
|
||
|
// The bitmask covers the versions SSL3.0 to TLS1.2
|
||
|
tlsVersionBitmask uint16 = 0xFFFC
|
||
|
tls13 uint16 = 0x0304
|
||
|
|
||
|
// GREASE values
|
||
|
// The bitmask covers all GREASE values
|
||
|
GreaseBitmask uint16 = 0x0F0F
|
||
|
|
||
|
// Constants used for marshalling
|
||
|
dashByte = byte(45)
|
||
|
commaByte = byte(44)
|
||
|
)
|
||
|
|
||
|
// parseSegment to populate the corresponding ClientHello object or return an error
|
||
|
func (j *ClientHello) parseSegment(segment []byte) error {
|
||
|
// Check if we can decode the next fields
|
||
|
if len(segment) < recordLayerHeaderLen {
|
||
|
return &ParseError{LengthErr, 1}
|
||
|
}
|
||
|
|
||
|
// Check if we have "Content Type: Handshake (22)"
|
||
|
contType := uint8(segment[0])
|
||
|
if contType != contentType {
|
||
|
return &ParseError{errType: ContentTypeErr}
|
||
|
}
|
||
|
|
||
|
// Check if TLS record layer version is supported
|
||
|
tlsRecordVersion := uint16(segment[1])<<8 | uint16(segment[2])
|
||
|
if tlsRecordVersion&tlsVersionBitmask != 0x0300 && tlsRecordVersion != tls13 {
|
||
|
return &ParseError{VersionErr, 1}
|
||
|
}
|
||
|
|
||
|
// Check that the Handshake is as long as expected from the length field
|
||
|
segmentLen := uint16(segment[3])<<8 | uint16(segment[4])
|
||
|
if len(segment[recordLayerHeaderLen:]) < int(segmentLen) {
|
||
|
return &ParseError{LengthErr, 2}
|
||
|
}
|
||
|
// Keep the Handshake messege, ignore any additional following record types
|
||
|
hs := segment[recordLayerHeaderLen : recordLayerHeaderLen+int(segmentLen)]
|
||
|
|
||
|
err := j.parseHandshake(hs)
|
||
|
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// parseHandshake body
|
||
|
func (j *ClientHello) parseHandshake(hs []byte) error {
|
||
|
// Check if we can decode the next fields
|
||
|
if len(hs) < handshakeHeaderLen+randomDataLen+sessionIDHeaderLen {
|
||
|
return &ParseError{LengthErr, 3}
|
||
|
}
|
||
|
|
||
|
// Check if we have "Handshake Type: Client Hello (1)"
|
||
|
handshType := uint8(hs[0])
|
||
|
if handshType != handshakeType {
|
||
|
return &ParseError{errType: HandshakeTypeErr}
|
||
|
}
|
||
|
|
||
|
// Check if actual length of handshake matches (this is a great exclusion criterion for false positives,
|
||
|
// as these fields have to match the actual length of the rest of the segment)
|
||
|
handshakeLen := uint32(hs[1])<<16 | uint32(hs[2])<<8 | uint32(hs[3])
|
||
|
if len(hs[4:]) != int(handshakeLen) {
|
||
|
return &ParseError{LengthErr, 4}
|
||
|
}
|
||
|
|
||
|
// Check if Client Hello version is supported
|
||
|
tlsVersion := uint16(hs[4])<<8 | uint16(hs[5])
|
||
|
if tlsVersion&tlsVersionBitmask != 0x0300 && tlsVersion != tls13 {
|
||
|
return &ParseError{VersionErr, 2}
|
||
|
}
|
||
|
j.Version = tlsVersion
|
||
|
|
||
|
// Check if we can decode the next fields
|
||
|
sessionIDLen := uint8(hs[38])
|
||
|
if len(hs) < handshakeHeaderLen+randomDataLen+sessionIDHeaderLen+int(sessionIDLen) {
|
||
|
return &ParseError{LengthErr, 5}
|
||
|
}
|
||
|
|
||
|
// Cipher Suites
|
||
|
cs := hs[handshakeHeaderLen+randomDataLen+sessionIDHeaderLen+int(sessionIDLen):]
|
||
|
|
||
|
// Check if we can decode the next fields
|
||
|
if len(cs) < cipherSuiteHeaderLen {
|
||
|
return &ParseError{LengthErr, 6}
|
||
|
}
|
||
|
|
||
|
csLen := uint16(cs[0])<<8 | uint16(cs[1])
|
||
|
numCiphers := int(csLen / 2)
|
||
|
cipherSuites := make([]uint16, 0, numCiphers)
|
||
|
|
||
|
// Check if we can decode the next fields
|
||
|
if len(cs) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen {
|
||
|
return &ParseError{LengthErr, 7}
|
||
|
}
|
||
|
|
||
|
for i := 0; i < numCiphers; i++ {
|
||
|
cipherSuite := uint16(cs[2+i<<1])<<8 | uint16(cs[3+i<<1])
|
||
|
cipherSuites = append(cipherSuites, cipherSuite)
|
||
|
}
|
||
|
j.CipherSuites = cipherSuites
|
||
|
|
||
|
// Check if we can decode the next fields
|
||
|
compressMethodLen := uint16(cs[cipherSuiteHeaderLen+int(csLen)])
|
||
|
if len(cs) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen+int(compressMethodLen) {
|
||
|
return &ParseError{LengthErr, 8}
|
||
|
}
|
||
|
|
||
|
// Extensions
|
||
|
exs := cs[cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen+int(compressMethodLen):]
|
||
|
|
||
|
err := j.parseExtensions(exs)
|
||
|
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// parseExtensions of the handshake
|
||
|
func (j *ClientHello) parseExtensions(exs []byte) error {
|
||
|
// Check for no extensions, this fields header is nonexistent if no body is used
|
||
|
if len(exs) == 0 {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Check if we can decode the next fields
|
||
|
if len(exs) < extensionsHeaderLen {
|
||
|
return &ParseError{LengthErr, 9}
|
||
|
}
|
||
|
|
||
|
exsLen := uint16(exs[0])<<8 | uint16(exs[1])
|
||
|
exs = exs[extensionsHeaderLen:]
|
||
|
|
||
|
// Check if we can decode the next fields
|
||
|
if len(exs) < int(exsLen) {
|
||
|
return &ParseError{LengthErr, 10}
|
||
|
}
|
||
|
|
||
|
var sni []byte
|
||
|
var extensions, ellipticCurves []uint16
|
||
|
var ellipticCurvePF []uint8
|
||
|
var versions []uint16
|
||
|
var signatureAlgorithms []uint16
|
||
|
for len(exs) > 0 {
|
||
|
|
||
|
// Check if we can decode the next fields
|
||
|
if len(exs) < extensionHeaderLen {
|
||
|
return &ParseError{LengthErr, 11}
|
||
|
}
|
||
|
|
||
|
exType := uint16(exs[0])<<8 | uint16(exs[1])
|
||
|
exLen := uint16(exs[2])<<8 | uint16(exs[3])
|
||
|
// Ignore any GREASE extensions
|
||
|
extensions = append(extensions, exType)
|
||
|
// Check if we can decode the next fields
|
||
|
if len(exs) < extensionHeaderLen+int(exLen) {
|
||
|
return &ParseError{LengthErr, 12}
|
||
|
}
|
||
|
|
||
|
sex := exs[extensionHeaderLen : extensionHeaderLen+int(exLen)]
|
||
|
|
||
|
switch exType {
|
||
|
case sniExtensionType: // Extensions: server_name
|
||
|
|
||
|
// Check if we can decode the next fields
|
||
|
if len(sex) < sniExtensionHeaderLen {
|
||
|
return &ParseError{LengthErr, 13}
|
||
|
}
|
||
|
|
||
|
sniType := uint8(sex[2])
|
||
|
sniLen := uint16(sex[3])<<8 | uint16(sex[4])
|
||
|
sex = sex[sniExtensionHeaderLen:]
|
||
|
|
||
|
// Check if we can decode the next fields
|
||
|
if len(sex) != int(sniLen) {
|
||
|
return &ParseError{LengthErr, 14}
|
||
|
}
|
||
|
|
||
|
switch sniType {
|
||
|
case sniNameDNSHostnameType:
|
||
|
sni = sex
|
||
|
default:
|
||
|
return &ParseError{errType: SNITypeErr}
|
||
|
}
|
||
|
case ecExtensionType: // Extensions: supported_groups
|
||
|
|
||
|
// Check if we can decode the next fields
|
||
|
if len(sex) < ecExtensionHeaderLen {
|
||
|
return &ParseError{LengthErr, 15}
|
||
|
}
|
||
|
|
||
|
ecsLen := uint16(sex[0])<<8 | uint16(sex[1])
|
||
|
numCurves := int(ecsLen / 2)
|
||
|
ellipticCurves = make([]uint16, 0, numCurves)
|
||
|
sex = sex[ecExtensionHeaderLen:]
|
||
|
|
||
|
// Check if we can decode the next fields
|
||
|
if len(sex) != int(ecsLen) {
|
||
|
return &ParseError{LengthErr, 16}
|
||
|
}
|
||
|
|
||
|
for i := 0; i < numCurves; i++ {
|
||
|
ecType := uint16(sex[i*2])<<8 | uint16(sex[1+i*2])
|
||
|
ellipticCurves = append(ellipticCurves, ecType)
|
||
|
}
|
||
|
|
||
|
case ecpfExtensionType: // Extensions: ec_point_formats
|
||
|
|
||
|
// Check if we can decode the next fields
|
||
|
if len(sex) < ecpfExtensionHeaderLen {
|
||
|
return &ParseError{LengthErr, 17}
|
||
|
}
|
||
|
|
||
|
ecpfsLen := uint8(sex[0])
|
||
|
numPF := int(ecpfsLen)
|
||
|
ellipticCurvePF = make([]uint8, numPF)
|
||
|
sex = sex[ecpfExtensionHeaderLen:]
|
||
|
|
||
|
// Check if we can decode the next fields
|
||
|
if len(sex) != numPF {
|
||
|
return &ParseError{LengthErr, 18}
|
||
|
}
|
||
|
|
||
|
for i := 0; i < numPF; i++ {
|
||
|
ellipticCurvePF[i] = uint8(sex[i])
|
||
|
}
|
||
|
case versionExtensionType:
|
||
|
if len(sex) < versionExtensionHeaderLen {
|
||
|
return &ParseError{LengthErr, 19}
|
||
|
}
|
||
|
versionsLen := int(sex[0])
|
||
|
for i := 0; i < versionsLen; i += 2 {
|
||
|
versions = append(versions, binary.BigEndian.Uint16(sex[1:][i:]))
|
||
|
}
|
||
|
case signatureAlgorithmsExtensionType:
|
||
|
if len(sex) < signatureAlgorithmsExtensionHeaderLen {
|
||
|
return &ParseError{LengthErr, 20}
|
||
|
}
|
||
|
ssaLen := binary.BigEndian.Uint16(sex)
|
||
|
for i := 0; i < int(ssaLen); i += 2 {
|
||
|
signatureAlgorithms = append(signatureAlgorithms, binary.BigEndian.Uint16(sex[2:][i:]))
|
||
|
}
|
||
|
}
|
||
|
exs = exs[4+exLen:]
|
||
|
}
|
||
|
j.ServerName = string(sni)
|
||
|
j.Extensions = extensions
|
||
|
j.EllipticCurves = ellipticCurves
|
||
|
j.EllipticCurvePF = ellipticCurvePF
|
||
|
j.Versions = versions
|
||
|
j.SignatureAlgorithms = signatureAlgorithms
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// marshalJA3 into a byte string
|
||
|
func (j *ClientHello) marshalJA3() {
|
||
|
// An uint16 can contain numbers with up to 5 digits and an uint8 can contain numbers with up to 3 digits, but we
|
||
|
// also need a byte for each separating character, except at the end.
|
||
|
byteStringLen := 6*(1+len(j.CipherSuites)+len(j.Extensions)+len(j.EllipticCurves)) + 4*len(j.EllipticCurvePF) - 1
|
||
|
byteString := make([]byte, 0, byteStringLen)
|
||
|
|
||
|
// Version
|
||
|
byteString = strconv.AppendUint(byteString, uint64(j.Version), 10)
|
||
|
byteString = append(byteString, commaByte)
|
||
|
|
||
|
// Cipher Suites
|
||
|
if len(j.CipherSuites) != 0 {
|
||
|
for _, val := range j.CipherSuites {
|
||
|
if val&GreaseBitmask != 0x0A0A {
|
||
|
continue
|
||
|
}
|
||
|
byteString = strconv.AppendUint(byteString, uint64(val), 10)
|
||
|
byteString = append(byteString, dashByte)
|
||
|
}
|
||
|
// Replace last dash with a comma
|
||
|
byteString[len(byteString)-1] = commaByte
|
||
|
} else {
|
||
|
byteString = append(byteString, commaByte)
|
||
|
}
|
||
|
|
||
|
// Extensions
|
||
|
if len(j.Extensions) != 0 {
|
||
|
for _, val := range j.Extensions {
|
||
|
if val&GreaseBitmask != 0x0A0A {
|
||
|
continue
|
||
|
}
|
||
|
byteString = strconv.AppendUint(byteString, uint64(val), 10)
|
||
|
byteString = append(byteString, dashByte)
|
||
|
}
|
||
|
// Replace last dash with a comma
|
||
|
byteString[len(byteString)-1] = commaByte
|
||
|
} else {
|
||
|
byteString = append(byteString, commaByte)
|
||
|
}
|
||
|
|
||
|
// Elliptic curves
|
||
|
if len(j.EllipticCurves) != 0 {
|
||
|
for _, val := range j.EllipticCurves {
|
||
|
if val&GreaseBitmask != 0x0A0A {
|
||
|
continue
|
||
|
}
|
||
|
byteString = strconv.AppendUint(byteString, uint64(val), 10)
|
||
|
byteString = append(byteString, dashByte)
|
||
|
}
|
||
|
// Replace last dash with a comma
|
||
|
byteString[len(byteString)-1] = commaByte
|
||
|
} else {
|
||
|
byteString = append(byteString, commaByte)
|
||
|
}
|
||
|
|
||
|
// ECPF
|
||
|
if len(j.EllipticCurvePF) != 0 {
|
||
|
for _, val := range j.EllipticCurvePF {
|
||
|
byteString = strconv.AppendUint(byteString, uint64(val), 10)
|
||
|
byteString = append(byteString, dashByte)
|
||
|
}
|
||
|
// Remove last dash
|
||
|
byteString = byteString[:len(byteString)-1]
|
||
|
}
|
||
|
|
||
|
j.ja3ByteString = byteString
|
||
|
}
|