From 45e7f1de14507584cb68061d5f02f4ddae74a675 Mon Sep 17 00:00:00 2001 From: Gaukas Wang Date: Sun, 13 Aug 2023 22:25:07 -0600 Subject: [PATCH] new: more parrots and safety update (#227) * new: PQ and other parrots Add new preset parrots: - HelloChrome_114_Padding_PSK_Shuf - HelloChrome_115_PQ - HelloChrome_115_PQ_PSK * new: ShuffleChromeTLSExtensions Implement a new function `ShuffleChromeTLSExtensions(exts []TLSExtension) []TLSExtension`. * update: include psk parameter for parrot-related functions Update following functions' prototype to accept an optional pskExtension (of type *FakePreSharedKeyExtension): - `UClient(conn net.Conn, config *Config, clientHelloID ClientHelloID)` => `UClient(conn net.Conn, config *Config, clientHelloID ClientHelloID, pskExtension ...*FakePreSharedKeyExtension)` - `UTLSIdToSpec(id ClientHelloID)` => `UTLSIdToSpec(id ClientHelloID, pskExtension ...*FakePreSharedKeyExtension)` * new: pre-defined error from UTLSIdToSpec Update UTLSIdToSpec to return more comprehensive errors by pre-defining them, allowing easier error comparing/unwrapping. --- u_common.go | 14 +- u_conn.go | 5 +- u_parrots.go | 469 ++++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 425 insertions(+), 63 deletions(-) diff --git a/u_common.go b/u_common.go index 776272d0..1b30df42 100644 --- a/u_common.go +++ b/u_common.go @@ -582,9 +582,17 @@ var ( HelloChrome_102 = ClientHelloID{helloChrome, "102", nil, nil} HelloChrome_106_Shuffle = ClientHelloID{helloChrome, "106", nil, nil} // beta: shuffler enabled starting from 106 - // Chrome with PSK: Chrome start sending this ClientHello after doing TLS 1.3 handshake with the same server. - HelloChrome_100_PSK = ClientHelloID{helloChrome, "100_PSK", nil, nil} // beta: PSK extension added. uTLS doesn't fully support PSK. Use at your own risk. - HelloChrome_112_PSK_Shuf = ClientHelloID{helloChrome, "112_PSK", nil, nil} // beta: PSK extension added. uTLS doesn't fully support PSK. Use at your own risk. + // Chrome w/ PSK: Chrome start sending this ClientHello after doing TLS 1.3 handshake with the same server. + // Beta: PSK extension added. However, uTLS doesn't ship with full PSK support. + // Use at your own discretion. + HelloChrome_100_PSK = ClientHelloID{helloChrome, "100_PSK", nil, nil} + HelloChrome_112_PSK_Shuf = ClientHelloID{helloChrome, "112_PSK", nil, nil} + HelloChrome_114_Padding_PSK_Shuf = ClientHelloID{helloChrome, "114_PSK", nil, nil} + + // Chrome w/ Post-Quantum Key Agreement + // Beta: PQ extension added. However, uTLS doesn't ship with full PQ support. Use at your own discretion. + HelloChrome_115_PQ = ClientHelloID{helloChrome, "115_PQ", nil, nil} + HelloChrome_115_PQ_PSK = ClientHelloID{helloChrome, "115_PQ_PSK", nil, nil} HelloIOS_Auto = HelloIOS_14 HelloIOS_11_1 = ClientHelloID{helloIOS, "111", nil, nil} // legacy "111" means 11.1 diff --git a/u_conn.go b/u_conn.go index d3348a10..cbb4fa41 100644 --- a/u_conn.go +++ b/u_conn.go @@ -24,6 +24,7 @@ type UConn struct { Extensions []TLSExtension ClientHelloID ClientHelloID + pskExtension []*FakePreSharedKeyExtension ClientHelloBuilt bool HandshakeState PubClientHandshakeState @@ -43,13 +44,13 @@ type UConn struct { // UClient returns a new uTLS client, with behavior depending on clientHelloID. // Config CAN be nil, but make sure to eventually specify ServerName. -func UClient(conn net.Conn, config *Config, clientHelloID ClientHelloID) *UConn { +func UClient(conn net.Conn, config *Config, clientHelloID ClientHelloID, pskExtension ...*FakePreSharedKeyExtension) *UConn { if config == nil { config = &Config{} } tlsConn := Conn{conn: conn, config: config, isClient: true} handshakeState := PubClientHandshakeState{C: &tlsConn, Hello: &PubClientHelloMsg{}} - uconn := UConn{Conn: &tlsConn, ClientHelloID: clientHelloID, HandshakeState: handshakeState} + uconn := UConn{Conn: &tlsConn, ClientHelloID: clientHelloID, pskExtension: pskExtension, HandshakeState: handshakeState} uconn.HandshakeState.uconn = &uconn uconn.handshakeFn = uconn.clientHandshake return &uconn diff --git a/u_parrots.go b/u_parrots.go index a696259c..070e0059 100644 --- a/u_parrots.go +++ b/u_parrots.go @@ -16,11 +16,24 @@ import ( "strconv" ) +var ErrUnknownClientHelloID = errors.New("tls: unknown ClientHelloID") +var ErrNotPSKClientHelloID = errors.New("tls: ClientHello does not contain pre_shared_key extension") +var ErrPSKExtensionExpected = errors.New("tls: pre_shared_key extension expected when fetching preset ClientHelloSpec") + // UTLSIdToSpec converts a ClientHelloID to a corresponding ClientHelloSpec. // // Exported internal function utlsIdToSpec per request. -func UTLSIdToSpec(id ClientHelloID) (ClientHelloSpec, error) { - return utlsIdToSpec(id) +func UTLSIdToSpec(id ClientHelloID, pskExtension ...*FakePreSharedKeyExtension) (ClientHelloSpec, error) { + if len(pskExtension) > 1 { + return ClientHelloSpec{}, errors.New("tls: at most one FakePreSharedKeyExtensions is allowed") + } + + chs, err := utlsIdToSpec(id) + if err != nil && errors.Is(err, ErrUnknownClientHelloID) { + chs, err = utlsIdToSpecWithPSK(id, pskExtension...) + } + + return chs, err } func utlsIdToSpec(id ClientHelloID) (ClientHelloSpec, error) { @@ -509,7 +522,7 @@ func utlsIdToSpec(id ClientHelloID) (ClientHelloSpec, error) { &UtlsPaddingExtension{GetPaddingLen: BoringPaddingStyle}, }, }, nil - case HelloChrome_100_PSK: + case HelloChrome_106_Shuffle: return ClientHelloSpec{ CipherSuites: []uint16{ GREASE_PLACEHOLDER, @@ -532,7 +545,7 @@ func utlsIdToSpec(id ClientHelloID) (ClientHelloSpec, error) { CompressionMethods: []byte{ 0x00, // compressionNone }, - Extensions: []TLSExtension{ + Extensions: ShuffleChromeTLSExtensions([]TLSExtension{ &UtlsGREASEExtension{}, &SNIExtension{}, &ExtendedMasterSecretExtension{}, @@ -577,27 +590,83 @@ func utlsIdToSpec(id ClientHelloID) (ClientHelloSpec, error) { }}, &ApplicationSettingsExtension{SupportedProtocols: []string{"h2"}}, &UtlsGREASEExtension{}, - &FakePreSharedKeyExtension{}, + &UtlsPaddingExtension{GetPaddingLen: BoringPaddingStyle}, + }), + }, nil + // Chrome w/ Post-Quantum Key Agreement + case HelloChrome_115_PQ: + return ClientHelloSpec{ + CipherSuites: []uint16{ + GREASE_PLACEHOLDER, + TLS_AES_128_GCM_SHA256, + TLS_AES_256_GCM_SHA384, + TLS_CHACHA20_POLY1305_SHA256, + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, + TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + TLS_RSA_WITH_AES_128_GCM_SHA256, + TLS_RSA_WITH_AES_256_GCM_SHA384, + TLS_RSA_WITH_AES_128_CBC_SHA, + TLS_RSA_WITH_AES_256_CBC_SHA, + }, + CompressionMethods: []byte{ + 0x00, // compressionNone }, + Extensions: ShuffleChromeTLSExtensions([]TLSExtension{ + &UtlsGREASEExtension{}, + &SNIExtension{}, + &ExtendedMasterSecretExtension{}, + &RenegotiationInfoExtension{Renegotiation: RenegotiateOnceAsClient}, + &SupportedCurvesExtension{[]CurveID{ + GREASE_PLACEHOLDER, + X25519Kyber768Draft00, + X25519, + CurveP256, + CurveP384, + }}, + &SupportedPointsExtension{SupportedPoints: []byte{ + 0x00, // pointFormatUncompressed + }}, + &SessionTicketExtension{}, + &ALPNExtension{AlpnProtocols: []string{"h2", "http/1.1"}}, + &StatusRequestExtension{}, + &SignatureAlgorithmsExtension{SupportedSignatureAlgorithms: []SignatureScheme{ + ECDSAWithP256AndSHA256, + PSSWithSHA256, + PKCS1WithSHA256, + ECDSAWithP384AndSHA384, + PSSWithSHA384, + PKCS1WithSHA384, + PSSWithSHA512, + PKCS1WithSHA512, + }}, + &SCTExtension{}, + &KeyShareExtension{[]KeyShare{ + {Group: CurveID(GREASE_PLACEHOLDER), Data: []byte{0}}, + {Group: X25519Kyber768Draft00}, + {Group: X25519}, + }}, + &PSKKeyExchangeModesExtension{[]uint8{ + PskModeDHE, + }}, + &SupportedVersionsExtension{[]uint16{ + GREASE_PLACEHOLDER, + VersionTLS13, + VersionTLS12, + }}, + &UtlsCompressCertExtension{[]CertCompressionAlgo{ + CertCompressionBrotli, + }}, + &ApplicationSettingsExtension{SupportedProtocols: []string{"h2"}}, + &UtlsGREASEExtension{}, + &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 - shuffleExtensions(&chs) - return chs, err - case HelloChrome_112_PSK_Shuf: - chs, err := utlsIdToSpec(HelloChrome_100_PSK) - if err != nil { - return chs, err - } - - // Chrome 112 started shuffling the order of extensions - shuffleExtensions(&chs) - return chs, err case HelloFirefox_55, HelloFirefox_56: return ClientHelloSpec{ TLSVersMax: VersionTLS12, @@ -1931,53 +2000,337 @@ func utlsIdToSpec(id ClientHelloID) (ClientHelloSpec, error) { // Use empty values as they can be filled later by UConn.ApplyPreset or manually. return generateRandomizedSpec(&id, "", nil, nil) } - return ClientHelloSpec{}, errors.New("ClientHello ID " + id.Str() + " is unknown") + return ClientHelloSpec{}, fmt.Errorf("%w: %s", ErrUnknownClientHelloID, id.Str()) } } -func shuffleExtensions(chs *ClientHelloSpec) error { - // Shuffle extensions to avoid fingerprinting -- introduced in Chrome 106 - var err error = nil +func utlsIdToSpecWithPSK(id ClientHelloID, pskExtension ...*FakePreSharedKeyExtension) (ClientHelloSpec, error) { + switch id { + case HelloChrome_100_PSK, HelloChrome_112_PSK_Shuf, HelloChrome_114_Padding_PSK_Shuf, HelloChrome_115_PQ_PSK: + if len(pskExtension) == 0 || pskExtension[0] == nil { + return ClientHelloSpec{}, fmt.Errorf("%w: %s", ErrPSKExtensionExpected, id.Str()) + } + } - // unshufCheck checks: - // - if the exts[idx] is a GREASE extension, then it should not be shuffled - // - if the exts[idx] is a padding/pre_shared_key extension, then it should be the - // last extension in the list and should not be shuffled - var unshufCheck = func(idx int, exts []TLSExtension) (donotshuf bool, userErr error) { + switch id { + case HelloChrome_100_PSK: + return ClientHelloSpec{ + CipherSuites: []uint16{ + GREASE_PLACEHOLDER, + TLS_AES_128_GCM_SHA256, + TLS_AES_256_GCM_SHA384, + TLS_CHACHA20_POLY1305_SHA256, + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, + TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + TLS_RSA_WITH_AES_128_GCM_SHA256, + TLS_RSA_WITH_AES_256_GCM_SHA384, + TLS_RSA_WITH_AES_128_CBC_SHA, + TLS_RSA_WITH_AES_256_CBC_SHA, + }, + CompressionMethods: []byte{ + 0x00, // compressionNone + }, + Extensions: []TLSExtension{ + &UtlsGREASEExtension{}, + &SNIExtension{}, + &ExtendedMasterSecretExtension{}, + &RenegotiationInfoExtension{Renegotiation: RenegotiateOnceAsClient}, + &SupportedCurvesExtension{[]CurveID{ + GREASE_PLACEHOLDER, + X25519, + CurveP256, + CurveP384, + }}, + &SupportedPointsExtension{SupportedPoints: []byte{ + 0x00, // pointFormatUncompressed + }}, + &SessionTicketExtension{}, + &ALPNExtension{AlpnProtocols: []string{"h2", "http/1.1"}}, + &StatusRequestExtension{}, + &SignatureAlgorithmsExtension{SupportedSignatureAlgorithms: []SignatureScheme{ + ECDSAWithP256AndSHA256, + PSSWithSHA256, + PKCS1WithSHA256, + ECDSAWithP384AndSHA384, + PSSWithSHA384, + PKCS1WithSHA384, + PSSWithSHA512, + PKCS1WithSHA512, + }}, + &SCTExtension{}, + &KeyShareExtension{[]KeyShare{ + {Group: CurveID(GREASE_PLACEHOLDER), Data: []byte{0}}, + {Group: X25519}, + }}, + &PSKKeyExchangeModesExtension{[]uint8{ + PskModeDHE, + }}, + &SupportedVersionsExtension{[]uint16{ + GREASE_PLACEHOLDER, + VersionTLS13, + VersionTLS12, + }}, + &UtlsCompressCertExtension{[]CertCompressionAlgo{ + CertCompressionBrotli, + }}, + &ApplicationSettingsExtension{SupportedProtocols: []string{"h2"}}, + &UtlsGREASEExtension{}, + pskExtension[0], + }, + }, nil + case HelloChrome_112_PSK_Shuf: + return ClientHelloSpec{ + CipherSuites: []uint16{ + GREASE_PLACEHOLDER, + TLS_AES_128_GCM_SHA256, + TLS_AES_256_GCM_SHA384, + TLS_CHACHA20_POLY1305_SHA256, + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, + TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + TLS_RSA_WITH_AES_128_GCM_SHA256, + TLS_RSA_WITH_AES_256_GCM_SHA384, + TLS_RSA_WITH_AES_128_CBC_SHA, + TLS_RSA_WITH_AES_256_CBC_SHA, + }, + CompressionMethods: []byte{ + 0x00, // compressionNone + }, + Extensions: ShuffleChromeTLSExtensions([]TLSExtension{ + &UtlsGREASEExtension{}, + &SNIExtension{}, + &ExtendedMasterSecretExtension{}, + &RenegotiationInfoExtension{Renegotiation: RenegotiateOnceAsClient}, + &SupportedCurvesExtension{[]CurveID{ + GREASE_PLACEHOLDER, + X25519, + CurveP256, + CurveP384, + }}, + &SupportedPointsExtension{SupportedPoints: []byte{ + 0x00, // pointFormatUncompressed + }}, + &SessionTicketExtension{}, + &ALPNExtension{AlpnProtocols: []string{"h2", "http/1.1"}}, + &StatusRequestExtension{}, + &SignatureAlgorithmsExtension{SupportedSignatureAlgorithms: []SignatureScheme{ + ECDSAWithP256AndSHA256, + PSSWithSHA256, + PKCS1WithSHA256, + ECDSAWithP384AndSHA384, + PSSWithSHA384, + PKCS1WithSHA384, + PSSWithSHA512, + PKCS1WithSHA512, + }}, + &SCTExtension{}, + &KeyShareExtension{[]KeyShare{ + {Group: CurveID(GREASE_PLACEHOLDER), Data: []byte{0}}, + {Group: X25519}, + }}, + &PSKKeyExchangeModesExtension{[]uint8{ + PskModeDHE, + }}, + &SupportedVersionsExtension{[]uint16{ + GREASE_PLACEHOLDER, + VersionTLS13, + VersionTLS12, + }}, + &UtlsCompressCertExtension{[]CertCompressionAlgo{ + CertCompressionBrotli, + }}, + &ApplicationSettingsExtension{SupportedProtocols: []string{"h2"}}, + &UtlsGREASEExtension{}, + pskExtension[0], + }), + }, nil + case HelloChrome_114_Padding_PSK_Shuf: + return ClientHelloSpec{ + CipherSuites: []uint16{ + GREASE_PLACEHOLDER, + TLS_AES_128_GCM_SHA256, + TLS_AES_256_GCM_SHA384, + TLS_CHACHA20_POLY1305_SHA256, + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, + TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + TLS_RSA_WITH_AES_128_GCM_SHA256, + TLS_RSA_WITH_AES_256_GCM_SHA384, + TLS_RSA_WITH_AES_128_CBC_SHA, + TLS_RSA_WITH_AES_256_CBC_SHA, + }, + CompressionMethods: []byte{ + 0x00, // compressionNone + }, + Extensions: ShuffleChromeTLSExtensions([]TLSExtension{ + &UtlsGREASEExtension{}, + &SNIExtension{}, + &ExtendedMasterSecretExtension{}, + &RenegotiationInfoExtension{Renegotiation: RenegotiateOnceAsClient}, + &SupportedCurvesExtension{[]CurveID{ + GREASE_PLACEHOLDER, + X25519, + CurveP256, + CurveP384, + }}, + &SupportedPointsExtension{SupportedPoints: []byte{ + 0x00, // pointFormatUncompressed + }}, + &SessionTicketExtension{}, + &ALPNExtension{AlpnProtocols: []string{"h2", "http/1.1"}}, + &StatusRequestExtension{}, + &SignatureAlgorithmsExtension{SupportedSignatureAlgorithms: []SignatureScheme{ + ECDSAWithP256AndSHA256, + PSSWithSHA256, + PKCS1WithSHA256, + ECDSAWithP384AndSHA384, + PSSWithSHA384, + PKCS1WithSHA384, + PSSWithSHA512, + PKCS1WithSHA512, + }}, + &SCTExtension{}, + &KeyShareExtension{[]KeyShare{ + {Group: CurveID(GREASE_PLACEHOLDER), Data: []byte{0}}, + {Group: X25519}, + }}, + &PSKKeyExchangeModesExtension{[]uint8{ + PskModeDHE, + }}, + &SupportedVersionsExtension{[]uint16{ + GREASE_PLACEHOLDER, + VersionTLS13, + VersionTLS12, + }}, + &UtlsCompressCertExtension{[]CertCompressionAlgo{ + CertCompressionBrotli, + }}, + &ApplicationSettingsExtension{SupportedProtocols: []string{"h2"}}, + &UtlsGREASEExtension{}, + &UtlsPaddingExtension{GetPaddingLen: BoringPaddingStyle}, + pskExtension[0], + }), + }, nil + // Chrome w/ Post-Quantum Key Agreement + case HelloChrome_115_PQ_PSK: + return ClientHelloSpec{ + CipherSuites: []uint16{ + GREASE_PLACEHOLDER, + TLS_AES_128_GCM_SHA256, + TLS_AES_256_GCM_SHA384, + TLS_CHACHA20_POLY1305_SHA256, + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, + TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + TLS_RSA_WITH_AES_128_GCM_SHA256, + TLS_RSA_WITH_AES_256_GCM_SHA384, + TLS_RSA_WITH_AES_128_CBC_SHA, + TLS_RSA_WITH_AES_256_CBC_SHA, + }, + CompressionMethods: []byte{ + 0x00, // compressionNone + }, + Extensions: ShuffleChromeTLSExtensions([]TLSExtension{ + &UtlsGREASEExtension{}, + &SNIExtension{}, + &ExtendedMasterSecretExtension{}, + &RenegotiationInfoExtension{Renegotiation: RenegotiateOnceAsClient}, + &SupportedCurvesExtension{[]CurveID{ + GREASE_PLACEHOLDER, + X25519Kyber768Draft00, + X25519, + CurveP256, + CurveP384, + }}, + &SupportedPointsExtension{SupportedPoints: []byte{ + 0x00, // pointFormatUncompressed + }}, + &SessionTicketExtension{}, + &ALPNExtension{AlpnProtocols: []string{"h2", "http/1.1"}}, + &StatusRequestExtension{}, + &SignatureAlgorithmsExtension{SupportedSignatureAlgorithms: []SignatureScheme{ + ECDSAWithP256AndSHA256, + PSSWithSHA256, + PKCS1WithSHA256, + ECDSAWithP384AndSHA384, + PSSWithSHA384, + PKCS1WithSHA384, + PSSWithSHA512, + PKCS1WithSHA512, + }}, + &SCTExtension{}, + &KeyShareExtension{[]KeyShare{ + {Group: CurveID(GREASE_PLACEHOLDER), Data: []byte{0}}, + {Group: X25519Kyber768Draft00}, + {Group: X25519}, + }}, + &PSKKeyExchangeModesExtension{[]uint8{ + PskModeDHE, + }}, + &SupportedVersionsExtension{[]uint16{ + GREASE_PLACEHOLDER, + VersionTLS13, + VersionTLS12, + }}, + &UtlsCompressCertExtension{[]CertCompressionAlgo{ + CertCompressionBrotli, + }}, + &ApplicationSettingsExtension{SupportedProtocols: []string{"h2"}}, + &UtlsGREASEExtension{}, + pskExtension[0], + }), + }, nil + } + + return ClientHelloSpec{}, fmt.Errorf("%w: %s", ErrUnknownClientHelloID, id.Str()) +} + +// ShuffleChromeTLSExtensions shuffles the extensions in the ClientHelloSpec to avoid ossification. +// It shuffles every extension except GREASE, padding and pre_shared_key extensions. +// +// This feature was first introduced by Chrome 106. +func ShuffleChromeTLSExtensions(exts []TLSExtension) []TLSExtension { + // unshufCheck checks if the exts[idx] is a GREASE/padding/pre_shared_key extension, + // and returns true on success. For these extensions are considered positionally invariant. + var skipShuf = func(idx int, exts []TLSExtension) bool { switch exts[idx].(type) { - case *UtlsGREASEExtension: - donotshuf = true - case *UtlsPaddingExtension, *FakePreSharedKeyExtension: - donotshuf = true - if idx != len(chs.Extensions)-1 { - userErr = errors.New("UtlsPaddingExtension or FakePreSharedKeyExtension must be the last extension") - } + case *UtlsGREASEExtension, *UtlsPaddingExtension, *FakePreSharedKeyExtension: + return true default: - donotshuf = false + return false } - return } // Shuffle other extensions - rand.Shuffle(len(chs.Extensions), func(i, j int) { - if unshuf, shuferr := unshufCheck(i, chs.Extensions); unshuf { - if shuferr != nil { - err = shuferr - } - return + rand.Shuffle(len(exts), func(i, j int) { + if skipShuf(i, exts) || skipShuf(j, exts) { + return // do not shuffle some of the extensions } - - if unshuf, shuferr := unshufCheck(j, chs.Extensions); unshuf { - if shuferr != nil { - err = shuferr - } - return - } - - chs.Extensions[i], chs.Extensions[j] = chs.Extensions[j], chs.Extensions[i] + exts[i], exts[j] = exts[j], exts[i] }) - return err + return exts } func (uconn *UConn) applyPresetByID(id ClientHelloID) (err error) { @@ -1994,7 +2347,7 @@ func (uconn *UConn) applyPresetByID(id ClientHelloID) (err error) { return nil default: - spec, err = utlsIdToSpec(id) + spec, err = UTLSIdToSpec(id, uconn.pskExtension...) if err != nil { return err }