|
@@ -58,6 +58,7 @@ import (
|
|
|
"crypto/x509"
|
|
"crypto/x509"
|
|
|
"encoding/hex"
|
|
"encoding/hex"
|
|
|
"errors"
|
|
"errors"
|
|
|
|
|
+ "fmt"
|
|
|
"io/ioutil"
|
|
"io/ioutil"
|
|
|
"net"
|
|
"net"
|
|
|
"time"
|
|
"time"
|
|
@@ -115,6 +116,11 @@ type CustomTLSConfig struct {
|
|
|
// compatibility constraints.
|
|
// compatibility constraints.
|
|
|
TLSProfile string
|
|
TLSProfile string
|
|
|
|
|
|
|
|
|
|
+ // NoDefaultTLSSessionID specifies whether to set a TLS session ID by
|
|
|
|
|
+ // default, for a new TLS connection that is not resuming a session.
|
|
|
|
|
+ // When nil, the parameter is set randomly.
|
|
|
|
|
+ NoDefaultTLSSessionID *bool
|
|
|
|
|
+
|
|
|
// RandomizedTLSProfileSeed specifies the PRNG seed to use when generating
|
|
// RandomizedTLSProfileSeed specifies the PRNG seed to use when generating
|
|
|
// a randomized TLS ClientHello, which applies to TLS profiles where
|
|
// a randomized TLS ClientHello, which applies to TLS profiles where
|
|
|
// protocol.TLSProfileIsRandomized is true. The PRNG seed allows for
|
|
// protocol.TLSProfileIsRandomized is true. The PRNG seed allows for
|
|
@@ -147,14 +153,20 @@ func (config *CustomTLSConfig) EnableClientSessionCache(
|
|
|
func SelectTLSProfile(
|
|
func SelectTLSProfile(
|
|
|
p *parameters.ClientParametersSnapshot) string {
|
|
p *parameters.ClientParametersSnapshot) string {
|
|
|
|
|
|
|
|
- // Two TLS profile lists are constructed, subject to limit constraints: fixed
|
|
|
|
|
- // parrots and randomized. If one list is empty, the non-empty list is used.
|
|
|
|
|
- // Otherwise SelectRandomizedTLSProfileProbability determines which list is used.
|
|
|
|
|
|
|
+ // Two TLS profile lists are constructed, subject to limit constraints:
|
|
|
|
|
+ // stock, fixed parrots (non-randomized SupportedTLSProfiles) and custom
|
|
|
|
|
+ // parrots (CustomTLSProfileNames); and randomized. If one list is empty, the
|
|
|
|
|
+ // non-empty list is used. Otherwise SelectRandomizedTLSProfileProbability
|
|
|
|
|
+ // determines which list is used.
|
|
|
|
|
+ //
|
|
|
|
|
+ // Note that LimitTLSProfiles is not applied to CustomTLSProfiles; the
|
|
|
|
|
+ // presence of a candidate in CustomTLSProfiles is treated as explicit
|
|
|
|
|
+ // enabling.
|
|
|
|
|
|
|
|
limitTLSProfiles := p.TLSProfiles(parameters.LimitTLSProfiles)
|
|
limitTLSProfiles := p.TLSProfiles(parameters.LimitTLSProfiles)
|
|
|
|
|
|
|
|
randomizedTLSProfiles := make([]string, 0)
|
|
randomizedTLSProfiles := make([]string, 0)
|
|
|
- parrotTLSProfiles := make([]string, 0)
|
|
|
|
|
|
|
+ parrotTLSProfiles := p.CustomTLSProfileNames()
|
|
|
|
|
|
|
|
for _, tlsProfile := range protocol.SupportedTLSProfiles {
|
|
for _, tlsProfile := range protocol.SupportedTLSProfiles {
|
|
|
|
|
|
|
@@ -184,42 +196,79 @@ func SelectTLSProfile(
|
|
|
return parrotTLSProfiles[prng.Intn(len(parrotTLSProfiles))]
|
|
return parrotTLSProfiles[prng.Intn(len(parrotTLSProfiles))]
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func getUTLSClientHelloID(tlsProfile string) utls.ClientHelloID {
|
|
|
|
|
|
|
+func getUTLSClientHelloID(
|
|
|
|
|
+ p *parameters.ClientParametersSnapshot,
|
|
|
|
|
+ tlsProfile string) (utls.ClientHelloID, *utls.ClientHelloSpec, error) {
|
|
|
|
|
+
|
|
|
switch tlsProfile {
|
|
switch tlsProfile {
|
|
|
case protocol.TLS_PROFILE_IOS_111:
|
|
case protocol.TLS_PROFILE_IOS_111:
|
|
|
- return utls.HelloIOS_11_1
|
|
|
|
|
|
|
+ return utls.HelloIOS_11_1, nil, nil
|
|
|
case protocol.TLS_PROFILE_IOS_121:
|
|
case protocol.TLS_PROFILE_IOS_121:
|
|
|
- return utls.HelloIOS_12_1
|
|
|
|
|
|
|
+ return utls.HelloIOS_12_1, nil, nil
|
|
|
case protocol.TLS_PROFILE_CHROME_58:
|
|
case protocol.TLS_PROFILE_CHROME_58:
|
|
|
- return utls.HelloChrome_58
|
|
|
|
|
|
|
+ return utls.HelloChrome_58, nil, nil
|
|
|
case protocol.TLS_PROFILE_CHROME_62:
|
|
case protocol.TLS_PROFILE_CHROME_62:
|
|
|
- return utls.HelloChrome_62
|
|
|
|
|
|
|
+ return utls.HelloChrome_62, nil, nil
|
|
|
case protocol.TLS_PROFILE_CHROME_70:
|
|
case protocol.TLS_PROFILE_CHROME_70:
|
|
|
- return utls.HelloChrome_70
|
|
|
|
|
|
|
+ return utls.HelloChrome_70, nil, nil
|
|
|
case protocol.TLS_PROFILE_CHROME_72:
|
|
case protocol.TLS_PROFILE_CHROME_72:
|
|
|
- return utls.HelloChrome_72
|
|
|
|
|
|
|
+ return utls.HelloChrome_72, nil, nil
|
|
|
case protocol.TLS_PROFILE_FIREFOX_55:
|
|
case protocol.TLS_PROFILE_FIREFOX_55:
|
|
|
- return utls.HelloFirefox_55
|
|
|
|
|
|
|
+ return utls.HelloFirefox_55, nil, nil
|
|
|
case protocol.TLS_PROFILE_FIREFOX_56:
|
|
case protocol.TLS_PROFILE_FIREFOX_56:
|
|
|
- return utls.HelloFirefox_56
|
|
|
|
|
|
|
+ return utls.HelloFirefox_56, nil, nil
|
|
|
case protocol.TLS_PROFILE_FIREFOX_65:
|
|
case protocol.TLS_PROFILE_FIREFOX_65:
|
|
|
- return utls.HelloFirefox_65
|
|
|
|
|
|
|
+ return utls.HelloFirefox_65, nil, nil
|
|
|
case protocol.TLS_PROFILE_RANDOMIZED:
|
|
case protocol.TLS_PROFILE_RANDOMIZED:
|
|
|
- return utls.HelloRandomized
|
|
|
|
|
- default:
|
|
|
|
|
- return utls.HelloGolang
|
|
|
|
|
|
|
+ return utls.HelloRandomized, nil, nil
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // utls.HelloCustom with a utls.ClientHelloSpec is used for
|
|
|
|
|
+ // CustomTLSProfiles.
|
|
|
|
|
+
|
|
|
|
|
+ customTLSProfile := p.CustomTLSProfile(tlsProfile)
|
|
|
|
|
+ if customTLSProfile == nil {
|
|
|
|
|
+ return utls.HelloCustom,
|
|
|
|
|
+ nil,
|
|
|
|
|
+ common.ContextError(fmt.Errorf("unknown TLS profile: %s", tlsProfile))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ utlsClientHelloSpec, err := customTLSProfile.GetClientHelloSpec()
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return utls.ClientHelloID{}, nil, common.ContextError(err)
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ return utls.HelloCustom, utlsClientHelloSpec, nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func getClientHelloVersion(utlsClientHelloID utls.ClientHelloID) (string, error) {
|
|
|
|
|
|
|
+func getClientHelloVersion(
|
|
|
|
|
+ utlsClientHelloID utls.ClientHelloID,
|
|
|
|
|
+ utlsClientHelloSpec *utls.ClientHelloSpec) (string, error) {
|
|
|
|
|
|
|
|
- // Assumes utlsClientHelloID.Seed has been set; otherwise the result is
|
|
|
|
|
- // ephemeral.
|
|
|
|
|
|
|
+ switch utlsClientHelloID {
|
|
|
|
|
+
|
|
|
|
|
+ case utls.HelloIOS_11_1, utls.HelloIOS_12_1, utls.HelloChrome_58,
|
|
|
|
|
+ utls.HelloChrome_62, utls.HelloFirefox_55, utls.HelloFirefox_56:
|
|
|
|
|
+ return protocol.TLS_VERSION_12, nil
|
|
|
|
|
+
|
|
|
|
|
+ case utls.HelloChrome_70, utls.HelloChrome_72, utls.HelloFirefox_65,
|
|
|
|
|
+ utls.HelloGolang:
|
|
|
|
|
+ return protocol.TLS_VERSION_13, nil
|
|
|
|
|
+
|
|
|
|
|
+ case utls.HelloCustom:
|
|
|
|
|
+ if utlsClientHelloSpec.TLSVersMax == utls.VersionTLS12 {
|
|
|
|
|
+ return protocol.TLS_VERSION_12, nil
|
|
|
|
|
+ }
|
|
|
|
|
+ return protocol.TLS_VERSION_13, nil
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
// As utls.HelloRandomized may be either TLS 1.2 or TLS 1.3, we cannot
|
|
// As utls.HelloRandomized may be either TLS 1.2 or TLS 1.3, we cannot
|
|
|
// perform a simple ClientHello ID check. BuildHandshakeState is run, which
|
|
// perform a simple ClientHello ID check. BuildHandshakeState is run, which
|
|
|
// constructs the entire ClientHello.
|
|
// constructs the entire ClientHello.
|
|
|
//
|
|
//
|
|
|
|
|
+ // Assumes utlsClientHelloID.Seed has been set; otherwise the result is
|
|
|
|
|
+ // ephemeral.
|
|
|
|
|
+ //
|
|
|
// BenchmarkRandomizedGetClientHelloVersion indicates that this operation
|
|
// BenchmarkRandomizedGetClientHelloVersion indicates that this operation
|
|
|
// takes on the order of 0.05ms and allocates ~8KB for randomized client
|
|
// takes on the order of 0.05ms and allocates ~8KB for randomized client
|
|
|
// hellos.
|
|
// hellos.
|
|
@@ -243,6 +292,21 @@ func getClientHelloVersion(utlsClientHelloID utls.ClientHelloID) (string, error)
|
|
|
return protocol.TLS_VERSION_12, nil
|
|
return protocol.TLS_VERSION_12, nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+func isNoDefaultSessionIDCandidate(utlsClientHelloID utls.ClientHelloID) bool {
|
|
|
|
|
+
|
|
|
|
|
+ // Either TLS 1.2 parrots or any randomized ClientHello is a candidate. This
|
|
|
|
|
+ // check doesn't incur the overhead of invoking BuildHandshakeState.
|
|
|
|
|
+
|
|
|
|
|
+ switch utlsClientHelloID {
|
|
|
|
|
+
|
|
|
|
|
+ case utls.HelloIOS_11_1, utls.HelloIOS_12_1, utls.HelloChrome_58,
|
|
|
|
|
+ utls.HelloChrome_62, utls.HelloFirefox_55, utls.HelloFirefox_56:
|
|
|
|
|
+ return true
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return utlsClientHelloID.Client == "Randomized"
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
func IsTLSConnUsingHTTP2(conn net.Conn) bool {
|
|
func IsTLSConnUsingHTTP2(conn net.Conn) bool {
|
|
|
if c, ok := conn.(*utls.UConn); ok {
|
|
if c, ok := conn.(*utls.UConn); ok {
|
|
|
state := c.ConnectionState()
|
|
state := c.ConnectionState()
|
|
@@ -274,6 +338,8 @@ func CustomTLSDial(
|
|
|
network, addr string,
|
|
network, addr string,
|
|
|
config *CustomTLSConfig) (net.Conn, error) {
|
|
config *CustomTLSConfig) (net.Conn, error) {
|
|
|
|
|
|
|
|
|
|
+ p := config.ClientParameters.Get()
|
|
|
|
|
+
|
|
|
dialAddr := addr
|
|
dialAddr := addr
|
|
|
if config.DialAddr != "" {
|
|
if config.DialAddr != "" {
|
|
|
dialAddr = config.DialAddr
|
|
dialAddr = config.DialAddr
|
|
@@ -293,7 +359,7 @@ func CustomTLSDial(
|
|
|
selectedTLSProfile := config.TLSProfile
|
|
selectedTLSProfile := config.TLSProfile
|
|
|
|
|
|
|
|
if selectedTLSProfile == "" {
|
|
if selectedTLSProfile == "" {
|
|
|
- selectedTLSProfile = SelectTLSProfile(config.ClientParameters.Get())
|
|
|
|
|
|
|
+ selectedTLSProfile = SelectTLSProfile(p)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
tlsConfigInsecureSkipVerify := false
|
|
tlsConfigInsecureSkipVerify := false
|
|
@@ -337,12 +403,14 @@ func CustomTLSDial(
|
|
|
ServerName: tlsConfigServerName,
|
|
ServerName: tlsConfigServerName,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- utlsClientHelloID := getUTLSClientHelloID(selectedTLSProfile)
|
|
|
|
|
-
|
|
|
|
|
- isRandomized := protocol.TLSProfileIsRandomized(selectedTLSProfile)
|
|
|
|
|
|
|
+ utlsClientHelloID, utlsClientHelloSpec, err := getUTLSClientHelloID(
|
|
|
|
|
+ p, selectedTLSProfile)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return nil, common.ContextError(err)
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
var randomizedTLSProfileSeed *prng.Seed
|
|
var randomizedTLSProfileSeed *prng.Seed
|
|
|
-
|
|
|
|
|
|
|
+ isRandomized := protocol.TLSProfileIsRandomized(selectedTLSProfile)
|
|
|
if isRandomized {
|
|
if isRandomized {
|
|
|
|
|
|
|
|
randomizedTLSProfileSeed = config.RandomizedTLSProfileSeed
|
|
randomizedTLSProfileSeed = config.RandomizedTLSProfileSeed
|
|
@@ -362,8 +430,8 @@ func CustomTLSDial(
|
|
|
// As noted here,
|
|
// As noted here,
|
|
|
// https://gitlab.com/yawning/obfs4/commit/ca6765e3e3995144df2b1ca9f0e9d823a7f8a47c,
|
|
// https://gitlab.com/yawning/obfs4/commit/ca6765e3e3995144df2b1ca9f0e9d823a7f8a47c,
|
|
|
// the dynamic record sizing optimization in crypto/tls is not commonly
|
|
// the dynamic record sizing optimization in crypto/tls is not commonly
|
|
|
- // implemented in browsers. Disable it for all non-Golang utls parrots and
|
|
|
|
|
- // select it randomly when using the randomized client hello.
|
|
|
|
|
|
|
+ // implemented in browsers. Disable it for all utls parrots and select it
|
|
|
|
|
+ // randomly when using the randomized client hello.
|
|
|
if isRandomized {
|
|
if isRandomized {
|
|
|
PRNG, err := prng.NewPRNGWithSaltedSeed(randomizedTLSProfileSeed, "tls-dynamic-record-sizing")
|
|
PRNG, err := prng.NewPRNGWithSaltedSeed(randomizedTLSProfileSeed, "tls-dynamic-record-sizing")
|
|
|
if err != nil {
|
|
if err != nil {
|
|
@@ -371,11 +439,18 @@ func CustomTLSDial(
|
|
|
}
|
|
}
|
|
|
tlsConfig.DynamicRecordSizingDisabled = PRNG.FlipCoin()
|
|
tlsConfig.DynamicRecordSizingDisabled = PRNG.FlipCoin()
|
|
|
} else {
|
|
} else {
|
|
|
- tlsConfig.DynamicRecordSizingDisabled = (utlsClientHelloID != utls.HelloGolang)
|
|
|
|
|
|
|
+ tlsConfig.DynamicRecordSizingDisabled = true
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
conn := utls.UClient(rawConn, tlsConfig, utlsClientHelloID)
|
|
conn := utls.UClient(rawConn, tlsConfig, utlsClientHelloID)
|
|
|
|
|
|
|
|
|
|
+ if utlsClientHelloSpec != nil {
|
|
|
|
|
+ err = conn.ApplyPreset(utlsClientHelloSpec)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return nil, common.ContextError(err)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
clientSessionCache := config.clientSessionCache
|
|
clientSessionCache := config.clientSessionCache
|
|
|
if clientSessionCache == nil {
|
|
if clientSessionCache == nil {
|
|
|
clientSessionCache = utls.NewLRUClientSessionCache(0)
|
|
clientSessionCache = utls.NewLRUClientSessionCache(0)
|
|
@@ -383,20 +458,25 @@ func CustomTLSDial(
|
|
|
|
|
|
|
|
conn.SetSessionCache(clientSessionCache)
|
|
conn.SetSessionCache(clientSessionCache)
|
|
|
|
|
|
|
|
- // Obfuscated session tickets are not currently supported in TLS 1.3, but we
|
|
|
|
|
- // allow UNFRONTED-MEEK-SESSION-TICKET-OSSH to use TLS 1.3 profiles for
|
|
|
|
|
- // additional diversity/capacity; TLS 1.3 encrypts the server certificate,
|
|
|
|
|
- // so the desired obfuscated session tickets property of obfuscating server
|
|
|
|
|
- // certificates is satisfied. We know that when the ClientHello offers TLS
|
|
|
|
|
- // 1.3, the Psiphon server, in these direct protocol cases, will negotiate
|
|
|
|
|
- // it.
|
|
|
|
|
if config.ObfuscatedSessionTicketKey != "" {
|
|
if config.ObfuscatedSessionTicketKey != "" {
|
|
|
|
|
|
|
|
- tlsVersion, err := getClientHelloVersion(utlsClientHelloID)
|
|
|
|
|
|
|
+ // Since getClientHelloVersion may incur some overhead, only invoke it when
|
|
|
|
|
+ // necessary.
|
|
|
|
|
+ tlsVersion, err := getClientHelloVersion(utlsClientHelloID, utlsClientHelloSpec)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
return nil, common.ContextError(err)
|
|
return nil, common.ContextError(err)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // Add the obfuscated session ticket only when using TLS 1.2.
|
|
|
|
|
+ //
|
|
|
|
|
+ // Obfuscated session tickets are not currently supported in TLS 1.3, but we
|
|
|
|
|
+ // allow UNFRONTED-MEEK-SESSION-TICKET-OSSH to use TLS 1.3 profiles for
|
|
|
|
|
+ // additional diversity/capacity; TLS 1.3 encrypts the server certificate,
|
|
|
|
|
+ // so the desired obfuscated session tickets property of obfuscating server
|
|
|
|
|
+ // certificates is satisfied. We know that when the ClientHello offers TLS
|
|
|
|
|
+ // 1.3, the Psiphon server, in these direct protocol cases, will negotiate
|
|
|
|
|
+ // it.
|
|
|
|
|
+
|
|
|
if tlsVersion == protocol.TLS_VERSION_12 {
|
|
if tlsVersion == protocol.TLS_VERSION_12 {
|
|
|
|
|
|
|
|
var obfuscatedSessionTicketKey [32]byte
|
|
var obfuscatedSessionTicketKey [32]byte
|
|
@@ -448,6 +528,39 @@ func CustomTLSDial(
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // Build the ClientHello and inspect to apply NoDefaultSessionID logic.
|
|
|
|
|
+
|
|
|
|
|
+ if len(conn.HandshakeState.Hello.SessionTicket) == 0 &&
|
|
|
|
|
+ isNoDefaultSessionIDCandidate(utlsClientHelloID) {
|
|
|
|
|
+
|
|
|
|
|
+ if !conn.ClientHelloBuilt {
|
|
|
|
|
+ err = conn.BuildHandshakeState()
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return nil, common.ContextError(err)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var noDefaultSessionID bool
|
|
|
|
|
+ if config.NoDefaultTLSSessionID != nil {
|
|
|
|
|
+ noDefaultSessionID = *config.NoDefaultTLSSessionID
|
|
|
|
|
+ } else {
|
|
|
|
|
+ noDefaultSessionID = config.ClientParameters.Get().WeightedCoinFlip(
|
|
|
|
|
+ parameters.NoDefaultTLSSessionIDProbability)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if noDefaultSessionID {
|
|
|
|
|
+
|
|
|
|
|
+ conn.HandshakeState.Hello.SessionId = nil
|
|
|
|
|
+
|
|
|
|
|
+ err = conn.MarshalClientHello()
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return nil, common.ContextError(err)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Perform the TLS Handshake.
|
|
|
|
|
+
|
|
|
resultChannel := make(chan error)
|
|
resultChannel := make(chan error)
|
|
|
|
|
|
|
|
go func() {
|
|
go func() {
|