utls/u_common.go
Gaukas Wang 8094658e76
new: Support TLS-PSK (TLS 1.3) (#231)
* uTLS: X25519Kyber768Draft00 hybrid post-quantum key agreement by cloudflare/go (#222)

* crypto/tls: Add hybrid post-quantum key agreement  (#13)

* import: client-side KEM from cloudflare/go

* import: server-side KEM from cloudflare/go

* fix: modify test to get rid of CFEvents.

Note: uTLS does not promise any server-side functionality, and this change is made to be able to conduct unit tests which requires both side to be able to handle KEM Curves.

Co-authored-by: Christopher Wood <caw@heapingbits.net>
Co-Authored-By: Bas Westerbaan <bas@westerbaan.name>

----

Based on:

* crypto/tls: Add hybrid post-quantum key agreement 

Adds X25519Kyber512Draft00, X25519Kyber768Draft00, and
P256Kyber768Draft00 hybrid post-quantum key agreements with temporary
group identifiers.

The hybrid post-quantum key exchanges uses plain X{25519,448} instead
of HPKE, which we assume will be more likely to be adopted. The order
is chosen to match CECPQ2.

Not enabled by default.

Adds CFEvents to detect `HelloRetryRequest`s and to signal which
key agreement was used.

Co-authored-by: Christopher Wood <caw@heapingbits.net>

 [bas, 1.20.1: also adds P256Kyber768Draft00]
 [pwu, 1.20.4: updated circl to v1.3.3, moved code to cfevent.go]

* crypto: add support for CIRCL signature schemes

* only partially port the commit from cloudflare/go. We would stick to the official x509 at the cost of incompatibility.

Co-Authored-By: Bas Westerbaan <bas@westerbaan.name>
Co-Authored-By: Christopher Patton <3453007+cjpatton@users.noreply.github.com>
Co-Authored-By: Peter Wu <peter@lekensteyn.nl>

* crypto/tls: add new X25519Kyber768Draft00 code point

Ported from cloudflare/go to support the upcoming new post-quantum keyshare.

----

* Point tls.X25519Kyber768Draft00 to the new 0x6399 identifier while the
  old 0xfe31 identifier is available as tls.X25519Kyber768Draft00Old.
* Make sure that the kem.PrivateKey can always be mapped to the CurveID
  that was linked to it. This is needed since we now have two ID
  aliasing to the same scheme, and clients need to be able to detect
  whether the key share presented by the server actually matches the key
  share that the client originally sent.
* Update tests, add the new identifier and remove unnecessary code.

Link: https://mailarchive.ietf.org/arch/msg/tls/HAWpNpgptl--UZNSYuvsjB-Pc2k/
Link: https://datatracker.ietf.org/doc/draft-tls-westerbaan-xyber768d00/02/
Co-Authored-By: Peter Wu <peter@lekensteyn.nl>
Co-Authored-By: Bas Westerbaan <bas@westerbaan.name>

---------

Co-authored-by: Bas Westerbaan <bas@westerbaan.name>
Co-authored-by: Christopher Patton <3453007+cjpatton@users.noreply.github.com>
Co-authored-by: Peter Wu <peter@lekensteyn.nl>

* new: enable PQ parrots (#225)

* Redesign KeySharesEcdheParameters into KeySharesParameters which supports multiple types of keys.

* Optimize program logic to prevent using unwanted keys

* 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.

* new: UtlsPreSharedKeyExtension

In `u_pre_shared_key.go`, create `PreSharedKeyExtension` as an interface, with 3 implementations:
- `UtlsPreSharedKeyExtension` implements full support for `pre_shared_key` less resuming after seeing HRR.
- `FakePreSharedKeyExtension` uses CipherSuiteID, SessionSecret and Identities to calculate the corresponding binders and send them, without setting the internal states. Therefore if the server accepts the PSK and tries to resume, the connection fails.
- `HardcodedPreSharedKeyExtension` allows user to hardcode Identities and Binders to be sent in the extension without setting the internal states. Therefore if the server accepts the PSK and tries to resume, the connection fails.

TODO: Only one of FakePreSharedKeyExtension and HardcodedPreSharedKeyExtension should be kept, the other one should be just removed. We still need to learn more of the safety of hardcoding both Identities and Binders without recalculating the latter.

* update: PSK minor changes and example

* Updates PSK implementations for more comprehensible interfaces when applying preset/json/raw fingerprints.
* Revert FakePreSharedKeyExtension to the old implementation. Add binder size checking.
* Implement TLS-PSK example

New bug: setting `tls.Config.ClientSessionCache` will cause PSK to fail. Currently users must set only `tls.UtlsPreSharedKeyExtension.ClientSessionCacheOverride`.

* fix: PSK failing if config session cache set

* Fix a bug causing PSK to fail if Config.ClientSessionCache is set.
* Removed `ClientSessionCacheOverride` from `UtlsPreSharedKeyExtension`. Set the `ClientSessionCache` in `Config`!

Co-Authored-By: zeeker999 <13848632+zeeker999@users.noreply.github.com>

* Optimize tls resumption (#235)

* feat: bug fix and refactor

* feat: improve example docs: add detailed explanation about the design feat: add assertion on uApplyPatch

* fix: address comments
feat: add option `OmitEmptyPsk` and throw error on empty psk by default
feat: revert changes to public interfaces

* fix: weird residue caused by merging conflict

* fix: remove merge conflict residue code

---------

Co-authored-by: Bas Westerbaan <bas@westerbaan.name>
Co-authored-by: Christopher Patton <3453007+cjpatton@users.noreply.github.com>
Co-authored-by: Peter Wu <peter@lekensteyn.nl>
Co-authored-by: zeeker999 <13848632+zeeker999@users.noreply.github.com>
Co-authored-by: 3andne <52860475+3andne@users.noreply.github.com>
2023-08-27 12:48:31 -06:00

786 lines
27 KiB
Go

// Copyright 2017 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package tls
import (
"crypto/hmac"
"crypto/sha512"
"encoding/json"
"errors"
"fmt"
"hash"
"log"
"github.com/refraction-networking/utls/internal/helper"
"golang.org/x/crypto/cryptobyte"
)
// Naming convention:
// Unsupported things are prefixed with "Fake"
// Things, supported by utls, but not crypto/tls' are prefixed with "utls"
// Supported things, that have changed their ID are prefixed with "Old"
// Supported but disabled things are prefixed with "Disabled". We will _enable_ them.
// TLS handshake message types.
const (
utlsTypeEncryptedExtensions uint8 = 8 // implemention incomplete by crypto/tls
// https://datatracker.ietf.org/doc/html/rfc8879#section-7.2
utlsTypeCompressedCertificate uint8 = 25
)
// TLS
const (
extensionNextProtoNeg uint16 = 13172 // not IANA assigned. Removed by crypto/tls since Nov 2019
utlsExtensionPadding uint16 = 21
utlsExtensionCompressCertificate uint16 = 27 // https://datatracker.ietf.org/doc/html/rfc8879#section-7.1
utlsExtensionApplicationSettings uint16 = 17513 // not IANA assigned
utlsFakeExtensionCustom uint16 = 1234 // not IANA assigned, for ALPS
// extensions with 'fake' prefix break connection, if server echoes them back
fakeExtensionEncryptThenMAC uint16 = 22
fakeExtensionTokenBinding uint16 = 24
fakeExtensionDelegatedCredentials uint16 = 34
fakeExtensionPreSharedKey uint16 = 41
fakeOldExtensionChannelID uint16 = 30031 // not IANA assigned
fakeExtensionChannelID uint16 = 30032 // not IANA assigned
)
const (
OLD_TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = uint16(0xcc13)
OLD_TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 = uint16(0xcc14)
DISABLED_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 = uint16(0xc024)
DISABLED_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 = uint16(0xc028)
DISABLED_TLS_RSA_WITH_AES_256_CBC_SHA256 = uint16(0x003d)
FAKE_OLD_TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = uint16(0xcc15) // we can try to craft these ciphersuites
FAKE_TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 = uint16(0x009e) // from existing pieces, if needed
FAKE_TLS_DHE_RSA_WITH_AES_128_CBC_SHA = uint16(0x0033)
FAKE_TLS_DHE_RSA_WITH_AES_256_CBC_SHA = uint16(0x0039)
FAKE_TLS_RSA_WITH_RC4_128_MD5 = uint16(0x0004)
FAKE_TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 = uint16(0x009f)
FAKE_TLS_DHE_DSS_WITH_AES_128_CBC_SHA = uint16(0x0032)
FAKE_TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 = uint16(0x006b)
FAKE_TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 = uint16(0x0067)
FAKE_TLS_EMPTY_RENEGOTIATION_INFO_SCSV = uint16(0x00ff)
// https://docs.microsoft.com/en-us/dotnet/api/system.net.security.tlsciphersuite?view=netcore-3.1
FAKE_TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA = uint16(0xc008)
)
const (
CurveSECP256R1 CurveID = 0x0017
CurveSECP384R1 CurveID = 0x0018
CurveSECP521R1 CurveID = 0x0019
CurveX25519 CurveID = 0x001d
FakeCurveFFDHE2048 CurveID = 0x0100
FakeCurveFFDHE3072 CurveID = 0x0101
FakeCurveFFDHE4096 CurveID = 0x0102
FakeCurveFFDHE6144 CurveID = 0x0103
FakeCurveFFDHE8192 CurveID = 0x0104
)
// Other things
const (
fakeRecordSizeLimit uint16 = 0x001c
)
// newest signatures
var (
FakePKCS1WithSHA224 SignatureScheme = 0x0301
FakeECDSAWithSHA224 SignatureScheme = 0x0303
FakeSHA1WithDSA SignatureScheme = 0x0202
FakeSHA256WithDSA SignatureScheme = 0x0402
// fakeEd25519 = SignatureAndHash{0x08, 0x07}
// fakeEd448 = SignatureAndHash{0x08, 0x08}
)
// fake curves(groups)
var (
FakeFFDHE2048 = uint16(0x0100)
FakeFFDHE3072 = uint16(0x0101)
)
// https://tools.ietf.org/html/draft-ietf-tls-certificate-compression-04
type CertCompressionAlgo uint16
const (
CertCompressionZlib CertCompressionAlgo = 0x0001
CertCompressionBrotli CertCompressionAlgo = 0x0002
CertCompressionZstd CertCompressionAlgo = 0x0003
)
const (
PskModePlain uint8 = pskModePlain
PskModeDHE uint8 = pskModeDHE
)
type ClientHelloID struct {
Client string
// Version specifies version of a mimicked clients (e.g. browsers).
// Not used in randomized, custom handshake, and default Go.
Version string
// Seed is only used for randomized fingerprints to seed PRNG.
// Must not be modified once set.
Seed *PRNGSeed
// Weights are only used for randomized fingerprints in func
// generateRandomizedSpec(). Must not be modified once set.
Weights *Weights
}
func (p *ClientHelloID) Str() string {
return fmt.Sprintf("%s-%s", p.Client, p.Version)
}
func (p *ClientHelloID) IsSet() bool {
return (p.Client == "") && (p.Version == "")
}
const (
// clients
helloGolang = "Golang"
helloRandomized = "Randomized"
helloRandomizedALPN = "Randomized-ALPN"
helloRandomizedNoALPN = "Randomized-NoALPN"
helloCustom = "Custom"
helloFirefox = "Firefox"
helloChrome = "Chrome"
helloIOS = "iOS"
helloAndroid = "Android"
helloEdge = "Edge"
helloSafari = "Safari"
hello360 = "360Browser"
helloQQ = "QQBrowser"
// versions
helloAutoVers = "0"
)
type ClientHelloSpec struct {
CipherSuites []uint16 // nil => default
CompressionMethods []uint8 // nil => no compression
Extensions []TLSExtension // nil => no extensions
TLSVersMin uint16 // [1.0-1.3] default: parse from .Extensions, if SupportedVersions ext is not present => 1.0
TLSVersMax uint16 // [1.2-1.3] default: parse from .Extensions, if SupportedVersions ext is not present => 1.2
// GreaseStyle: currently only random
// sessionID may or may not depend on ticket; nil => random
GetSessionID func(ticket []byte) [32]byte
// TLSFingerprintLink string // ?? link to tlsfingerprint.io for informational purposes
}
// ReadCipherSuites is a helper function to construct a list of cipher suites from
// a []byte into []uint16.
//
// example: []byte{0x13, 0x01, 0x13, 0x02, 0x13, 0x03} => []uint16{0x1301, 0x1302, 0x1303}
func (chs *ClientHelloSpec) ReadCipherSuites(b []byte) error {
cipherSuites := []uint16{}
s := cryptobyte.String(b)
for !s.Empty() {
var suite uint16
if !s.ReadUint16(&suite) {
return errors.New("unable to read ciphersuite")
}
cipherSuites = append(cipherSuites, unGREASEUint16(suite))
}
chs.CipherSuites = cipherSuites
return nil
}
// ReadCompressionMethods is a helper function to construct a list of compression
// methods from a []byte into []uint8.
func (chs *ClientHelloSpec) ReadCompressionMethods(compressionMethods []byte) error {
chs.CompressionMethods = compressionMethods
return nil
}
// ReadTLSExtensions is a helper function to construct a list of TLS extensions from
// a byte slice into []TLSExtension.
//
// If keepPSK is not set, the PSK extension will cause an error.
func (chs *ClientHelloSpec) ReadTLSExtensions(b []byte, allowBluntMimicry bool, realPSK bool) error {
extensions := cryptobyte.String(b)
for !extensions.Empty() {
var extension uint16
var extData cryptobyte.String
if !extensions.ReadUint16(&extension) {
return fmt.Errorf("unable to read extension ID")
}
if !extensions.ReadUint16LengthPrefixed(&extData) {
return fmt.Errorf("unable to read data for extension %x", extension)
}
ext := ExtensionFromID(extension)
extWriter, ok := ext.(TLSExtensionWriter)
if ext != nil && ok { // known extension and implements TLSExtensionWriter properly
switch extension {
case extensionPreSharedKey:
// PSK extension, need to see if we do real or fake PSK
if realPSK {
extWriter = &UtlsPreSharedKeyExtension{}
} else {
extWriter = &FakePreSharedKeyExtension{}
}
}
if extension == extensionSupportedVersions {
chs.TLSVersMin = 0
chs.TLSVersMax = 0
}
if _, err := extWriter.Write(extData); err != nil {
return err
}
chs.Extensions = append(chs.Extensions, extWriter)
} else {
if allowBluntMimicry {
chs.Extensions = append(chs.Extensions, &GenericExtension{extension, extData})
} else {
return fmt.Errorf("unsupported extension %d", extension)
}
}
}
return nil
}
func (chs *ClientHelloSpec) AlwaysAddPadding() {
alreadyHasPadding := false
for idx, ext := range chs.Extensions {
if _, ok := ext.(*UtlsPaddingExtension); ok {
alreadyHasPadding = true
break
}
if _, ok := ext.(PreSharedKeyExtension); ok {
alreadyHasPadding = true // PSK must be last, so we can't append padding after it
// instead we will insert padding before PSK
chs.Extensions = append(chs.Extensions[:idx], append([]TLSExtension{&UtlsPaddingExtension{GetPaddingLen: BoringPaddingStyle}}, chs.Extensions[idx:]...)...)
break
}
}
if !alreadyHasPadding {
chs.Extensions = append(chs.Extensions, &UtlsPaddingExtension{GetPaddingLen: BoringPaddingStyle})
}
}
// Import TLS ClientHello data from client.tlsfingerprint.io:8443
//
// data is a map of []byte with following keys:
// - cipher_suites: [10, 10, 19, 1, 19, 2, 19, 3, 192, 43, 192, 47, 192, 44, 192, 48, 204, 169, 204, 168, 192, 19, 192, 20, 0, 156, 0, 157, 0, 47, 0, 53]
// - compression_methods: [0] => null
// - extensions: [10, 10, 255, 1, 0, 45, 0, 35, 0, 16, 68, 105, 0, 11, 0, 43, 0, 18, 0, 13, 0, 0, 0, 10, 0, 27, 0, 5, 0, 51, 0, 23, 10, 10, 0, 21]
// - pt_fmts (ec_point_formats): [1, 0] => len: 1, content: 0x00
// - sig_algs (signature_algorithms): [0, 16, 4, 3, 8, 4, 4, 1, 5, 3, 8, 5, 5, 1, 8, 6, 6, 1] => len: 16, content: 0x0403, 0x0804, 0x0401, 0x0503, 0x0805, 0x0501, 0x0806, 0x0601
// - supported_versions: [10, 10, 3, 4, 3, 3] => 0x0a0a, 0x0304, 0x0303 (GREASE, TLS 1.3, TLS 1.2)
// - curves (named_groups, supported_groups): [0, 8, 10, 10, 0, 29, 0, 23, 0, 24] => len: 8, content: GREASE, 0x001d, 0x0017, 0x0018
// - alpn: [0, 12, 2, 104, 50, 8, 104, 116, 116, 112, 47, 49, 46, 49] => len: 12, content: h2, http/1.1
// - key_share: [10, 10, 0, 1, 0, 29, 0, 32] => {group: 0x0a0a, len:1}, {group: 0x001d, len:32}
// - psk_key_exchange_modes: [1] => psk_dhe_ke(0x01)
// - cert_compression_algs: [2, 0, 2] => brotli (0x0002)
// - record_size_limit: [0, 255] => 255
//
// TLSVersMin/TLSVersMax are set to 0 if supported_versions is present.
// To prevent conflict, they should be set manually if needed BEFORE calling this function.
func (chs *ClientHelloSpec) ImportTLSClientHello(data map[string][]byte) error {
var tlsExtensionTypes []uint16
var err error
if data["cipher_suites"] == nil {
return errors.New("cipher_suites is required")
}
chs.CipherSuites, err = helper.Uint8to16(data["cipher_suites"])
if err != nil {
return err
}
if data["compression_methods"] == nil {
return errors.New("compression_methods is required")
}
chs.CompressionMethods = data["compression_methods"]
if data["extensions"] == nil {
return errors.New("extensions is required")
}
tlsExtensionTypes, err = helper.Uint8to16(data["extensions"])
if err != nil {
return err
}
for _, extType := range tlsExtensionTypes {
extension := ExtensionFromID(extType)
extWriter, ok := extension.(TLSExtensionWriter)
if !ok {
return fmt.Errorf("unsupported extension %d", extType)
}
if extension == nil || !ok {
log.Printf("[Warning] Unsupported extension %d added as a &GenericExtension without Data", extType)
chs.Extensions = append(chs.Extensions, &GenericExtension{extType, []byte{}})
} else {
switch extType {
case extensionSupportedPoints:
if data["pt_fmts"] == nil {
return errors.New("pt_fmts is required")
}
_, err = extWriter.Write(data["pt_fmts"])
if err != nil {
return err
}
case extensionSignatureAlgorithms:
if data["sig_algs"] == nil {
return errors.New("sig_algs is required")
}
_, err = extWriter.Write(data["sig_algs"])
if err != nil {
return err
}
case extensionSupportedVersions:
chs.TLSVersMin = 0
chs.TLSVersMax = 0
if data["supported_versions"] == nil {
return errors.New("supported_versions is required")
}
// need to add uint8 length prefix
fixedData := make([]byte, len(data["supported_versions"])+1)
fixedData[0] = uint8(len(data["supported_versions"]) & 0xff)
copy(fixedData[1:], data["supported_versions"])
_, err = extWriter.Write(fixedData)
if err != nil {
return err
}
case extensionSupportedCurves:
if data["curves"] == nil {
return errors.New("curves is required")
}
_, err = extWriter.Write(data["curves"])
if err != nil {
return err
}
case extensionALPN:
if data["alpn"] == nil {
return errors.New("alpn is required")
}
_, err = extWriter.Write(data["alpn"])
if err != nil {
return err
}
case extensionKeyShare:
if data["key_share"] == nil {
return errors.New("key_share is required")
}
// need to add (zero) data per each key share, [10, 10, 0, 1] => [10, 10, 0, 1, 0]
fixedData := make([]byte, 0)
for i := 0; i < len(data["key_share"]); i += 4 {
fixedData = append(fixedData, data["key_share"][i:i+4]...)
for j := 0; j < int(data["key_share"][i+3]); j++ {
fixedData = append(fixedData, 0)
}
}
// add uint16 length prefix
fixedData = append([]byte{uint8(len(fixedData) >> 8), uint8(len(fixedData) & 0xff)}, fixedData...)
_, err = extWriter.Write(fixedData)
if err != nil {
return err
}
case extensionPSKModes:
if data["psk_key_exchange_modes"] == nil {
return errors.New("psk_key_exchange_modes is required")
}
// need to add uint8 length prefix
fixedData := make([]byte, len(data["psk_key_exchange_modes"])+1)
fixedData[0] = uint8(len(data["psk_key_exchange_modes"]) & 0xff)
copy(fixedData[1:], data["psk_key_exchange_modes"])
_, err = extWriter.Write(fixedData)
if err != nil {
return err
}
case utlsExtensionCompressCertificate:
if data["cert_compression_algs"] == nil {
return errors.New("cert_compression_algs is required")
}
// need to add uint8 length prefix
fixedData := make([]byte, len(data["cert_compression_algs"])+1)
fixedData[0] = uint8(len(data["cert_compression_algs"]) & 0xff)
copy(fixedData[1:], data["cert_compression_algs"])
_, err = extWriter.Write(fixedData)
if err != nil {
return err
}
case fakeRecordSizeLimit:
if data["record_size_limit"] == nil {
return errors.New("record_size_limit is required")
}
_, err = extWriter.Write(data["record_size_limit"]) // uint16 as []byte
if err != nil {
return err
}
case utlsExtensionApplicationSettings:
// TODO: tlsfingerprint.io should record/provide application settings data
extWriter.(*ApplicationSettingsExtension).SupportedProtocols = []string{"h2"}
case extensionPreSharedKey:
log.Printf("[Warning] PSK extension added without data")
default:
if !isGREASEUint16(extType) {
log.Printf("[Warning] extension %d added without data", extType)
} /*else {
log.Printf("[Warning] GREASE extension added but ID/Data discarded. They will be automatically re-GREASEd on ApplyPreset() call.")
}*/
}
chs.Extensions = append(chs.Extensions, extWriter)
}
}
return nil
}
// ImportTLSClientHelloFromJSON imports ClientHelloSpec from JSON data from client.tlsfingerprint.io format
//
// It calls ImportTLSClientHello internally after unmarshaling JSON data into map[string][]byte
func (chs *ClientHelloSpec) ImportTLSClientHelloFromJSON(jsonB []byte) error {
var data map[string][]byte
err := json.Unmarshal(jsonB, &data)
if err != nil {
return err
}
return chs.ImportTLSClientHello(data)
}
// FromRaw converts a ClientHello message in the form of raw bytes into a ClientHelloSpec.
//
// ctrlFlags: []bool{bluntMimicry, realPSK}
func (chs *ClientHelloSpec) FromRaw(raw []byte, ctrlFlags ...bool) error {
if chs == nil {
return errors.New("cannot unmarshal into nil ClientHelloSpec")
}
var bluntMimicry = false
var realPSK = false
if len(ctrlFlags) > 0 {
bluntMimicry = ctrlFlags[0]
}
if len(ctrlFlags) > 1 {
realPSK = ctrlFlags[1]
}
*chs = ClientHelloSpec{} // reset
s := cryptobyte.String(raw)
var contentType uint8
var recordVersion uint16
if !s.ReadUint8(&contentType) || // record type
!s.ReadUint16(&recordVersion) || !s.Skip(2) { // record version and length
return errors.New("unable to read record type, version, and length")
}
if recordType(contentType) != recordTypeHandshake {
return errors.New("record is not a handshake")
}
var handshakeVersion uint16
var handshakeType uint8
if !s.ReadUint8(&handshakeType) || !s.Skip(3) || // message type and 3 byte length
!s.ReadUint16(&handshakeVersion) || !s.Skip(32) { // 32 byte random
return errors.New("unable to read handshake message type, length, and random")
}
if handshakeType != typeClientHello {
return errors.New("handshake message is not a ClientHello")
}
chs.TLSVersMin = recordVersion
chs.TLSVersMax = handshakeVersion
var ignoredSessionID cryptobyte.String
if !s.ReadUint8LengthPrefixed(&ignoredSessionID) {
return errors.New("unable to read session id")
}
// CipherSuites
var cipherSuitesBytes cryptobyte.String
if !s.ReadUint16LengthPrefixed(&cipherSuitesBytes) {
return errors.New("unable to read ciphersuites")
}
if err := chs.ReadCipherSuites(cipherSuitesBytes); err != nil {
return err
}
// CompressionMethods
var compressionMethods cryptobyte.String
if !s.ReadUint8LengthPrefixed(&compressionMethods) {
return errors.New("unable to read compression methods")
}
if err := chs.ReadCompressionMethods(compressionMethods); err != nil {
return err
}
if s.Empty() {
// Extensions are optional
return nil
}
var extensions cryptobyte.String
if !s.ReadUint16LengthPrefixed(&extensions) {
return errors.New("unable to read extensions data")
}
if err := chs.ReadTLSExtensions(extensions, bluntMimicry, realPSK); err != nil {
return err
}
return nil
}
// UnmarshalJSON unmarshals a ClientHello message in the form of JSON into a ClientHelloSpec.
func (chs *ClientHelloSpec) UnmarshalJSON(jsonB []byte) error {
var chsju ClientHelloSpecJSONUnmarshaler
if err := json.Unmarshal(jsonB, &chsju); err != nil {
return err
}
*chs = chsju.ClientHelloSpec()
return nil
}
var (
// HelloGolang will use default "crypto/tls" handshake marshaling codepath, which WILL
// overwrite your changes to Hello(Config, Session are fine).
// You might want to call BuildHandshakeState() before applying any changes.
// UConn.Extensions will be completely ignored.
HelloGolang = ClientHelloID{helloGolang, helloAutoVers, nil, nil}
// HelloCustom will prepare ClientHello with empty uconn.Extensions so you can fill it with
// TLSExtensions manually or use ApplyPreset function
HelloCustom = ClientHelloID{helloCustom, helloAutoVers, nil, nil}
// HelloRandomized* randomly adds/reorders extensions, ciphersuites, etc.
HelloRandomized = ClientHelloID{helloRandomized, helloAutoVers, nil, nil}
HelloRandomizedALPN = ClientHelloID{helloRandomizedALPN, helloAutoVers, nil, nil}
HelloRandomizedNoALPN = ClientHelloID{helloRandomizedNoALPN, helloAutoVers, nil, nil}
// The rest will will parrot given browser.
HelloFirefox_Auto = HelloFirefox_105
HelloFirefox_55 = ClientHelloID{helloFirefox, "55", nil, nil}
HelloFirefox_56 = ClientHelloID{helloFirefox, "56", nil, nil}
HelloFirefox_63 = ClientHelloID{helloFirefox, "63", nil, nil}
HelloFirefox_65 = ClientHelloID{helloFirefox, "65", nil, nil}
HelloFirefox_99 = ClientHelloID{helloFirefox, "99", nil, nil}
HelloFirefox_102 = ClientHelloID{helloFirefox, "102", nil, nil}
HelloFirefox_105 = ClientHelloID{helloFirefox, "105", nil, nil}
HelloChrome_Auto = HelloChrome_106_Shuffle
HelloChrome_58 = ClientHelloID{helloChrome, "58", nil, nil}
HelloChrome_62 = ClientHelloID{helloChrome, "62", nil, nil}
HelloChrome_70 = ClientHelloID{helloChrome, "70", nil, nil}
HelloChrome_72 = ClientHelloID{helloChrome, "72", nil, nil}
HelloChrome_83 = ClientHelloID{helloChrome, "83", nil, nil}
HelloChrome_87 = ClientHelloID{helloChrome, "87", nil, nil}
HelloChrome_96 = ClientHelloID{helloChrome, "96", nil, nil}
HelloChrome_100 = ClientHelloID{helloChrome, "100", nil, nil}
HelloChrome_102 = ClientHelloID{helloChrome, "102", nil, nil}
HelloChrome_106_Shuffle = ClientHelloID{helloChrome, "106", nil, nil} // beta: shuffler enabled starting from 106
// 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
HelloIOS_12_1 = ClientHelloID{helloIOS, "12.1", nil, nil}
HelloIOS_13 = ClientHelloID{helloIOS, "13", nil, nil}
HelloIOS_14 = ClientHelloID{helloIOS, "14", nil, nil}
HelloAndroid_11_OkHttp = ClientHelloID{helloAndroid, "11", nil, nil}
HelloEdge_Auto = HelloEdge_85 // HelloEdge_106 seems to be incompatible with this library
HelloEdge_85 = ClientHelloID{helloEdge, "85", nil, nil}
HelloEdge_106 = ClientHelloID{helloEdge, "106", nil, nil}
HelloSafari_Auto = HelloSafari_16_0
HelloSafari_16_0 = ClientHelloID{helloSafari, "16.0", nil, nil}
Hello360_Auto = Hello360_7_5 // Hello360_11_0 seems to be incompatible with this library
Hello360_7_5 = ClientHelloID{hello360, "7.5", nil, nil}
Hello360_11_0 = ClientHelloID{hello360, "11.0", nil, nil}
HelloQQ_Auto = HelloQQ_11_1
HelloQQ_11_1 = ClientHelloID{helloQQ, "11.1", nil, nil}
)
type Weights struct {
Extensions_Append_ALPN float64
TLSVersMax_Set_VersionTLS13 float64
CipherSuites_Remove_RandomCiphers float64
SigAndHashAlgos_Append_ECDSAWithSHA1 float64
SigAndHashAlgos_Append_ECDSAWithP521AndSHA512 float64
SigAndHashAlgos_Append_PSSWithSHA256 float64
SigAndHashAlgos_Append_PSSWithSHA384_PSSWithSHA512 float64
CurveIDs_Append_X25519 float64
CurveIDs_Append_CurveP521 float64
Extensions_Append_Padding float64
Extensions_Append_Status float64
Extensions_Append_SCT float64
Extensions_Append_Reneg float64
Extensions_Append_EMS float64
FirstKeyShare_Set_CurveP256 float64
Extensions_Append_ALPS float64
}
// Do not modify them directly as they may being used. If you
// want to use your custom weights, please make a copy first.
var DefaultWeights = Weights{
Extensions_Append_ALPN: 0.7,
TLSVersMax_Set_VersionTLS13: 0.4,
CipherSuites_Remove_RandomCiphers: 0.4,
SigAndHashAlgos_Append_ECDSAWithSHA1: 0.63,
SigAndHashAlgos_Append_ECDSAWithP521AndSHA512: 0.59,
SigAndHashAlgos_Append_PSSWithSHA256: 0.51,
SigAndHashAlgos_Append_PSSWithSHA384_PSSWithSHA512: 0.9,
CurveIDs_Append_X25519: 0.71,
CurveIDs_Append_CurveP521: 0.46,
Extensions_Append_Padding: 0.62,
Extensions_Append_Status: 0.74,
Extensions_Append_SCT: 0.46,
Extensions_Append_Reneg: 0.75,
Extensions_Append_EMS: 0.77,
FirstKeyShare_Set_CurveP256: 0.25,
Extensions_Append_ALPS: 0.33,
}
// based on spec's GreaseStyle, GREASE_PLACEHOLDER may be replaced by another GREASE value
// https://tools.ietf.org/html/draft-ietf-tls-grease-01
const GREASE_PLACEHOLDER = 0x0a0a
func isGREASEUint16(v uint16) bool {
// First byte is same as second byte
// and lowest nibble is 0xa
return ((v >> 8) == v&0xff) && v&0xf == 0xa
}
func unGREASEUint16(v uint16) uint16 {
if isGREASEUint16(v) {
return GREASE_PLACEHOLDER
} else {
return v
}
}
// utlsMacSHA384 returns a SHA-384 based MAC. These are only supported in TLS 1.2
// so the given version is ignored.
func utlsMacSHA384(key []byte) hash.Hash {
return hmac.New(sha512.New384, key)
}
var utlsSupportedCipherSuites []*cipherSuite
func init() {
utlsSupportedCipherSuites = append(cipherSuites, []*cipherSuite{
{OLD_TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, 32, 0, 12, ecdheRSAKA,
suiteECDHE | suiteTLS12, nil, nil, aeadChaCha20Poly1305},
{OLD_TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, 32, 0, 12, ecdheECDSAKA,
suiteECDHE | suiteECSign | suiteTLS12, nil, nil, aeadChaCha20Poly1305},
}...)
}
// EnableWeakCiphers allows utls connections to continue in some cases, when weak cipher was chosen.
// This provides better compatibility with servers on the web, but weakens security. Feel free
// to use this option if you establish additional secure connection inside of utls connection.
// This option does not change the shape of parrots (i.e. same ciphers will be offered either way).
// Must be called before establishing any connections.
func EnableWeakCiphers() {
utlsSupportedCipherSuites = append(cipherSuites, []*cipherSuite{
{DISABLED_TLS_RSA_WITH_AES_256_CBC_SHA256, 32, 32, 16, rsaKA,
suiteTLS12, cipherAES, macSHA256, nil},
{DISABLED_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, 32, 48, 16, ecdheECDSAKA,
suiteECDHE | suiteECSign | suiteTLS12 | suiteSHA384, cipherAES, utlsMacSHA384, nil},
{DISABLED_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, 32, 48, 16, ecdheRSAKA,
suiteECDHE | suiteTLS12 | suiteSHA384, cipherAES, utlsMacSHA384, nil},
}...)
}
func mapSlice[T any, U any](slice []T, transform func(T) U) []U {
newSlice := make([]U, 0, len(slice))
for _, t := range slice {
newSlice = append(newSlice, transform(t))
}
return newSlice
}
func panicOnNil(caller string, params ...any) {
for i, p := range params {
if p == nil {
panic(fmt.Sprintf("tls: %s failed: the [%d] parameter is nil", caller, i))
}
}
}
func anyTrue[T any](slice []T, predicate func(i int, t *T) bool) bool {
for i := 0; i < len(slice); i++ {
if predicate(i, &slice[i]) {
return true
}
}
return false
}
func allTrue[T any](slice []T, predicate func(i int, t *T) bool) bool {
for i := 0; i < len(slice); i++ {
if !predicate(i, &slice[i]) {
return false
}
}
return true
}
func uAssert(condition bool, msg string) {
if !condition {
panic(msg)
}
}
func sliceEq[T comparable](sliceA []T, sliceB []T) bool {
if len(sliceA) != len(sliceB) {
return false
}
for i := 0; i < len(sliceA); i++ {
if sliceA[i] != sliceB[i] {
return false
}
}
return true
}
type Initializable interface {
// IsInitialized returns a boolean indicating whether the extension has been initialized.
// If false is returned, utls will initialize the extension.
IsInitialized() bool
}