mirror of
https://github.com/TecharoHQ/anubis.git
synced 2026-04-13 03:58:45 +00:00
177 lines
3.6 KiB
Go
177 lines
3.6 KiB
Go
package fingerprint
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"crypto/tls"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// TLSFingerprintJA4 represents a JA4 fingerprint
|
|
type TLSFingerprintJA4 struct {
|
|
A [10]byte
|
|
B [6]byte
|
|
C [6]byte
|
|
}
|
|
|
|
func (f *TLSFingerprintJA4) String() string {
|
|
if f == nil {
|
|
return ""
|
|
}
|
|
|
|
return strings.Join([]string{
|
|
string(f.A[:]),
|
|
hex.EncodeToString(f.B[:]),
|
|
hex.EncodeToString(f.C[:]),
|
|
}, "_")
|
|
}
|
|
|
|
func buildJA4(hello *tls.ClientHelloInfo) (ja4 TLSFingerprintJA4) {
|
|
buf := make([]byte, 0, 36)
|
|
|
|
hasQuic := false
|
|
|
|
for _, ext := range hello.Extensions {
|
|
if ext == extensionQUICTransportParameters {
|
|
hasQuic = true
|
|
}
|
|
}
|
|
|
|
switch hasQuic {
|
|
case true:
|
|
buf = append(buf, 'q')
|
|
case false:
|
|
buf = append(buf, 't')
|
|
}
|
|
|
|
{
|
|
var sslVersion uint16
|
|
for _, v := range hello.SupportedVersions {
|
|
if v&greaseMask != greaseValue {
|
|
if v > sslVersion {
|
|
sslVersion = v
|
|
}
|
|
}
|
|
}
|
|
|
|
switch sslVersion {
|
|
case tls.VersionTLS10:
|
|
buf = append(buf, '1', '0')
|
|
case tls.VersionTLS11:
|
|
buf = append(buf, '1', '1')
|
|
case tls.VersionTLS12:
|
|
buf = append(buf, '1', '2')
|
|
case tls.VersionTLS13:
|
|
buf = append(buf, '1', '3')
|
|
default:
|
|
sslVersion -= 0x0201
|
|
buf = strconv.AppendUint(buf, uint64(sslVersion>>8), 10)
|
|
buf = strconv.AppendUint(buf, uint64(sslVersion&0xff), 10)
|
|
}
|
|
|
|
}
|
|
|
|
if slices.Contains(hello.Extensions, extensionServerName) && hello.ServerName != "" {
|
|
buf = append(buf, 'd')
|
|
} else {
|
|
buf = append(buf, 'i')
|
|
}
|
|
|
|
ciphers := make([]uint16, 0, len(hello.CipherSuites))
|
|
for _, cipher := range hello.CipherSuites {
|
|
if cipher&greaseMask != greaseValue {
|
|
ciphers = append(ciphers, cipher)
|
|
}
|
|
}
|
|
|
|
extensionCount := 0
|
|
extensions := make([]uint16, 0, len(hello.Extensions))
|
|
for _, extension := range hello.Extensions {
|
|
if extension&greaseMask != greaseValue {
|
|
extensionCount++
|
|
if extension != extensionALPN && extension != extensionServerName {
|
|
extensions = append(extensions, extension)
|
|
}
|
|
}
|
|
}
|
|
|
|
schemes := make([]tls.SignatureScheme, 0, len(hello.SignatureSchemes))
|
|
|
|
for _, scheme := range hello.SignatureSchemes {
|
|
if scheme&greaseMask != greaseValue {
|
|
schemes = append(schemes, scheme)
|
|
}
|
|
}
|
|
|
|
//TODO: maybe little endian
|
|
slices.Sort(ciphers)
|
|
slices.Sort(extensions)
|
|
//slices.Sort(schemes)
|
|
|
|
if len(ciphers) < 10 {
|
|
buf = append(buf, '0')
|
|
buf = strconv.AppendUint(buf, uint64(len(ciphers)), 10)
|
|
} else if len(ciphers) > 99 {
|
|
buf = append(buf, '9', '9')
|
|
} else {
|
|
buf = strconv.AppendUint(buf, uint64(len(ciphers)), 10)
|
|
}
|
|
|
|
if extensionCount < 10 {
|
|
buf = append(buf, '0')
|
|
buf = strconv.AppendUint(buf, uint64(extensionCount), 10)
|
|
} else if extensionCount > 99 {
|
|
buf = append(buf, '9', '9')
|
|
} else {
|
|
buf = strconv.AppendUint(buf, uint64(extensionCount), 10)
|
|
}
|
|
|
|
if len(hello.SupportedProtos) > 0 && len(hello.SupportedProtos[0]) > 1 {
|
|
buf = append(buf, hello.SupportedProtos[0][0], hello.SupportedProtos[0][len(hello.SupportedProtos[0])-1])
|
|
} else {
|
|
buf = append(buf, '0', '0')
|
|
}
|
|
|
|
copy(ja4.A[:], buf)
|
|
|
|
ja4.B = ja4SHA256(uint16SliceToHex(ciphers))
|
|
|
|
extBuf := uint16SliceToHex(extensions)
|
|
|
|
if len(schemes) > 0 {
|
|
extBuf = append(extBuf, '_')
|
|
extBuf = append(extBuf, uint16SliceToHex(schemes)...)
|
|
}
|
|
|
|
ja4.C = ja4SHA256(extBuf)
|
|
|
|
return ja4
|
|
}
|
|
|
|
func uint16SliceToHex[T ~uint16](in []T) (out []byte) {
|
|
if len(in) == 0 {
|
|
return out
|
|
}
|
|
out = slices.Grow(out, hex.EncodedLen(len(in)*2)+len(in))
|
|
|
|
for _, n := range in {
|
|
out = append(out, fmt.Sprintf("%04x", uint16(n))...)
|
|
out = append(out, ',')
|
|
}
|
|
out = out[:len(out)-1]
|
|
|
|
return out
|
|
}
|
|
|
|
func ja4SHA256(buf []byte) [6]byte {
|
|
if len(buf) == 0 {
|
|
return [6]byte{0, 0, 0, 0, 0, 0}
|
|
}
|
|
sum := sha256.Sum256(buf)
|
|
|
|
return [6]byte(sum[:6])
|
|
}
|