From 1b3a9ad4c51e6eb16e2025df9ed4d38580540063 Mon Sep 17 00:00:00 2001 From: Gaukas Wang Date: Mon, 14 Nov 2022 17:02:01 -0700 Subject: [PATCH] feat: Chrome 106 Shuffled Fingerprint (#133) * feat: Chrome 107 fp with shuffler - added `HelloChrome_107` (not used by `HelloChrome_Auto`) - added `shuffleExtensions()` to shuffle the order of extensions in a `ClientHelloSpec` * fix: rename for chronologically accuracy - Renamed `HelloChrome_107` to `HelloChrome_106_Shuffle` to match the versioning info from https://groups.google.com/a/chromium.org/g/blink-dev/c/zdmNs2rTyVI/m/MAiQwQkwCAAJ --- u_common.go | 21 +++++++++-------- u_parrots.go | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 76 insertions(+), 11 deletions(-) diff --git a/u_common.go b/u_common.go index a80bce3f..6cfc3f60 100644 --- a/u_common.go +++ b/u_common.go @@ -173,16 +173,17 @@ var ( HelloFirefox_102 = ClientHelloID{helloFirefox, "102", nil} HelloFirefox_105 = ClientHelloID{helloFirefox, "105", nil} - HelloChrome_Auto = HelloChrome_102 - HelloChrome_58 = ClientHelloID{helloChrome, "58", nil} - HelloChrome_62 = ClientHelloID{helloChrome, "62", nil} - HelloChrome_70 = ClientHelloID{helloChrome, "70", nil} - HelloChrome_72 = ClientHelloID{helloChrome, "72", nil} - HelloChrome_83 = ClientHelloID{helloChrome, "83", nil} - HelloChrome_87 = ClientHelloID{helloChrome, "87", nil} - HelloChrome_96 = ClientHelloID{helloChrome, "96", nil} - HelloChrome_100 = ClientHelloID{helloChrome, "100", nil} - HelloChrome_102 = ClientHelloID{helloChrome, "102", nil} + HelloChrome_Auto = HelloChrome_102 + HelloChrome_58 = ClientHelloID{helloChrome, "58", nil} + HelloChrome_62 = ClientHelloID{helloChrome, "62", nil} + HelloChrome_70 = ClientHelloID{helloChrome, "70", nil} + HelloChrome_72 = ClientHelloID{helloChrome, "72", nil} + HelloChrome_83 = ClientHelloID{helloChrome, "83", nil} + HelloChrome_87 = ClientHelloID{helloChrome, "87", nil} + HelloChrome_96 = ClientHelloID{helloChrome, "96", nil} + HelloChrome_100 = ClientHelloID{helloChrome, "100", nil} + HelloChrome_102 = ClientHelloID{helloChrome, "102", nil} + HelloChrome_106_Shuffle = ClientHelloID{helloChrome, "106", nil} // beta: shuffler enabled starting from 106 HelloIOS_Auto = HelloIOS_14 HelloIOS_11_1 = ClientHelloID{helloIOS, "111", nil} // legacy "111" means 11.1 diff --git a/u_parrots.go b/u_parrots.go index 820d71d5..7fab0212 100644 --- a/u_parrots.go +++ b/u_parrots.go @@ -10,6 +10,7 @@ import ( "errors" "fmt" "io" + "math/rand" "sort" "strconv" ) @@ -507,6 +508,14 @@ func utlsIdToSpec(id ClientHelloID) (ClientHelloSpec, error) { &UtlsPaddingExtension{GetPaddingLen: BoringPaddingStyle}, }, }, nil + case HelloChrome_106_Shuffle: + chs, err := utlsIdToSpec(HelloChrome_102) + if err != nil { + return chs, err + } + + // Chrome 107 started shuffling the order of extensions + return shuffleExtensions(chs) case HelloFirefox_55, HelloFirefox_56: return ClientHelloSpec{ TLSVersMax: VersionTLS12, @@ -1840,6 +1849,61 @@ func utlsIdToSpec(id ClientHelloID) (ClientHelloSpec, error) { } } +func shuffleExtensions(chs ClientHelloSpec) (ClientHelloSpec, error) { + // Shuffle extensions to avoid fingerprinting -- introduced in Chrome 106 + // GREASE, padding will remain in place (if present) + + // Find indexes of GREASE and padding extensions + var greaseIdx []int + var paddingIdx []int + var otherExtensions []TLSExtension + + for i, ext := range chs.Extensions { + switch ext.(type) { + case *UtlsGREASEExtension: + greaseIdx = append(greaseIdx, i) + case *UtlsPaddingExtension: + paddingIdx = append(paddingIdx, i) + default: + otherExtensions = append(otherExtensions, ext) + } + } + + // Shuffle other extensions + rand.Shuffle(len(otherExtensions), func(i, j int) { + otherExtensions[i], otherExtensions[j] = otherExtensions[j], otherExtensions[i] + }) + + // Rebuild extensions slice + otherExtIdx := 0 +SHUF_EXTENSIONS: + for i := 0; i < len(chs.Extensions); i++ { + // if current index is in greaseIdx or paddingIdx, add GREASE or padding extension + for _, idx := range greaseIdx { + if i == idx { + chs.Extensions[i] = &UtlsGREASEExtension{} + continue SHUF_EXTENSIONS + } + } + for _, idx := range paddingIdx { + if i == idx { + chs.Extensions[i] = &UtlsPaddingExtension{ + GetPaddingLen: BoringPaddingStyle, + } + break SHUF_EXTENSIONS + } + } + + // otherwise add other extension + chs.Extensions[i] = otherExtensions[otherExtIdx] + otherExtIdx++ + } + if otherExtIdx != len(otherExtensions) { + return ClientHelloSpec{}, errors.New("shuffleExtensions: otherExtIdx != len(otherExtensions)") + } + return chs, nil +} + func (uconn *UConn) applyPresetByID(id ClientHelloID) (err error) { var spec ClientHelloSpec uconn.ClientHelloID = id @@ -1980,7 +2044,7 @@ func (uconn *UConn) ApplyPreset(p *ClientHelloSpec) error { ecdheParams, err := generateECDHEParameters(uconn.config.rand(), curveID) if err != nil { return fmt.Errorf("unsupported Curve in KeyShareExtension: %v."+ - "To mimic it, fill the Data(key) field manually.", curveID) + "To mimic it, fill the Data(key) field manually", curveID) } ext.KeyShares[i].Data = ecdheParams.PublicKey() if !preferredCurveIsSet {