|
|
@@ -47,20 +47,20 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
*/
|
|
|
|
|
|
-// Based on https://github.com/getlantern/tlsdialer (http://gopkg.in/getlantern/tlsdialer.v1)
|
|
|
-// which itself is a "Fork of crypto/tls.Dial and DialWithDialer"
|
|
|
+// Originally based on https://gopkg.in/getlantern/tlsdialer.v1.
|
|
|
|
|
|
package psiphon
|
|
|
|
|
|
import (
|
|
|
"bytes"
|
|
|
"context"
|
|
|
+ "crypto/sha256"
|
|
|
"crypto/x509"
|
|
|
+ "encoding/base64"
|
|
|
"encoding/hex"
|
|
|
std_errors "errors"
|
|
|
"io/ioutil"
|
|
|
"net"
|
|
|
- "time"
|
|
|
|
|
|
"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
|
|
|
"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
|
|
|
@@ -71,16 +71,16 @@ import (
|
|
|
utls "github.com/refraction-networking/utls"
|
|
|
)
|
|
|
|
|
|
-// CustomTLSConfig contains parameters to determine the behavior
|
|
|
-// of CustomTLSDial.
|
|
|
+// CustomTLSConfig specifies the parameters for a CustomTLSDial, supporting
|
|
|
+// many TLS-related network obfuscation mechanisms.
|
|
|
type CustomTLSConfig struct {
|
|
|
|
|
|
// Parameters is the active set of parameters.Parameters to use for the TLS
|
|
|
- // dial.
|
|
|
+ // dial. Must not be nil.
|
|
|
Parameters *parameters.Parameters
|
|
|
|
|
|
- // Dial is the network connection dialer. TLS is layered on
|
|
|
- // top of a new network connection created with dialer.
|
|
|
+ // Dial is the network connection dialer. TLS is layered on top of a new
|
|
|
+ // network connection created with dialer. Must not be nil.
|
|
|
Dial common.Dialer
|
|
|
|
|
|
// DialAddr overrides the "addr" input to Dial when specified
|
|
|
@@ -98,15 +98,35 @@ type CustomTLSConfig struct {
|
|
|
// SNIServerName is ignored when UseDialAddrSNI is true.
|
|
|
SNIServerName string
|
|
|
|
|
|
- // SkipVerify completely disables server certificate verification.
|
|
|
- SkipVerify bool
|
|
|
+ // VerifyServerName specifies a domain name that must appear in the server
|
|
|
+ // certificate. When specified, certificate verification checks for
|
|
|
+ // VerifyServerName in the server certificate, in place of the dial or SNI
|
|
|
+ // hostname.
|
|
|
+ VerifyServerName string
|
|
|
+
|
|
|
+ // VerifyPins specifies one or more certificate pin values, one of which must
|
|
|
+ // appear in the verified server certificate chain. A pin value is the
|
|
|
+ // base64-encoded SHA2 digest of a certificate's public key. When specified,
|
|
|
+ // at least one pin must match at least one certificate in the chain, at any
|
|
|
+ // position; e.g., the root CA may be pinned, or the server certificate,
|
|
|
+ // etc.
|
|
|
+ VerifyPins []string
|
|
|
|
|
|
// VerifyLegacyCertificate is a special case self-signed server
|
|
|
// certificate case. Ignores IP SANs and basic constraints. No
|
|
|
// certificate chain. Just checks that the server presented the
|
|
|
- // specified certificate. SNI is disbled when this is set.
|
|
|
+ // specified certificate.
|
|
|
+ //
|
|
|
+ // When VerifyLegacyCertificate is set, none of VerifyServerName, VerifyPins,
|
|
|
+ // SkipVerify may be set.
|
|
|
VerifyLegacyCertificate *x509.Certificate
|
|
|
|
|
|
+ // SkipVerify completely disables server certificate verification.
|
|
|
+ //
|
|
|
+ // When SkipVerify is set, none of VerifyServerName, VerifyPins,
|
|
|
+ // VerifyLegacyCertificate may be set.
|
|
|
+ SkipVerify bool
|
|
|
+
|
|
|
// TLSProfile specifies a particular indistinguishable TLS profile to use for
|
|
|
// the TLS dial. Setting TLSProfile allows the caller to pin the selection so
|
|
|
// all TLS connections in a certain context (e.g. a single meek connection)
|
|
|
@@ -159,209 +179,6 @@ func (config *CustomTLSConfig) EnableClientSessionCache() {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-// SelectTLSProfile picks a TLS profile at random from the available candidates.
|
|
|
-func SelectTLSProfile(
|
|
|
- requireTLS12SessionTickets bool,
|
|
|
- isFronted bool,
|
|
|
- frontingProviderID string,
|
|
|
- p parameters.ParametersAccessor) string {
|
|
|
-
|
|
|
- // 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.
|
|
|
- //
|
|
|
- // UseOnlyCustomTLSProfiles may be used to disable all stock TLS profiles and
|
|
|
- // use only CustomTLSProfiles; UseOnlyCustomTLSProfiles is ignored if
|
|
|
- // CustomTLSProfiles is empty.
|
|
|
- //
|
|
|
- // For fronted servers, DisableFrontingProviderTLSProfiles may be used
|
|
|
- // to disable TLS profiles which are incompatible with the TLS stack used
|
|
|
- // by the front. For example, if a utls parrot doesn't fully support all
|
|
|
- // of the capabilities in the ClientHello. Unlike the LimitTLSProfiles case,
|
|
|
- // DisableFrontingProviderTLSProfiles may disable CustomTLSProfiles.
|
|
|
-
|
|
|
- limitTLSProfiles := p.TLSProfiles(parameters.LimitTLSProfiles)
|
|
|
- var disableTLSProfiles protocol.TLSProfiles
|
|
|
-
|
|
|
- if isFronted && frontingProviderID != "" {
|
|
|
- disableTLSProfiles = p.LabeledTLSProfiles(
|
|
|
- parameters.DisableFrontingProviderTLSProfiles, frontingProviderID)
|
|
|
- }
|
|
|
-
|
|
|
- randomizedTLSProfiles := make([]string, 0)
|
|
|
- parrotTLSProfiles := make([]string, 0)
|
|
|
-
|
|
|
- for _, tlsProfile := range p.CustomTLSProfileNames() {
|
|
|
- if !common.Contains(disableTLSProfiles, tlsProfile) {
|
|
|
- parrotTLSProfiles = append(parrotTLSProfiles, tlsProfile)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- useOnlyCustomTLSProfiles := p.Bool(parameters.UseOnlyCustomTLSProfiles)
|
|
|
- if useOnlyCustomTLSProfiles && len(parrotTLSProfiles) == 0 {
|
|
|
- useOnlyCustomTLSProfiles = false
|
|
|
- }
|
|
|
-
|
|
|
- if !useOnlyCustomTLSProfiles {
|
|
|
- for _, tlsProfile := range protocol.SupportedTLSProfiles {
|
|
|
-
|
|
|
- if len(limitTLSProfiles) > 0 &&
|
|
|
- !common.Contains(limitTLSProfiles, tlsProfile) {
|
|
|
- continue
|
|
|
- }
|
|
|
-
|
|
|
- if common.Contains(disableTLSProfiles, tlsProfile) {
|
|
|
- continue
|
|
|
- }
|
|
|
-
|
|
|
- // requireTLS12SessionTickets is specified for
|
|
|
- // UNFRONTED-MEEK-SESSION-TICKET-OSSH, a protocol which depends on using
|
|
|
- // obfuscated session tickets to ensure that the server doesn't send its
|
|
|
- // certificate in the TLS handshake. TLS 1.2 profiles which omit session
|
|
|
- // tickets should not be selected. As TLS 1.3 encrypts the server
|
|
|
- // certificate message, there's no exclusion for TLS 1.3.
|
|
|
-
|
|
|
- if requireTLS12SessionTickets &&
|
|
|
- protocol.TLS12ProfileOmitsSessionTickets(tlsProfile) {
|
|
|
- continue
|
|
|
- }
|
|
|
-
|
|
|
- if protocol.TLSProfileIsRandomized(tlsProfile) {
|
|
|
- randomizedTLSProfiles = append(randomizedTLSProfiles, tlsProfile)
|
|
|
- } else {
|
|
|
- parrotTLSProfiles = append(parrotTLSProfiles, tlsProfile)
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if len(randomizedTLSProfiles) > 0 &&
|
|
|
- (len(parrotTLSProfiles) == 0 ||
|
|
|
- p.WeightedCoinFlip(parameters.SelectRandomizedTLSProfileProbability)) {
|
|
|
-
|
|
|
- return randomizedTLSProfiles[prng.Intn(len(randomizedTLSProfiles))]
|
|
|
- }
|
|
|
-
|
|
|
- if len(parrotTLSProfiles) == 0 {
|
|
|
- return ""
|
|
|
- }
|
|
|
-
|
|
|
- return parrotTLSProfiles[prng.Intn(len(parrotTLSProfiles))]
|
|
|
-}
|
|
|
-
|
|
|
-func getUTLSClientHelloID(
|
|
|
- p parameters.ParametersAccessor,
|
|
|
- tlsProfile string) (utls.ClientHelloID, *utls.ClientHelloSpec, error) {
|
|
|
-
|
|
|
- switch tlsProfile {
|
|
|
- case protocol.TLS_PROFILE_IOS_111:
|
|
|
- return utls.HelloIOS_11_1, nil, nil
|
|
|
- case protocol.TLS_PROFILE_IOS_121:
|
|
|
- return utls.HelloIOS_12_1, nil, nil
|
|
|
- case protocol.TLS_PROFILE_CHROME_58:
|
|
|
- return utls.HelloChrome_58, nil, nil
|
|
|
- case protocol.TLS_PROFILE_CHROME_62:
|
|
|
- return utls.HelloChrome_62, nil, nil
|
|
|
- case protocol.TLS_PROFILE_CHROME_70:
|
|
|
- return utls.HelloChrome_70, nil, nil
|
|
|
- case protocol.TLS_PROFILE_CHROME_72:
|
|
|
- return utls.HelloChrome_72, nil, nil
|
|
|
- case protocol.TLS_PROFILE_CHROME_83:
|
|
|
- return utls.HelloChrome_83, nil, nil
|
|
|
- case protocol.TLS_PROFILE_FIREFOX_55:
|
|
|
- return utls.HelloFirefox_55, nil, nil
|
|
|
- case protocol.TLS_PROFILE_FIREFOX_56:
|
|
|
- return utls.HelloFirefox_56, nil, nil
|
|
|
- case protocol.TLS_PROFILE_FIREFOX_65:
|
|
|
- return utls.HelloFirefox_65, nil, nil
|
|
|
- case protocol.TLS_PROFILE_RANDOMIZED:
|
|
|
- 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,
|
|
|
- errors.Tracef("unknown TLS profile: %s", tlsProfile)
|
|
|
- }
|
|
|
-
|
|
|
- utlsClientHelloSpec, err := customTLSProfile.GetClientHelloSpec()
|
|
|
- if err != nil {
|
|
|
- return utls.ClientHelloID{}, nil, errors.Trace(err)
|
|
|
- }
|
|
|
-
|
|
|
- return utls.HelloCustom, utlsClientHelloSpec, nil
|
|
|
-}
|
|
|
-
|
|
|
-func getClientHelloVersion(
|
|
|
- utlsClientHelloID utls.ClientHelloID,
|
|
|
- utlsClientHelloSpec *utls.ClientHelloSpec) (string, error) {
|
|
|
-
|
|
|
- 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.HelloChrome_83,
|
|
|
- utls.HelloFirefox_65, utls.HelloGolang:
|
|
|
- return protocol.TLS_VERSION_13, nil
|
|
|
- }
|
|
|
-
|
|
|
- // As utls.HelloRandomized/Custom may be either TLS 1.2 or TLS 1.3, we cannot
|
|
|
- // perform a simple ClientHello ID check. BuildHandshakeState is run, which
|
|
|
- // constructs the entire ClientHello.
|
|
|
- //
|
|
|
- // Assumes utlsClientHelloID.Seed has been set; otherwise the result is
|
|
|
- // ephemeral.
|
|
|
- //
|
|
|
- // BenchmarkRandomizedGetClientHelloVersion indicates that this operation
|
|
|
- // takes on the order of 0.05ms and allocates ~8KB for randomized client
|
|
|
- // hellos.
|
|
|
-
|
|
|
- conn := utls.UClient(
|
|
|
- nil,
|
|
|
- &utls.Config{InsecureSkipVerify: true},
|
|
|
- utlsClientHelloID)
|
|
|
-
|
|
|
- if utlsClientHelloSpec != nil {
|
|
|
- err := conn.ApplyPreset(utlsClientHelloSpec)
|
|
|
- if err != nil {
|
|
|
- return "", errors.Trace(err)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- err := conn.BuildHandshakeState()
|
|
|
- if err != nil {
|
|
|
- return "", errors.Trace(err)
|
|
|
- }
|
|
|
-
|
|
|
- for _, v := range conn.HandshakeState.Hello.SupportedVersions {
|
|
|
- if v == utls.VersionTLS13 {
|
|
|
- return protocol.TLS_VERSION_13, nil
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return protocol.TLS_VERSION_12, nil
|
|
|
-}
|
|
|
-
|
|
|
-func IsTLSConnUsingHTTP2(conn net.Conn) bool {
|
|
|
- if c, ok := conn.(*utls.UConn); ok {
|
|
|
- state := c.ConnectionState()
|
|
|
- return state.NegotiatedProtocolIsMutual &&
|
|
|
- state.NegotiatedProtocol == "h2"
|
|
|
- }
|
|
|
- return false
|
|
|
-}
|
|
|
-
|
|
|
// NewCustomTLSDialer creates a new dialer based on CustomTLSDial.
|
|
|
func NewCustomTLSDialer(config *CustomTLSConfig) common.Dialer {
|
|
|
return func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
|
@@ -369,21 +186,28 @@ func NewCustomTLSDialer(config *CustomTLSConfig) common.Dialer {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-// CustomTLSDial is a customized replacement for tls.Dial.
|
|
|
-// Based on tlsdialer.DialWithDialer which is based on crypto/tls.DialWithDialer.
|
|
|
-//
|
|
|
-// To ensure optimal TLS profile selection when using CustomTLSDial for tunnel
|
|
|
-// protocols, call SelectTLSProfile first and set its result into
|
|
|
-// config.TLSProfile.
|
|
|
+// CustomTLSDial dials a new TLS connection using the parameters set in
|
|
|
+// CustomTLSConfig.
|
|
|
//
|
|
|
-// tlsdialer comment:
|
|
|
-// Note - if sendServerName is false, the VerifiedChains field on the
|
|
|
-// connection's ConnectionState will never get populated.
|
|
|
+// The dial aborts if ctx becomes Done before the dial completes.
|
|
|
func CustomTLSDial(
|
|
|
ctx context.Context,
|
|
|
network, addr string,
|
|
|
config *CustomTLSConfig) (net.Conn, error) {
|
|
|
|
|
|
+ if (config.SkipVerify &&
|
|
|
+ (config.VerifyLegacyCertificate != nil ||
|
|
|
+ len(config.VerifyServerName) > 0 ||
|
|
|
+ len(config.VerifyPins) > 0)) ||
|
|
|
+
|
|
|
+ (config.VerifyLegacyCertificate != nil &&
|
|
|
+ (config.SkipVerify ||
|
|
|
+ len(config.VerifyServerName) > 0 ||
|
|
|
+ len(config.VerifyPins) > 0)) {
|
|
|
+
|
|
|
+ return nil, errors.TraceNew("incompatible certification verification parameters")
|
|
|
+ }
|
|
|
+
|
|
|
p := config.Parameters.Get()
|
|
|
|
|
|
dialAddr := addr
|
|
|
@@ -402,51 +226,116 @@ func CustomTLSDial(
|
|
|
return nil, errors.Trace(err)
|
|
|
}
|
|
|
|
|
|
- selectedTLSProfile := config.TLSProfile
|
|
|
+ var tlsConfigRootCAs *x509.CertPool
|
|
|
+ if !config.SkipVerify &&
|
|
|
+ config.VerifyLegacyCertificate == nil &&
|
|
|
+ config.TrustedCACertificatesFilename != "" {
|
|
|
|
|
|
- if selectedTLSProfile == "" {
|
|
|
- selectedTLSProfile = SelectTLSProfile(false, false, "", p)
|
|
|
+ tlsConfigRootCAs = x509.NewCertPool()
|
|
|
+ certData, err := ioutil.ReadFile(config.TrustedCACertificatesFilename)
|
|
|
+ if err != nil {
|
|
|
+ return nil, errors.Trace(err)
|
|
|
+ }
|
|
|
+ tlsConfigRootCAs.AppendCertsFromPEM(certData)
|
|
|
}
|
|
|
|
|
|
+ // In some cases, config.SkipVerify is false, but
|
|
|
+ // utls.Config.InsecureSkipVerify will be set to true to disable verification
|
|
|
+ // in utls that will otherwise fail: when SNI is omitted, and when
|
|
|
+ // VerifyServerName differs from SNI. In these cases, the certificate chain
|
|
|
+ // is verified in VerifyPeerCertificate.
|
|
|
+
|
|
|
tlsConfigInsecureSkipVerify := false
|
|
|
tlsConfigServerName := ""
|
|
|
+ verifyServerName := hostname
|
|
|
|
|
|
if config.SkipVerify {
|
|
|
tlsConfigInsecureSkipVerify = true
|
|
|
}
|
|
|
|
|
|
if config.UseDialAddrSNI {
|
|
|
+
|
|
|
+ // Set SNI to match the dial hostname. This is the standard case.
|
|
|
tlsConfigServerName = hostname
|
|
|
- } else if config.SNIServerName != "" && config.VerifyLegacyCertificate == nil {
|
|
|
- // Set the ServerName and rely on the usual logic in
|
|
|
- // tls.Conn.Handshake() to do its verification.
|
|
|
- // Note: Go TLS will automatically omit this ServerName when it's an IP address
|
|
|
+
|
|
|
+ } else if config.SNIServerName != "" {
|
|
|
+
|
|
|
+ // Set a custom SNI value. If this value doesn't match the server
|
|
|
+ // certificate, SkipVerify and/or VerifyServerName may need to be
|
|
|
+ // configured; but by itself this case doesn't necessarily require
|
|
|
+ // custom certificate verification.
|
|
|
tlsConfigServerName = config.SNIServerName
|
|
|
+
|
|
|
} else {
|
|
|
- // No SNI.
|
|
|
- // Disable verification in tls.Conn.Handshake(). We'll verify manually
|
|
|
- // after handshaking
|
|
|
+
|
|
|
+ // Omit SNI. If SkipVerify is not set, this case requires custom certificate
|
|
|
+ // verification, which will check that the server certificate matches either
|
|
|
+ // the dial hostname or VerifyServerName, as if the SNI were set to one of
|
|
|
+ // those values.
|
|
|
tlsConfigInsecureSkipVerify = true
|
|
|
}
|
|
|
|
|
|
- var tlsRootCAs *x509.CertPool
|
|
|
+ // When VerifyServerName does not match the SNI, custom certificate
|
|
|
+ // verification is necessary.
|
|
|
+ if config.VerifyServerName != "" && config.VerifyServerName != tlsConfigServerName {
|
|
|
+ verifyServerName = config.VerifyServerName
|
|
|
+ tlsConfigInsecureSkipVerify = true
|
|
|
+ }
|
|
|
|
|
|
- if !config.SkipVerify &&
|
|
|
- config.VerifyLegacyCertificate == nil &&
|
|
|
- config.TrustedCACertificatesFilename != "" {
|
|
|
+ // With the VerifyPeerCertificate callback, we perform any custom certificate
|
|
|
+ // verification at the same point in the TLS handshake as standard utls
|
|
|
+ // verification; and abort the handshake at the same point, if custom
|
|
|
+ // verification fails.
|
|
|
+ var tlsConfigVerifyPeerCertificate func([][]byte, [][]*x509.Certificate) error
|
|
|
+ if !config.SkipVerify {
|
|
|
+ tlsConfigVerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
|
|
|
|
|
- tlsRootCAs = x509.NewCertPool()
|
|
|
- certData, err := ioutil.ReadFile(config.TrustedCACertificatesFilename)
|
|
|
- if err != nil {
|
|
|
- return nil, errors.Trace(err)
|
|
|
+ if config.VerifyLegacyCertificate != nil {
|
|
|
+ return verifyLegacyCertificate(
|
|
|
+ rawCerts, config.VerifyLegacyCertificate)
|
|
|
+ }
|
|
|
+
|
|
|
+ if tlsConfigInsecureSkipVerify {
|
|
|
+
|
|
|
+ // Limitation: this verification path does not set the utls.Conn's
|
|
|
+ // ConnectionState certificate information.
|
|
|
+
|
|
|
+ if len(verifiedChains) > 0 {
|
|
|
+ return errors.TraceNew("unexpected verified chains")
|
|
|
+ }
|
|
|
+ var err error
|
|
|
+ verifiedChains, err = verifyServerCertificate(
|
|
|
+ tlsConfigRootCAs, rawCerts, verifyServerName)
|
|
|
+ if err != nil {
|
|
|
+ return errors.Trace(err)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if len(config.VerifyPins) > 0 {
|
|
|
+ err := verifyCertificatePins(
|
|
|
+ config.VerifyPins, verifiedChains)
|
|
|
+ if err != nil {
|
|
|
+ return errors.Trace(err)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil
|
|
|
}
|
|
|
- tlsRootCAs.AppendCertsFromPEM(certData)
|
|
|
}
|
|
|
|
|
|
+ // Note: utls will automatically omit SNI when ServerName is an IP address.
|
|
|
+
|
|
|
tlsConfig := &utls.Config{
|
|
|
- RootCAs: tlsRootCAs,
|
|
|
- InsecureSkipVerify: tlsConfigInsecureSkipVerify,
|
|
|
- ServerName: tlsConfigServerName,
|
|
|
+ RootCAs: tlsConfigRootCAs,
|
|
|
+ InsecureSkipVerify: tlsConfigInsecureSkipVerify,
|
|
|
+ ServerName: tlsConfigServerName,
|
|
|
+ VerifyPeerCertificate: tlsConfigVerifyPeerCertificate,
|
|
|
+ }
|
|
|
+
|
|
|
+ selectedTLSProfile := config.TLSProfile
|
|
|
+
|
|
|
+ if selectedTLSProfile == "" {
|
|
|
+ selectedTLSProfile = SelectTLSProfile(false, false, "", p)
|
|
|
}
|
|
|
|
|
|
utlsClientHelloID, utlsClientHelloSpec, err := getUTLSClientHelloID(
|
|
|
@@ -697,16 +586,6 @@ func CustomTLSDial(
|
|
|
<-resultChannel
|
|
|
}
|
|
|
|
|
|
- if err == nil && !config.SkipVerify && tlsConfigInsecureSkipVerify {
|
|
|
-
|
|
|
- if config.VerifyLegacyCertificate != nil {
|
|
|
- err = verifyLegacyCertificate(conn, config.VerifyLegacyCertificate)
|
|
|
- } else {
|
|
|
- // Manually verify certificates
|
|
|
- err = verifyServerCerts(conn, hostname)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
if err != nil {
|
|
|
rawConn.Close()
|
|
|
return nil, errors.Trace(err)
|
|
|
@@ -715,24 +594,33 @@ func CustomTLSDial(
|
|
|
return conn, nil
|
|
|
}
|
|
|
|
|
|
-func verifyLegacyCertificate(conn *utls.UConn, expectedCertificate *x509.Certificate) error {
|
|
|
- certs := conn.ConnectionState().PeerCertificates
|
|
|
- if len(certs) < 1 {
|
|
|
- return errors.TraceNew("no certificate to verify")
|
|
|
+func verifyLegacyCertificate(rawCerts [][]byte, expectedCertificate *x509.Certificate) error {
|
|
|
+ if len(rawCerts) < 1 {
|
|
|
+ return errors.TraceNew("missing certificate")
|
|
|
}
|
|
|
- if !bytes.Equal(certs[0].Raw, expectedCertificate.Raw) {
|
|
|
+ if !bytes.Equal(rawCerts[0], expectedCertificate.Raw) {
|
|
|
return errors.TraceNew("unexpected certificate")
|
|
|
}
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-func verifyServerCerts(conn *utls.UConn, hostname string) error {
|
|
|
- certs := conn.ConnectionState().PeerCertificates
|
|
|
+func verifyServerCertificate(
|
|
|
+ rootCAs *x509.CertPool, rawCerts [][]byte, verifyServerName string) ([][]*x509.Certificate, error) {
|
|
|
+
|
|
|
+ // This duplicates the verification logic in utls (and standard crypto/tls).
|
|
|
+
|
|
|
+ certs := make([]*x509.Certificate, len(rawCerts))
|
|
|
+ for i, rawCert := range rawCerts {
|
|
|
+ cert, err := x509.ParseCertificate(rawCert)
|
|
|
+ if err != nil {
|
|
|
+ return nil, errors.Trace(err)
|
|
|
+ }
|
|
|
+ certs[i] = cert
|
|
|
+ }
|
|
|
|
|
|
opts := x509.VerifyOptions{
|
|
|
- Roots: nil, // Use host's root CAs
|
|
|
- CurrentTime: time.Now(),
|
|
|
- DNSName: hostname,
|
|
|
+ Roots: rootCAs,
|
|
|
+ DNSName: verifyServerName,
|
|
|
Intermediates: x509.NewCertPool(),
|
|
|
}
|
|
|
|
|
|
@@ -743,11 +631,230 @@ func verifyServerCerts(conn *utls.UConn, hostname string) error {
|
|
|
opts.Intermediates.AddCert(cert)
|
|
|
}
|
|
|
|
|
|
- _, err := certs[0].Verify(opts)
|
|
|
+ verifiedChains, err := certs[0].Verify(opts)
|
|
|
if err != nil {
|
|
|
- return errors.Trace(err)
|
|
|
+ return nil, errors.Trace(err)
|
|
|
}
|
|
|
- return nil
|
|
|
+
|
|
|
+ return verifiedChains, nil
|
|
|
+}
|
|
|
+
|
|
|
+func verifyCertificatePins(pins []string, verifiedChains [][]*x509.Certificate) error {
|
|
|
+ for _, chain := range verifiedChains {
|
|
|
+ for _, cert := range chain {
|
|
|
+ publicKeyDigest := sha256.Sum256(cert.RawSubjectPublicKeyInfo)
|
|
|
+ expectedPin := base64.StdEncoding.EncodeToString(publicKeyDigest[:])
|
|
|
+ if common.Contains(pins, expectedPin) {
|
|
|
+ // Return success on the first match of any certificate public key to any
|
|
|
+ // pin.
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return errors.TraceNew("no pin found")
|
|
|
+}
|
|
|
+
|
|
|
+func IsTLSConnUsingHTTP2(conn net.Conn) bool {
|
|
|
+ if c, ok := conn.(*utls.UConn); ok {
|
|
|
+ state := c.ConnectionState()
|
|
|
+ return state.NegotiatedProtocolIsMutual &&
|
|
|
+ state.NegotiatedProtocol == "h2"
|
|
|
+ }
|
|
|
+ return false
|
|
|
+}
|
|
|
+
|
|
|
+// SelectTLSProfile picks a TLS profile at random from the available candidates.
|
|
|
+func SelectTLSProfile(
|
|
|
+ requireTLS12SessionTickets bool,
|
|
|
+ isFronted bool,
|
|
|
+ frontingProviderID string,
|
|
|
+ p parameters.ParametersAccessor) string {
|
|
|
+
|
|
|
+ // 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.
|
|
|
+ //
|
|
|
+ // UseOnlyCustomTLSProfiles may be used to disable all stock TLS profiles and
|
|
|
+ // use only CustomTLSProfiles; UseOnlyCustomTLSProfiles is ignored if
|
|
|
+ // CustomTLSProfiles is empty.
|
|
|
+ //
|
|
|
+ // For fronted servers, DisableFrontingProviderTLSProfiles may be used
|
|
|
+ // to disable TLS profiles which are incompatible with the TLS stack used
|
|
|
+ // by the front. For example, if a utls parrot doesn't fully support all
|
|
|
+ // of the capabilities in the ClientHello. Unlike the LimitTLSProfiles case,
|
|
|
+ // DisableFrontingProviderTLSProfiles may disable CustomTLSProfiles.
|
|
|
+
|
|
|
+ limitTLSProfiles := p.TLSProfiles(parameters.LimitTLSProfiles)
|
|
|
+ var disableTLSProfiles protocol.TLSProfiles
|
|
|
+
|
|
|
+ if isFronted && frontingProviderID != "" {
|
|
|
+ disableTLSProfiles = p.LabeledTLSProfiles(
|
|
|
+ parameters.DisableFrontingProviderTLSProfiles, frontingProviderID)
|
|
|
+ }
|
|
|
+
|
|
|
+ randomizedTLSProfiles := make([]string, 0)
|
|
|
+ parrotTLSProfiles := make([]string, 0)
|
|
|
+
|
|
|
+ for _, tlsProfile := range p.CustomTLSProfileNames() {
|
|
|
+ if !common.Contains(disableTLSProfiles, tlsProfile) {
|
|
|
+ parrotTLSProfiles = append(parrotTLSProfiles, tlsProfile)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ useOnlyCustomTLSProfiles := p.Bool(parameters.UseOnlyCustomTLSProfiles)
|
|
|
+ if useOnlyCustomTLSProfiles && len(parrotTLSProfiles) == 0 {
|
|
|
+ useOnlyCustomTLSProfiles = false
|
|
|
+ }
|
|
|
+
|
|
|
+ if !useOnlyCustomTLSProfiles {
|
|
|
+ for _, tlsProfile := range protocol.SupportedTLSProfiles {
|
|
|
+
|
|
|
+ if len(limitTLSProfiles) > 0 &&
|
|
|
+ !common.Contains(limitTLSProfiles, tlsProfile) {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ if common.Contains(disableTLSProfiles, tlsProfile) {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ // requireTLS12SessionTickets is specified for
|
|
|
+ // UNFRONTED-MEEK-SESSION-TICKET-OSSH, a protocol which depends on using
|
|
|
+ // obfuscated session tickets to ensure that the server doesn't send its
|
|
|
+ // certificate in the TLS handshake. TLS 1.2 profiles which omit session
|
|
|
+ // tickets should not be selected. As TLS 1.3 encrypts the server
|
|
|
+ // certificate message, there's no exclusion for TLS 1.3.
|
|
|
+
|
|
|
+ if requireTLS12SessionTickets &&
|
|
|
+ protocol.TLS12ProfileOmitsSessionTickets(tlsProfile) {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ if protocol.TLSProfileIsRandomized(tlsProfile) {
|
|
|
+ randomizedTLSProfiles = append(randomizedTLSProfiles, tlsProfile)
|
|
|
+ } else {
|
|
|
+ parrotTLSProfiles = append(parrotTLSProfiles, tlsProfile)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if len(randomizedTLSProfiles) > 0 &&
|
|
|
+ (len(parrotTLSProfiles) == 0 ||
|
|
|
+ p.WeightedCoinFlip(parameters.SelectRandomizedTLSProfileProbability)) {
|
|
|
+
|
|
|
+ return randomizedTLSProfiles[prng.Intn(len(randomizedTLSProfiles))]
|
|
|
+ }
|
|
|
+
|
|
|
+ if len(parrotTLSProfiles) == 0 {
|
|
|
+ return ""
|
|
|
+ }
|
|
|
+
|
|
|
+ return parrotTLSProfiles[prng.Intn(len(parrotTLSProfiles))]
|
|
|
+}
|
|
|
+
|
|
|
+func getUTLSClientHelloID(
|
|
|
+ p parameters.ParametersAccessor,
|
|
|
+ tlsProfile string) (utls.ClientHelloID, *utls.ClientHelloSpec, error) {
|
|
|
+
|
|
|
+ switch tlsProfile {
|
|
|
+ case protocol.TLS_PROFILE_IOS_111:
|
|
|
+ return utls.HelloIOS_11_1, nil, nil
|
|
|
+ case protocol.TLS_PROFILE_IOS_121:
|
|
|
+ return utls.HelloIOS_12_1, nil, nil
|
|
|
+ case protocol.TLS_PROFILE_CHROME_58:
|
|
|
+ return utls.HelloChrome_58, nil, nil
|
|
|
+ case protocol.TLS_PROFILE_CHROME_62:
|
|
|
+ return utls.HelloChrome_62, nil, nil
|
|
|
+ case protocol.TLS_PROFILE_CHROME_70:
|
|
|
+ return utls.HelloChrome_70, nil, nil
|
|
|
+ case protocol.TLS_PROFILE_CHROME_72:
|
|
|
+ return utls.HelloChrome_72, nil, nil
|
|
|
+ case protocol.TLS_PROFILE_CHROME_83:
|
|
|
+ return utls.HelloChrome_83, nil, nil
|
|
|
+ case protocol.TLS_PROFILE_FIREFOX_55:
|
|
|
+ return utls.HelloFirefox_55, nil, nil
|
|
|
+ case protocol.TLS_PROFILE_FIREFOX_56:
|
|
|
+ return utls.HelloFirefox_56, nil, nil
|
|
|
+ case protocol.TLS_PROFILE_FIREFOX_65:
|
|
|
+ return utls.HelloFirefox_65, nil, nil
|
|
|
+ case protocol.TLS_PROFILE_RANDOMIZED:
|
|
|
+ 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,
|
|
|
+ errors.Tracef("unknown TLS profile: %s", tlsProfile)
|
|
|
+ }
|
|
|
+
|
|
|
+ utlsClientHelloSpec, err := customTLSProfile.GetClientHelloSpec()
|
|
|
+ if err != nil {
|
|
|
+ return utls.ClientHelloID{}, nil, errors.Trace(err)
|
|
|
+ }
|
|
|
+
|
|
|
+ return utls.HelloCustom, utlsClientHelloSpec, nil
|
|
|
+}
|
|
|
+
|
|
|
+func getClientHelloVersion(
|
|
|
+ utlsClientHelloID utls.ClientHelloID,
|
|
|
+ utlsClientHelloSpec *utls.ClientHelloSpec) (string, error) {
|
|
|
+
|
|
|
+ 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.HelloChrome_83,
|
|
|
+ utls.HelloFirefox_65, utls.HelloGolang:
|
|
|
+ return protocol.TLS_VERSION_13, nil
|
|
|
+ }
|
|
|
+
|
|
|
+ // As utls.HelloRandomized/Custom may be either TLS 1.2 or TLS 1.3, we cannot
|
|
|
+ // perform a simple ClientHello ID check. BuildHandshakeState is run, which
|
|
|
+ // constructs the entire ClientHello.
|
|
|
+ //
|
|
|
+ // Assumes utlsClientHelloID.Seed has been set; otherwise the result is
|
|
|
+ // ephemeral.
|
|
|
+ //
|
|
|
+ // BenchmarkRandomizedGetClientHelloVersion indicates that this operation
|
|
|
+ // takes on the order of 0.05ms and allocates ~8KB for randomized client
|
|
|
+ // hellos.
|
|
|
+
|
|
|
+ conn := utls.UClient(
|
|
|
+ nil,
|
|
|
+ &utls.Config{InsecureSkipVerify: true},
|
|
|
+ utlsClientHelloID)
|
|
|
+
|
|
|
+ if utlsClientHelloSpec != nil {
|
|
|
+ err := conn.ApplyPreset(utlsClientHelloSpec)
|
|
|
+ if err != nil {
|
|
|
+ return "", errors.Trace(err)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ err := conn.BuildHandshakeState()
|
|
|
+ if err != nil {
|
|
|
+ return "", errors.Trace(err)
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, v := range conn.HandshakeState.Hello.SupportedVersions {
|
|
|
+ if v == utls.VersionTLS13 {
|
|
|
+ return protocol.TLS_VERSION_13, nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return protocol.TLS_VERSION_12, nil
|
|
|
}
|
|
|
|
|
|
func init() {
|