mirror of
https://github.com/v2fly/v2ray-core.git
synced 2024-09-07 04:24:23 -04:00
795 lines
23 KiB
Go
795 lines
23 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/rand"
|
||
|
"crypto/sha256"
|
||
|
"encoding/binary"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"math/big"
|
||
|
"sort"
|
||
|
"strconv"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
func utlsIdToSpec(id ClientHelloID) (ClientHelloSpec, error) {
|
||
|
switch id {
|
||
|
case HelloChrome_58, HelloChrome_62:
|
||
|
return ClientHelloSpec{
|
||
|
TLSVersMax: VersionTLS12,
|
||
|
TLSVersMin: VersionTLS10,
|
||
|
CipherSuites: []uint16{
|
||
|
GREASE_PLACEHOLDER,
|
||
|
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,
|
||
|
TLS_RSA_WITH_3DES_EDE_CBC_SHA,
|
||
|
},
|
||
|
CompressionMethods: []byte{compressionNone},
|
||
|
Extensions: []TLSExtension{
|
||
|
&UtlsGREASEExtension{},
|
||
|
&RenegotiationInfoExtension{renegotiation: RenegotiateOnceAsClient},
|
||
|
&SNIExtension{},
|
||
|
&UtlsExtendedMasterSecretExtension{},
|
||
|
&SessionTicketExtension{},
|
||
|
&SignatureAlgorithmsExtension{SupportedSignatureAlgorithms: []SignatureScheme{
|
||
|
ECDSAWithP256AndSHA256,
|
||
|
PSSWithSHA256,
|
||
|
PKCS1WithSHA256,
|
||
|
ECDSAWithP384AndSHA384,
|
||
|
PSSWithSHA384,
|
||
|
PKCS1WithSHA384,
|
||
|
PSSWithSHA512,
|
||
|
PKCS1WithSHA512,
|
||
|
PKCS1WithSHA1},
|
||
|
},
|
||
|
&StatusRequestExtension{},
|
||
|
&SCTExtension{},
|
||
|
&ALPNExtension{AlpnProtocols: []string{"h2", "http/1.1"}},
|
||
|
&FakeChannelIDExtension{},
|
||
|
&SupportedPointsExtension{SupportedPoints: []byte{pointFormatUncompressed}},
|
||
|
&SupportedCurvesExtension{[]CurveID{CurveID(GREASE_PLACEHOLDER),
|
||
|
X25519, CurveP256, CurveP384}},
|
||
|
&UtlsGREASEExtension{},
|
||
|
&UtlsPaddingExtension{GetPaddingLen: BoringPaddingStyle},
|
||
|
},
|
||
|
GetSessionID: sha256.Sum256,
|
||
|
}, nil
|
||
|
case HelloChrome_70:
|
||
|
return ClientHelloSpec{
|
||
|
TLSVersMin: VersionTLS10,
|
||
|
TLSVersMax: VersionTLS13,
|
||
|
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,
|
||
|
TLS_RSA_WITH_3DES_EDE_CBC_SHA,
|
||
|
},
|
||
|
CompressionMethods: []byte{
|
||
|
compressionNone,
|
||
|
},
|
||
|
Extensions: []TLSExtension{
|
||
|
&UtlsGREASEExtension{},
|
||
|
&RenegotiationInfoExtension{renegotiation: RenegotiateOnceAsClient},
|
||
|
&SNIExtension{},
|
||
|
&UtlsExtendedMasterSecretExtension{},
|
||
|
&SessionTicketExtension{},
|
||
|
&SignatureAlgorithmsExtension{SupportedSignatureAlgorithms: []SignatureScheme{
|
||
|
ECDSAWithP256AndSHA256,
|
||
|
PSSWithSHA256,
|
||
|
PKCS1WithSHA256,
|
||
|
ECDSAWithP384AndSHA384,
|
||
|
PSSWithSHA384,
|
||
|
PKCS1WithSHA384,
|
||
|
PSSWithSHA512,
|
||
|
PKCS1WithSHA512,
|
||
|
PKCS1WithSHA1,
|
||
|
}},
|
||
|
&StatusRequestExtension{},
|
||
|
&SCTExtension{},
|
||
|
&ALPNExtension{AlpnProtocols: []string{"h2", "http/1.1"}},
|
||
|
&FakeChannelIDExtension{},
|
||
|
&SupportedPointsExtension{SupportedPoints: []byte{
|
||
|
pointFormatUncompressed,
|
||
|
}},
|
||
|
&KeyShareExtension{[]KeyShare{
|
||
|
{Group: CurveID(GREASE_PLACEHOLDER), Data: []byte{0}},
|
||
|
{Group: X25519},
|
||
|
}},
|
||
|
&PSKKeyExchangeModesExtension{[]uint8{pskModeDHE}},
|
||
|
&SupportedVersionsExtension{[]uint16{
|
||
|
GREASE_PLACEHOLDER,
|
||
|
VersionTLS13,
|
||
|
VersionTLS12,
|
||
|
VersionTLS11,
|
||
|
VersionTLS10}},
|
||
|
&SupportedCurvesExtension{[]CurveID{
|
||
|
CurveID(GREASE_PLACEHOLDER),
|
||
|
X25519,
|
||
|
CurveP256,
|
||
|
CurveP384,
|
||
|
}},
|
||
|
&GenericExtension{id: fakeCertCompressionAlgs, data: []byte{02, 00, 02}},
|
||
|
&UtlsGREASEExtension{},
|
||
|
&UtlsPaddingExtension{GetPaddingLen: BoringPaddingStyle},
|
||
|
},
|
||
|
}, nil
|
||
|
case HelloFirefox_55, HelloFirefox_56:
|
||
|
return ClientHelloSpec{
|
||
|
TLSVersMax: VersionTLS12,
|
||
|
TLSVersMin: VersionTLS10,
|
||
|
CipherSuites: []uint16{
|
||
|
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||
|
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||
|
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||
|
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||
|
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||
|
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||
|
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
||
|
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
||
|
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||
|
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||
|
FAKE_TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
|
||
|
FAKE_TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
|
||
|
TLS_RSA_WITH_AES_128_CBC_SHA,
|
||
|
TLS_RSA_WITH_AES_256_CBC_SHA,
|
||
|
TLS_RSA_WITH_3DES_EDE_CBC_SHA,
|
||
|
},
|
||
|
CompressionMethods: []byte{compressionNone},
|
||
|
Extensions: []TLSExtension{
|
||
|
&SNIExtension{},
|
||
|
&UtlsExtendedMasterSecretExtension{},
|
||
|
&RenegotiationInfoExtension{renegotiation: RenegotiateOnceAsClient},
|
||
|
&SupportedCurvesExtension{[]CurveID{X25519, CurveP256, CurveP384, CurveP521}},
|
||
|
&SupportedPointsExtension{SupportedPoints: []byte{pointFormatUncompressed}},
|
||
|
&SessionTicketExtension{},
|
||
|
&ALPNExtension{AlpnProtocols: []string{"h2", "http/1.1"}},
|
||
|
&StatusRequestExtension{},
|
||
|
&SignatureAlgorithmsExtension{SupportedSignatureAlgorithms: []SignatureScheme{
|
||
|
ECDSAWithP256AndSHA256,
|
||
|
ECDSAWithP384AndSHA384,
|
||
|
ECDSAWithP521AndSHA512,
|
||
|
PSSWithSHA256,
|
||
|
PSSWithSHA384,
|
||
|
PSSWithSHA512,
|
||
|
PKCS1WithSHA256,
|
||
|
PKCS1WithSHA384,
|
||
|
PKCS1WithSHA512,
|
||
|
ECDSAWithSHA1,
|
||
|
PKCS1WithSHA1},
|
||
|
},
|
||
|
&UtlsPaddingExtension{GetPaddingLen: BoringPaddingStyle},
|
||
|
},
|
||
|
GetSessionID: nil,
|
||
|
}, nil
|
||
|
case HelloFirefox_63:
|
||
|
return ClientHelloSpec{
|
||
|
TLSVersMin: VersionTLS10,
|
||
|
TLSVersMax: VersionTLS13,
|
||
|
CipherSuites: []uint16{
|
||
|
TLS_AES_128_GCM_SHA256,
|
||
|
TLS_CHACHA20_POLY1305_SHA256,
|
||
|
TLS_AES_256_GCM_SHA384,
|
||
|
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||
|
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||
|
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||
|
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||
|
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||
|
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||
|
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
||
|
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
||
|
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||
|
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||
|
FAKE_TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
|
||
|
FAKE_TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
|
||
|
TLS_RSA_WITH_AES_128_CBC_SHA,
|
||
|
TLS_RSA_WITH_AES_256_CBC_SHA,
|
||
|
TLS_RSA_WITH_3DES_EDE_CBC_SHA,
|
||
|
},
|
||
|
CompressionMethods: []byte{
|
||
|
compressionNone,
|
||
|
},
|
||
|
Extensions: []TLSExtension{
|
||
|
&SNIExtension{},
|
||
|
&UtlsExtendedMasterSecretExtension{},
|
||
|
&RenegotiationInfoExtension{renegotiation: RenegotiateOnceAsClient},
|
||
|
&SupportedCurvesExtension{[]CurveID{
|
||
|
X25519,
|
||
|
CurveP256,
|
||
|
CurveP384,
|
||
|
CurveP521,
|
||
|
CurveID(FakeFFDHE2048),
|
||
|
CurveID(FakeFFDHE3072),
|
||
|
}},
|
||
|
&SupportedPointsExtension{SupportedPoints: []byte{
|
||
|
pointFormatUncompressed,
|
||
|
}},
|
||
|
&SessionTicketExtension{},
|
||
|
&ALPNExtension{AlpnProtocols: []string{"h2", "http/1.1"}},
|
||
|
&StatusRequestExtension{},
|
||
|
&KeyShareExtension{[]KeyShare{
|
||
|
{Group: X25519},
|
||
|
{Group: CurveP256},
|
||
|
}},
|
||
|
&SupportedVersionsExtension{[]uint16{
|
||
|
VersionTLS13,
|
||
|
VersionTLS12,
|
||
|
VersionTLS11,
|
||
|
VersionTLS10}},
|
||
|
&SignatureAlgorithmsExtension{SupportedSignatureAlgorithms: []SignatureScheme{
|
||
|
ECDSAWithP256AndSHA256,
|
||
|
ECDSAWithP384AndSHA384,
|
||
|
ECDSAWithP521AndSHA512,
|
||
|
PSSWithSHA256,
|
||
|
PSSWithSHA384,
|
||
|
PSSWithSHA512,
|
||
|
PKCS1WithSHA256,
|
||
|
PKCS1WithSHA384,
|
||
|
PKCS1WithSHA512,
|
||
|
ECDSAWithSHA1,
|
||
|
PKCS1WithSHA1,
|
||
|
}},
|
||
|
&PSKKeyExchangeModesExtension{[]uint8{pskModeDHE}},
|
||
|
&GenericExtension{id: fakeRecordSizeLimit, data: []byte{0x40, 0x01}},
|
||
|
&UtlsPaddingExtension{GetPaddingLen: BoringPaddingStyle},
|
||
|
}}, nil
|
||
|
case HelloIOS_11_1:
|
||
|
return ClientHelloSpec{
|
||
|
TLSVersMax: VersionTLS12,
|
||
|
TLSVersMin: VersionTLS10,
|
||
|
CipherSuites: []uint16{
|
||
|
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||
|
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||
|
DISABLED_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,
|
||
|
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
|
||
|
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
||
|
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
||
|
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||
|
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||
|
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||
|
DISABLED_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,
|
||
|
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
|
||
|
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||
|
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||
|
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||
|
TLS_RSA_WITH_AES_256_GCM_SHA384,
|
||
|
TLS_RSA_WITH_AES_128_GCM_SHA256,
|
||
|
DISABLED_TLS_RSA_WITH_AES_256_CBC_SHA256,
|
||
|
TLS_RSA_WITH_AES_128_CBC_SHA256,
|
||
|
TLS_RSA_WITH_AES_256_CBC_SHA,
|
||
|
TLS_RSA_WITH_AES_128_CBC_SHA,
|
||
|
},
|
||
|
CompressionMethods: []byte{
|
||
|
compressionNone,
|
||
|
},
|
||
|
Extensions: []TLSExtension{
|
||
|
&RenegotiationInfoExtension{renegotiation: RenegotiateOnceAsClient},
|
||
|
&SNIExtension{},
|
||
|
&UtlsExtendedMasterSecretExtension{},
|
||
|
&SignatureAlgorithmsExtension{SupportedSignatureAlgorithms: []SignatureScheme{
|
||
|
ECDSAWithP256AndSHA256,
|
||
|
PSSWithSHA256,
|
||
|
PKCS1WithSHA256,
|
||
|
ECDSAWithP384AndSHA384,
|
||
|
PSSWithSHA384,
|
||
|
PKCS1WithSHA384,
|
||
|
PSSWithSHA512,
|
||
|
PKCS1WithSHA512,
|
||
|
PKCS1WithSHA1,
|
||
|
}},
|
||
|
&StatusRequestExtension{},
|
||
|
&NPNExtension{},
|
||
|
&SCTExtension{},
|
||
|
&ALPNExtension{AlpnProtocols: []string{"h2", "h2-16", "h2-15", "h2-14", "spdy/3.1", "spdy/3", "http/1.1"}},
|
||
|
&SupportedPointsExtension{SupportedPoints: []byte{
|
||
|
pointFormatUncompressed,
|
||
|
}},
|
||
|
&SupportedCurvesExtension{Curves: []CurveID{
|
||
|
X25519,
|
||
|
CurveP256,
|
||
|
CurveP384,
|
||
|
CurveP521,
|
||
|
}},
|
||
|
},
|
||
|
}, nil
|
||
|
default:
|
||
|
return ClientHelloSpec{}, errors.New("ClientHello ID " + id.Str() + " is unknown")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (uconn *UConn) applyPresetByID(id ClientHelloID) (err error) {
|
||
|
var spec ClientHelloSpec
|
||
|
// choose/generate the spec
|
||
|
switch id {
|
||
|
case HelloRandomized:
|
||
|
if tossBiasedCoin(0.5) {
|
||
|
return uconn.applyPresetByID(HelloRandomizedALPN)
|
||
|
} else {
|
||
|
return uconn.applyPresetByID(HelloRandomizedNoALPN)
|
||
|
}
|
||
|
case HelloRandomizedALPN:
|
||
|
spec, err = uconn.generateRandomizedSpec(true)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
case HelloRandomizedNoALPN:
|
||
|
spec, err = uconn.generateRandomizedSpec(false)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
case HelloCustom:
|
||
|
return nil
|
||
|
|
||
|
default:
|
||
|
spec, err = utlsIdToSpec(id)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
uconn.clientHelloID = id
|
||
|
return uconn.ApplyPreset(&spec)
|
||
|
}
|
||
|
|
||
|
// ApplyPreset should only be used in conjunction with HelloCustom to apply custom specs.
|
||
|
// Fields of TLSExtensions that are slices/pointers are shared across different connections with
|
||
|
// same ClientHelloSpec. It is advised to use different specs and avoid any shared state.
|
||
|
func (uconn *UConn) ApplyPreset(p *ClientHelloSpec) error {
|
||
|
var err error
|
||
|
err = uconn.SetTLSVers(p.TLSVersMin, p.TLSVersMax)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
privateHello, ecdheParams, err := uconn.makeClientHello()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
uconn.HandshakeState.Hello = privateHello.getPublicPtr()
|
||
|
uconn.HandshakeState.State13.EcdheParams = ecdheParams
|
||
|
hello := uconn.HandshakeState.Hello
|
||
|
session := uconn.HandshakeState.Session
|
||
|
|
||
|
switch len(hello.Random) {
|
||
|
case 0:
|
||
|
hello.Random = make([]byte, 32)
|
||
|
_, err := io.ReadFull(uconn.config.rand(), hello.Random)
|
||
|
if err != nil {
|
||
|
return errors.New("tls: short read from Rand: " + err.Error())
|
||
|
}
|
||
|
case 32:
|
||
|
// carry on
|
||
|
default:
|
||
|
return errors.New("ClientHello expected length: 32 bytes. Got: " +
|
||
|
strconv.Itoa(len(hello.Random)) + " bytes")
|
||
|
}
|
||
|
if len(hello.CipherSuites) == 0 {
|
||
|
hello.CipherSuites = defaultCipherSuites()
|
||
|
}
|
||
|
if len(hello.CompressionMethods) == 0 {
|
||
|
hello.CompressionMethods = []uint8{compressionNone}
|
||
|
}
|
||
|
|
||
|
// Currently, GREASE is assumed to come from BoringSSL
|
||
|
grease_bytes := make([]byte, 2*ssl_grease_last_index)
|
||
|
grease_extensions_seen := 0
|
||
|
_, err = io.ReadFull(uconn.config.rand(), grease_bytes)
|
||
|
if err != nil {
|
||
|
return errors.New("tls: short read from Rand: " + err.Error())
|
||
|
}
|
||
|
for i := range uconn.greaseSeed {
|
||
|
uconn.greaseSeed[i] = binary.LittleEndian.Uint16(grease_bytes[2*i : 2*i+2])
|
||
|
}
|
||
|
if uconn.greaseSeed[ssl_grease_extension1] == uconn.greaseSeed[ssl_grease_extension2] {
|
||
|
uconn.greaseSeed[ssl_grease_extension2] ^= 0x1010
|
||
|
}
|
||
|
|
||
|
hello.CipherSuites = make([]uint16, len(p.CipherSuites))
|
||
|
copy(hello.CipherSuites, p.CipherSuites)
|
||
|
for i := range hello.CipherSuites {
|
||
|
if hello.CipherSuites[i] == GREASE_PLACEHOLDER {
|
||
|
hello.CipherSuites[i] = GetBoringGREASEValue(uconn.greaseSeed, ssl_grease_cipher)
|
||
|
}
|
||
|
}
|
||
|
uconn.GetSessionID = p.GetSessionID
|
||
|
uconn.Extensions = make([]TLSExtension, len(p.Extensions))
|
||
|
copy(uconn.Extensions, p.Extensions)
|
||
|
|
||
|
// reGrease, and point things to each other
|
||
|
for _, e := range uconn.Extensions {
|
||
|
switch ext := e.(type) {
|
||
|
case *SNIExtension:
|
||
|
if ext.ServerName == "" {
|
||
|
ext.ServerName = uconn.config.ServerName
|
||
|
}
|
||
|
case *UtlsGREASEExtension:
|
||
|
switch grease_extensions_seen {
|
||
|
case 0:
|
||
|
ext.Value = GetBoringGREASEValue(uconn.greaseSeed, ssl_grease_extension1)
|
||
|
case 1:
|
||
|
ext.Value = GetBoringGREASEValue(uconn.greaseSeed, ssl_grease_extension2)
|
||
|
ext.Body = []byte{0}
|
||
|
default:
|
||
|
return errors.New("at most 2 grease extensions are supported")
|
||
|
}
|
||
|
grease_extensions_seen += 1
|
||
|
case *SessionTicketExtension:
|
||
|
err := uconn.SetSessionState(session)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
case *SupportedCurvesExtension:
|
||
|
for i := range ext.Curves {
|
||
|
if ext.Curves[i] == GREASE_PLACEHOLDER {
|
||
|
ext.Curves[i] = CurveID(GetBoringGREASEValue(uconn.greaseSeed, ssl_grease_group))
|
||
|
}
|
||
|
}
|
||
|
case *KeyShareExtension:
|
||
|
preferredCurveIsSet := false
|
||
|
for i := range ext.KeyShares {
|
||
|
curveID := ext.KeyShares[i].Group
|
||
|
if curveID == GREASE_PLACEHOLDER {
|
||
|
ext.KeyShares[i].Group = CurveID(GetBoringGREASEValue(uconn.greaseSeed, ssl_grease_group))
|
||
|
continue
|
||
|
}
|
||
|
if len(ext.KeyShares[i].Data) > 1 {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
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)
|
||
|
}
|
||
|
ext.KeyShares[i].Data = ecdheParams.PublicKey()
|
||
|
if !preferredCurveIsSet {
|
||
|
// only do this once for the first non-grease curve
|
||
|
uconn.HandshakeState.State13.EcdheParams = ecdheParams
|
||
|
preferredCurveIsSet = true
|
||
|
}
|
||
|
}
|
||
|
case *SupportedVersionsExtension:
|
||
|
for i := range ext.Versions {
|
||
|
if ext.Versions[i] == GREASE_PLACEHOLDER {
|
||
|
ext.Versions[i] = GetBoringGREASEValue(uconn.greaseSeed, ssl_grease_version)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (uconn *UConn) generateRandomizedSpec(WithALPN bool) (ClientHelloSpec, error) {
|
||
|
p := ClientHelloSpec{}
|
||
|
|
||
|
p.CipherSuites = make([]uint16, len(defaultCipherSuites()))
|
||
|
copy(p.CipherSuites, defaultCipherSuites())
|
||
|
shuffledSuites, err := shuffledCiphers()
|
||
|
if err != nil {
|
||
|
return p, err
|
||
|
}
|
||
|
|
||
|
if tossBiasedCoin(0.4) {
|
||
|
p.TLSVersMin = VersionTLS10
|
||
|
p.TLSVersMax = VersionTLS13
|
||
|
tls13ciphers := defaultCipherSuitesTLS13()
|
||
|
err = shuffleUInts16(tls13ciphers)
|
||
|
if err != nil {
|
||
|
return p, err
|
||
|
}
|
||
|
// appending TLS 1.3 ciphers before TLS 1.2, since that's what popular implementations do
|
||
|
shuffledSuites = append(tls13ciphers, shuffledSuites...)
|
||
|
|
||
|
// TLS 1.3 forbids RC4 in any configurations
|
||
|
shuffledSuites = removeRC4Ciphers(shuffledSuites)
|
||
|
} else {
|
||
|
p.TLSVersMin = VersionTLS10
|
||
|
p.TLSVersMax = VersionTLS12
|
||
|
}
|
||
|
|
||
|
p.CipherSuites = removeRandomCiphers(shuffledSuites, 0.4)
|
||
|
|
||
|
sni := SNIExtension{uconn.config.ServerName}
|
||
|
sessionTicket := SessionTicketExtension{Session: uconn.HandshakeState.Session}
|
||
|
|
||
|
sigAndHashAlgos := []SignatureScheme{
|
||
|
ECDSAWithP256AndSHA256,
|
||
|
PKCS1WithSHA256,
|
||
|
ECDSAWithP384AndSHA384,
|
||
|
PKCS1WithSHA384,
|
||
|
PKCS1WithSHA1,
|
||
|
PKCS1WithSHA512,
|
||
|
}
|
||
|
|
||
|
if tossBiasedCoin(0.63) {
|
||
|
sigAndHashAlgos = append(sigAndHashAlgos, ECDSAWithSHA1)
|
||
|
}
|
||
|
if tossBiasedCoin(0.59) {
|
||
|
sigAndHashAlgos = append(sigAndHashAlgos, ECDSAWithP521AndSHA512)
|
||
|
}
|
||
|
if tossBiasedCoin(0.51) || p.TLSVersMax == VersionTLS13 {
|
||
|
// https://tools.ietf.org/html/rfc8446 says "...RSASSA-PSS (which is mandatory in TLS 1.3)..."
|
||
|
sigAndHashAlgos = append(sigAndHashAlgos, PSSWithSHA256)
|
||
|
if tossBiasedCoin(0.9) {
|
||
|
// these usually go together
|
||
|
sigAndHashAlgos = append(sigAndHashAlgos, PSSWithSHA384)
|
||
|
sigAndHashAlgos = append(sigAndHashAlgos, PSSWithSHA512)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
err = shuffleSignatures(sigAndHashAlgos)
|
||
|
if err != nil {
|
||
|
return p, err
|
||
|
}
|
||
|
sigAndHash := SignatureAlgorithmsExtension{SupportedSignatureAlgorithms: sigAndHashAlgos}
|
||
|
|
||
|
status := StatusRequestExtension{}
|
||
|
sct := SCTExtension{}
|
||
|
ems := UtlsExtendedMasterSecretExtension{}
|
||
|
points := SupportedPointsExtension{SupportedPoints: []byte{pointFormatUncompressed}}
|
||
|
|
||
|
curveIDs := []CurveID{}
|
||
|
if tossBiasedCoin(0.71) || p.TLSVersMax == VersionTLS13 {
|
||
|
curveIDs = append(curveIDs, X25519)
|
||
|
}
|
||
|
curveIDs = append(curveIDs, CurveP256, CurveP384)
|
||
|
if tossBiasedCoin(0.46) {
|
||
|
curveIDs = append(curveIDs, CurveP521)
|
||
|
}
|
||
|
|
||
|
curves := SupportedCurvesExtension{curveIDs}
|
||
|
|
||
|
padding := UtlsPaddingExtension{GetPaddingLen: BoringPaddingStyle}
|
||
|
reneg := RenegotiationInfoExtension{renegotiation: RenegotiateOnceAsClient}
|
||
|
|
||
|
p.Extensions = []TLSExtension{
|
||
|
&sni,
|
||
|
&sessionTicket,
|
||
|
&sigAndHash,
|
||
|
&points,
|
||
|
&curves,
|
||
|
}
|
||
|
|
||
|
if WithALPN {
|
||
|
if len(uconn.config.NextProtos) == 0 {
|
||
|
// if user didn't specify alpn yet, choose something popular
|
||
|
uconn.config.NextProtos = []string{"h2", "http/1.1"}
|
||
|
}
|
||
|
alpn := ALPNExtension{AlpnProtocols: uconn.config.NextProtos}
|
||
|
p.Extensions = append(p.Extensions, &alpn)
|
||
|
}
|
||
|
|
||
|
if tossBiasedCoin(0.62) || p.TLSVersMax == VersionTLS13 {
|
||
|
// always include for TLS 1.3, since TLS 1.3 ClientHellos are often over 256 bytes
|
||
|
// and that's when padding is required to work around buggy middleboxes
|
||
|
p.Extensions = append(p.Extensions, &padding)
|
||
|
}
|
||
|
if tossBiasedCoin(0.74) {
|
||
|
p.Extensions = append(p.Extensions, &status)
|
||
|
}
|
||
|
if tossBiasedCoin(0.46) {
|
||
|
p.Extensions = append(p.Extensions, &sct)
|
||
|
}
|
||
|
if tossBiasedCoin(0.75) {
|
||
|
p.Extensions = append(p.Extensions, &reneg)
|
||
|
}
|
||
|
if tossBiasedCoin(0.77) {
|
||
|
p.Extensions = append(p.Extensions, &ems)
|
||
|
}
|
||
|
if p.TLSVersMax == VersionTLS13 {
|
||
|
ks := KeyShareExtension{[]KeyShare{
|
||
|
{Group: X25519}, // the key for the group will be generated later
|
||
|
}}
|
||
|
if tossBiasedCoin(0.25) {
|
||
|
// do not ADD second keyShare because crypto/tls does not support multiple ecdheParams
|
||
|
// TODO: add it back when they implement multiple keyShares, or implement it oursevles
|
||
|
// ks.KeyShares = append(ks.KeyShares, KeyShare{Group: CurveP256})
|
||
|
ks.KeyShares[0].Group = CurveP256
|
||
|
}
|
||
|
pskExchangeModes := PSKKeyExchangeModesExtension{[]uint8{pskModeDHE}}
|
||
|
supportedVersionsExt := SupportedVersionsExtension{
|
||
|
Versions: makeSupportedVersions(p.TLSVersMin, p.TLSVersMax),
|
||
|
}
|
||
|
p.Extensions = append(p.Extensions, &ks, &pskExchangeModes, &supportedVersionsExt)
|
||
|
}
|
||
|
err = shuffleTLSExtensions(p.Extensions)
|
||
|
if err != nil {
|
||
|
return p, err
|
||
|
}
|
||
|
err = uconn.SetTLSVers(p.TLSVersMin, p.TLSVersMax)
|
||
|
if err != nil {
|
||
|
return p, err
|
||
|
}
|
||
|
|
||
|
return p, nil
|
||
|
}
|
||
|
|
||
|
func tossBiasedCoin(probability float32) bool {
|
||
|
// probability is expected to be in [0,1]
|
||
|
// this function never returns errors for ease of use
|
||
|
const precision = 0xffff
|
||
|
threshold := float32(precision) * probability
|
||
|
value, err := getRandInt(precision)
|
||
|
if err != nil {
|
||
|
// I doubt that this code will ever actually be used, as other functions are expected to complain
|
||
|
// about used source of entropy. Nonetheless, this is more than enough for given purpose
|
||
|
return ((time.Now().Unix() & 1) == 0)
|
||
|
}
|
||
|
|
||
|
if float32(value) <= threshold {
|
||
|
return true
|
||
|
} else {
|
||
|
return false
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func removeRandomCiphers(s []uint16, maxRemovalProbability float32) []uint16 {
|
||
|
// removes elements in place
|
||
|
// probability to remove increases for further elements
|
||
|
// never remove first cipher
|
||
|
if len(s) <= 1 {
|
||
|
return s
|
||
|
}
|
||
|
|
||
|
// remove random elements
|
||
|
floatLen := float32(len(s))
|
||
|
sliceLen := len(s)
|
||
|
for i := 1; i < sliceLen; i++ {
|
||
|
if tossBiasedCoin(maxRemovalProbability * float32(i) / floatLen) {
|
||
|
s = append(s[:i], s[i+1:]...)
|
||
|
sliceLen--
|
||
|
i--
|
||
|
}
|
||
|
}
|
||
|
return s[:sliceLen]
|
||
|
}
|
||
|
|
||
|
func removeRC4Ciphers(s []uint16) []uint16 {
|
||
|
// removes elements in place
|
||
|
sliceLen := len(s)
|
||
|
for i := 0; i < sliceLen; i++ {
|
||
|
cipher := s[i]
|
||
|
if cipher == TLS_ECDHE_ECDSA_WITH_RC4_128_SHA ||
|
||
|
cipher == TLS_ECDHE_RSA_WITH_RC4_128_SHA ||
|
||
|
cipher == TLS_RSA_WITH_RC4_128_SHA {
|
||
|
s = append(s[:i], s[i+1:]...)
|
||
|
sliceLen--
|
||
|
i--
|
||
|
}
|
||
|
}
|
||
|
return s[:sliceLen]
|
||
|
}
|
||
|
|
||
|
func getRandInt(max int) (int, error) {
|
||
|
bigInt, err := rand.Int(rand.Reader, big.NewInt(int64(max)))
|
||
|
return int(bigInt.Int64()), err
|
||
|
}
|
||
|
|
||
|
func getRandPerm(n int) ([]int, error) {
|
||
|
permArray := make([]int, n)
|
||
|
for i := 1; i < n; i++ {
|
||
|
j, err := getRandInt(i + 1)
|
||
|
if err != nil {
|
||
|
return permArray, err
|
||
|
}
|
||
|
permArray[i] = permArray[j]
|
||
|
permArray[j] = i
|
||
|
}
|
||
|
return permArray, nil
|
||
|
}
|
||
|
|
||
|
func shuffledCiphers() ([]uint16, error) {
|
||
|
ciphers := make(sortableCiphers, len(cipherSuites))
|
||
|
perm, err := getRandPerm(len(cipherSuites))
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
for i, suite := range cipherSuites {
|
||
|
ciphers[i] = sortableCipher{suite: suite.id,
|
||
|
isObsolete: ((suite.flags & suiteTLS12) == 0),
|
||
|
randomTag: perm[i]}
|
||
|
}
|
||
|
sort.Sort(ciphers)
|
||
|
return ciphers.GetCiphers(), nil
|
||
|
}
|
||
|
|
||
|
type sortableCipher struct {
|
||
|
isObsolete bool
|
||
|
randomTag int
|
||
|
suite uint16
|
||
|
}
|
||
|
|
||
|
type sortableCiphers []sortableCipher
|
||
|
|
||
|
func (ciphers sortableCiphers) Len() int {
|
||
|
return len(ciphers)
|
||
|
}
|
||
|
|
||
|
func (ciphers sortableCiphers) Less(i, j int) bool {
|
||
|
if ciphers[i].isObsolete && !ciphers[j].isObsolete {
|
||
|
return false
|
||
|
}
|
||
|
if ciphers[j].isObsolete && !ciphers[i].isObsolete {
|
||
|
return true
|
||
|
}
|
||
|
return ciphers[i].randomTag < ciphers[j].randomTag
|
||
|
}
|
||
|
|
||
|
func (ciphers sortableCiphers) Swap(i, j int) {
|
||
|
ciphers[i], ciphers[j] = ciphers[j], ciphers[i]
|
||
|
}
|
||
|
|
||
|
func (ciphers sortableCiphers) GetCiphers() []uint16 {
|
||
|
cipherIDs := make([]uint16, len(ciphers))
|
||
|
for i := range ciphers {
|
||
|
cipherIDs[i] = ciphers[i].suite
|
||
|
}
|
||
|
return cipherIDs
|
||
|
}
|
||
|
|
||
|
// so much for generics
|
||
|
func shuffleTLSExtensions(s []TLSExtension) error {
|
||
|
// shuffles array in place
|
||
|
perm, err := getRandPerm(len(s))
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
for i := range s {
|
||
|
s[i], s[perm[i]] = s[perm[i]], s[i]
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// so much for generics
|
||
|
func shuffleSignatures(s []SignatureScheme) error {
|
||
|
// shuffles array in place
|
||
|
perm, err := getRandPerm(len(s))
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
for i := range s {
|
||
|
s[i], s[perm[i]] = s[perm[i]], s[i]
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// so much for generics
|
||
|
func shuffleUInts16(s []uint16) error {
|
||
|
// shuffles array in place
|
||
|
perm, err := getRandPerm(len(s))
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
for i := range s {
|
||
|
s[i], s[perm[i]] = s[perm[i]], s[i]
|
||
|
}
|
||
|
return nil
|
||
|
}
|