| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371 |
- /*
- * Copyright (c) 2015, Psiphon Inc.
- * All rights reserved.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
- /*
- Copyright (c) 2012 The Go Authors. All rights reserved.
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions are
- met:
- * Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above
- copyright notice, this list of conditions and the following disclaimer
- in the documentation and/or other materials provided with the
- distribution.
- * Neither the name of Google Inc. nor the names of its
- contributors may be used to endorse or promote products derived from
- this software without specific prior written permission.
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
- // Fork of https://github.com/getlantern/tlsdialer (http://gopkg.in/getlantern/tlsdialer.v1)
- // which itself is a "Fork of crypto/tls.Dial and DialWithDialer"
- // Adds two capabilities to tlsdialer:
- //
- // 1. HTTP proxy support, so the dialer may be used with http.Transport.
- //
- // 2. Support for self-signed Psiphon server certificates, which Go's certificate
- // verification rejects due to two short comings:
- // - lack of IP address SANs.
- // see: "...because it doesn't contain any IP SANs" case in crypto/x509/verify.go
- // - non-compliant constraint configuration (RFC 5280, 4.2.1.9).
- // see: CheckSignatureFrom() in crypto/x509/x509.go
- // Since the client has to be able to handle existing Psiphon server certificates,
- // we need to be able to perform some form of verification in these cases.
- // tlsdialer:
- // package tlsdialer contains a customized version of crypto/tls.Dial that
- // allows control over whether or not to send the ServerName extension in the
- // client handshake.
- package psiphon
- import (
- "bytes"
- "crypto/x509"
- "encoding/hex"
- "errors"
- "net"
- "time"
- "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
- "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/tls"
- )
- const (
- TLSProfileAndroid = "Android"
- TLSProfileChrome = "Chrome"
- )
- // CustomTLSConfig contains parameters to determine the behavior
- // of CustomTLSDial.
- type CustomTLSConfig struct {
- // Dial is the network connection dialer. TLS is layered on
- // top of a new network connection created with dialer.
- Dial Dialer
- // Timeout is and optional timeout for combined network
- // connection dial and TLS handshake.
- Timeout time.Duration
- // DialAddr overrides the "addr" input to Dial when specified
- DialAddr string
- // SNIServerName specifies the value to set in the SNI
- // server_name field. When blank, SNI is omitted. Note that
- // underlying TLS code also automatically omits SNI when
- // the server_name is an IP address.
- SNIServerName string
- // SkipVerify completely disables server certificate verification.
- SkipVerify bool
- // 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.
- VerifyLegacyCertificate *x509.Certificate
- // UseIndistinguishableTLS specifies whether to try to use an
- // alternative stack for TLS. From a circumvention perspective,
- // Go's TLS has a distinct fingerprint that may be used for blocking.
- UseIndistinguishableTLS bool
- // TLSProfile specifies a particular indistinguishable TLS profile
- // to use for the TLS dial. UseIndistinguishableTLS must be set for
- // TLSProfile to take effect.
- // When TLSProfile is "" and UseIndistinguishableTLS is set, a profile
- // is selected at random. Setting TLSProfile allows the caller to pin
- // the selection so all TLS connections in a certain context (e.g. a
- // single meek connection) use a consistent value.
- // Valid values include "Android" and "Chrome". The value should be
- // selected by calling SelectTLSProfile, which will pick a value at
- // random, but subject to compatibility constraints.
- TLSProfile string
- // TrustedCACertificatesFilename specifies a file containing trusted
- // CA certs. Directory contents should be compatible with OpenSSL's
- // SSL_CTX_load_verify_locations
- // Only applies to UseIndistinguishableTLS connections.
- TrustedCACertificatesFilename string
- // ObfuscatedSessionTicketKey enables obfuscated session tickets
- // using the specified key.
- ObfuscatedSessionTicketKey string
- }
- func SelectTLSProfile(
- useIndistinguishableTLS, useObfuscatedSessionTickets,
- skipVerify, haveTrustedCACertificates bool) string {
- selectedTLSProfile := ""
- if useIndistinguishableTLS {
- // OpenSSL cannot be used in all cases
- canUseOpenSSL := openSSLSupported() &&
- !useObfuscatedSessionTickets &&
- // TODO: (... || config.VerifyLegacyCertificate != nil)
- (skipVerify || haveTrustedCACertificates)
- if canUseOpenSSL && common.FlipCoin() {
- selectedTLSProfile = TLSProfileAndroid
- } else {
- selectedTLSProfile = TLSProfileChrome
- }
- }
- return selectedTLSProfile
- }
- func NewCustomTLSDialer(config *CustomTLSConfig) Dialer {
- return func(network, addr string) (net.Conn, error) {
- return CustomTLSDial(network, addr, config)
- }
- }
- // handshakeConn is a net.Conn that can perform a TLS handshake
- type handshakeConn interface {
- net.Conn
- Handshake() error
- }
- // CustomTLSDialWithDialer is a customized replacement for tls.Dial.
- // Based on tlsdialer.DialWithDialer which is based on crypto/tls.DialWithDialer.
- //
- // tlsdialer comment:
- // Note - if sendServerName is false, the VerifiedChains field on the
- // connection's ConnectionState will never get populated.
- func CustomTLSDial(network, addr string, config *CustomTLSConfig) (net.Conn, error) {
- // We want the Timeout and Deadline values from dialer to cover the
- // whole process: TCP connection and TLS handshake. This means that we
- // also need to start our own timers now.
- var errChannel chan error
- if config.Timeout != 0 {
- errChannel = make(chan error, 2)
- time.AfterFunc(config.Timeout, func() {
- errChannel <- errors.New("timed out")
- })
- }
- dialAddr := addr
- if config.DialAddr != "" {
- dialAddr = config.DialAddr
- }
- rawConn, err := config.Dial(network, dialAddr)
- if err != nil {
- return nil, common.ContextError(err)
- }
- hostname, _, err := net.SplitHostPort(dialAddr)
- if err != nil {
- rawConn.Close()
- return nil, common.ContextError(err)
- }
- tlsConfig := &tls.Config{}
- // Select indistinguishable TLS implementation
- useOpenSSL := false
- if config.UseIndistinguishableTLS {
- selectedTLSProfile := config.TLSProfile
- if selectedTLSProfile == "" {
- selectedTLSProfile = SelectTLSProfile(
- true,
- config.ObfuscatedSessionTicketKey != "",
- config.SkipVerify,
- config.TrustedCACertificatesFilename != "")
- }
- switch selectedTLSProfile {
- case TLSProfileAndroid:
- // Validate selection; if config.TLSProfile was preset, it should
- // have been selected using SelectTLSProfile.
- if !openSSLSupported() ||
- config.ObfuscatedSessionTicketKey != "" ||
- // TODO: (... || config.VerifyLegacyCertificate != nil)
- !(config.SkipVerify || config.TrustedCACertificatesFilename != "") {
- return nil, common.ContextError(errors.New("TLSProfileAndroid not supported"))
- }
- useOpenSSL = true
- case TLSProfileChrome:
- tlsConfig.EmulateChrome = true
- tlsConfig.ClientSessionCache = tls.NewLRUClientSessionCache(0)
- }
- }
- if config.SkipVerify {
- tlsConfig.InsecureSkipVerify = true
- }
- 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
- tlsConfig.ServerName = config.SNIServerName
- } else {
- // No SNI.
- // Disable verification in tls.Conn.Handshake(). We'll verify manually
- // after handshaking
- tlsConfig.InsecureSkipVerify = true
- }
- if config.ObfuscatedSessionTicketKey != "" {
- // See obfuscated session ticket overview
- // in tls.NewObfuscatedClientSessionCache
- var obfuscatedSessionTicketKey [32]byte
- key, err := hex.DecodeString(config.ObfuscatedSessionTicketKey)
- if err == nil && len(key) != 32 {
- err = errors.New("invalid obfuscated session key length")
- }
- if err != nil {
- return nil, common.ContextError(err)
- }
- copy(obfuscatedSessionTicketKey[:], key)
- tlsConfig.ClientSessionCache = tls.NewObfuscatedClientSessionCache(
- obfuscatedSessionTicketKey)
- }
- var conn handshakeConn
- // When supported, use OpenSSL TLS as a more indistinguishable TLS.
- if useOpenSSL {
- conn, err = newOpenSSLConn(rawConn, hostname, config)
- if err != nil {
- rawConn.Close()
- return nil, common.ContextError(err)
- }
- } else {
- conn = tls.Client(rawConn, tlsConfig)
- }
- if config.Timeout == 0 {
- err = conn.Handshake()
- } else {
- go func() {
- errChannel <- conn.Handshake()
- }()
- err = <-errChannel
- }
- // openSSLConns complete verification automatically. For Go TLS,
- // we need to complete the process from crypto/tls.Dial.
- // NOTE: for (config.SendServerName && !config.tlsConfig.InsecureSkipVerify),
- // the tls.Conn.Handshake() does the complete verification, including host name.
- tlsConn, isTlsConn := conn.(*tls.Conn)
- if err == nil && isTlsConn &&
- !config.SkipVerify && tlsConfig.InsecureSkipVerify {
- if config.VerifyLegacyCertificate != nil {
- err = verifyLegacyCertificate(tlsConn, config.VerifyLegacyCertificate)
- } else {
- // Manually verify certificates
- err = verifyServerCerts(tlsConn, hostname, tlsConfig)
- }
- }
- if err != nil {
- rawConn.Close()
- return nil, common.ContextError(err)
- }
- return conn, nil
- }
- func verifyLegacyCertificate(conn *tls.Conn, expectedCertificate *x509.Certificate) error {
- certs := conn.ConnectionState().PeerCertificates
- if len(certs) < 1 {
- return common.ContextError(errors.New("no certificate to verify"))
- }
- if !bytes.Equal(certs[0].Raw, expectedCertificate.Raw) {
- return common.ContextError(errors.New("unexpected certificate"))
- }
- return nil
- }
- func verifyServerCerts(conn *tls.Conn, hostname string, config *tls.Config) error {
- certs := conn.ConnectionState().PeerCertificates
- opts := x509.VerifyOptions{
- Roots: config.RootCAs,
- CurrentTime: time.Now(),
- DNSName: hostname,
- Intermediates: x509.NewCertPool(),
- }
- for i, cert := range certs {
- if i == 0 {
- continue
- }
- opts.Intermediates.AddCert(cert)
- }
- _, err := certs[0].Verify(opts)
- if err != nil {
- return common.ContextError(err)
- }
- return nil
- }
|