This is an automated email from the git hooks/post-receive script.
meskio pushed a commit to branch master in repository pluggable-transports/obfs4.
commit 83f01d5a74379114101e2384709d4e9ae6f6201e Author: Yawning Angel yawning@schwanenlied.me AuthorDate: Thu Feb 3 22:18:44 2022 +0000
transports/meek_lite: Remove utls support
While this was a good idea back when I did it:
* People don't like the fact that it requires a fork of utls to fix compatibility issues, and would rather spend 3 years complaining about it instead of spending a weekend to fix the issues in upstream.
* Tor over meek is trivially identifiable regardless of utls or not.
* Malware asshats ruined domain fronting for everybody. --- ChangeLog | 2 + go.mod | 1 - go.sum | 17 --- transports/meeklite/base.go | 6 +- transports/meeklite/hpkp_lite.go | 124 ------------------- transports/meeklite/meek.go | 47 ++------ transports/meeklite/transport.go | 250 --------------------------------------- 7 files changed, 17 insertions(+), 430 deletions(-)
diff --git a/ChangeLog b/ChangeLog index dc7a357..d9cc09d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,6 @@ Changes in version 0.0.13 - UNRELEASED: + - Stop using utls entirely for TLS signature normalization (meek_lite). + - Stop pinning the certificate chain for default bridges (meek_lite).
Changes in version 0.0.12 - 2021-12-31: - Fix the long standing distinguishers associated with agl's Elligator2 diff --git a/go.mod b/go.mod index 622ac6f..49e491b 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,6 @@ require ( git.torproject.org/pluggable-transports/goptlib.git v1.0.0 github.com/dchest/siphash v1.2.1 gitlab.com/yawning/edwards25519-extra.git v0.0.0-20211229043746-2f91fcc9fbdb - gitlab.com/yawning/utls.git v0.0.12-1 golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 ) diff --git a/go.sum b/go.sum index 5de7fd9..b542a6b 100644 --- a/go.sum +++ b/go.sum @@ -4,32 +4,15 @@ git.torproject.org/pluggable-transports/goptlib.git v1.0.0 h1:ElTwFFPKf/tA6x5nuI git.torproject.org/pluggable-transports/goptlib.git v1.0.0/go.mod h1:YT4XMSkuEXbtqlydr9+OxqFAyspUv0Gr9qhM3B++o/Q= github.com/dchest/siphash v1.2.1 h1:4cLinnzVJDKxTCl9B01807Yiy+W7ZzVHj/KIroQRvT4= github.com/dchest/siphash v1.2.1/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4= -github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q= -github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo= -github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= -github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= -gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo= -gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ= gitlab.com/yawning/edwards25519-extra.git v0.0.0-20211229043746-2f91fcc9fbdb h1:qRSZHsODmAP5qDvb3YsO7Qnf3TRiVbGxNG/WYnlM4/o= gitlab.com/yawning/edwards25519-extra.git v0.0.0-20211229043746-2f91fcc9fbdb/go.mod h1:gvdJuZuO/tPZyhEV8K3Hmoxv/DWud5L4qEQxfYjEUTo= -gitlab.com/yawning/utls.git v0.0.12-1 h1:RL6O0MP2YI0KghuEU/uGN6+8b4183eqNWoYgx7CXD0U= -gitlab.com/yawning/utls.git v0.0.12-1/go.mod h1:3ONKiSFR9Im/c3t5RKmMJTVdmZN496FNyk3mjrY1dyo= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/net v0.0.0-20190328230028-74de082e2cca/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/transports/meeklite/base.go b/transports/meeklite/base.go index 399ff4c..e9d8ca1 100644 --- a/transports/meeklite/base.go +++ b/transports/meeklite/base.go @@ -85,5 +85,7 @@ func (cf *meekClientFactory) Dial(network, addr string, dialFn base.DialFunc, ar return newMeekConn(network, addr, dialFn, ca) }
-var _ base.ClientFactory = (*meekClientFactory)(nil) -var _ base.Transport = (*Transport)(nil) +var ( + _ base.ClientFactory = (*meekClientFactory)(nil) + _ base.Transport = (*Transport)(nil) +) diff --git a/transports/meeklite/hpkp_lite.go b/transports/meeklite/hpkp_lite.go deleted file mode 100644 index 97ac590..0000000 --- a/transports/meeklite/hpkp_lite.go +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (c) 2019 Yawning Angel <yawning at schwanenlied dot me> - * - * 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 https://www.gnu.org/licenses/. - */ - -package meeklite - -import ( - "crypto/sha256" - "crypto/x509" - "encoding/base64" - "time" - - "golang.org/x/net/idna" -) - -var builtinPinDB *hpkpDatabase - -type hpkpDatabase struct { - pins map[string]*pinEntry -} - -type pinEntry struct { - digests map[string]bool - expiry time.Time -} - -func (db *hpkpDatabase) HasPins(host string) (string, bool) { - h, err := normalizeHost(host) - if err == nil { - if entry := db.pins[host]; entry != nil { - if time.Now().Before(entry.expiry) { - return h, true - } - } - } - return h, false -} - -func (db *hpkpDatabase) Validate(host string, chains [][]*x509.Certificate) bool { - host, err := normalizeHost(host) - if err != nil { - return false - } - entry := db.pins[host] - if entry == nil { - return false - } - if time.Now().After(entry.expiry) { - // If the pins are expired, assume that it is valid. - return true - } - - // Search for an intersection between the pins and the cert chain. - for _, chain := range chains { - for _, cert := range chain { - derivedPin := sha256.Sum256(cert.RawSubjectPublicKeyInfo) - derivedPinEncoded := base64.StdEncoding.EncodeToString(derivedPin[:]) - if entry.digests[derivedPinEncoded] { - return true - } - } - } - - return false -} - -func (db *hpkpDatabase) Add(host string, pins []string, expiry time.Time) { - h, err := normalizeHost(host) - if err != nil { - panic("failed to add hpkp pin, invalid host: " + err.Error()) - } - - pinMap := make(map[string]bool) - for _, pin := range pins { - pinMap[pin] = true - } - - db.pins[h] = &pinEntry{ - digests: pinMap, - expiry: expiry, - } -} - -func normalizeHost(host string) (string, error) { - return idna.Lookup.ToASCII(host) -} - -func init() { - builtinPinDB = &hpkpDatabase{ - pins: make(map[string]*pinEntry), - } - - // Pin all of Microsoft Azure's root CA certificates for the Tor Browser - // Azure bridge. - // - // See: https://docs.microsoft.com/en-us/azure/security/fundamentals/tls-certificate... - builtinPinDB.Add( - "ajax.aspnetcdn.com", - []string{ - "i7WTqTvh0OioIruIfFR4kMPnBqrS2rdiVPl/s2uC/CY=", // DigiCert Global Root G2 - 2038-01-15 12:00:00 - "r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=", // DigiCert Global Root CA - 2031-11-10 00:00:00 - "Y9mvm0exBk1JoQ57f9Vm28jKo5lFm/woKcVxrYxu80o=", // Baltimore CyberTrust Root - 2025-05-12 23:59:00 - "7KDxgUAs56hlKzG00DbfJH46MLf0GlDZHsT5CwBrQ6E=", // D-TRUST Root Class 3 CA 2 2009 - 2029-11-05 08:35:58 - "svcpi1K/LDysTd/nLeTWgqxYlXWVmC8rYjAa9ZfGmcU=", // Microsoft RSA Root Certificate Authority 2017 - 2042-07-18 23:00:23 - "NfU84SZGEeAzQP434ex9TMmGxWE9ynD9BKpEVF8tryg=", // Microsoft ECC Root Certificate Authority 2017 - 2042-07-18 23:16:04 - }, - // As of 2020-12-07, we're getting the "DigiCert Global Root CA" - // certificate, so our expiration time matches this certificate. - time.Date(2031, time.November, 20, 00, 00, 00, 00, time.UTC), - ) -} diff --git a/transports/meeklite/meek.go b/transports/meeklite/meek.go index 367563b..17c7a67 100644 --- a/transports/meeklite/meek.go +++ b/transports/meeklite/meek.go @@ -41,20 +41,16 @@ import ( gourl "net/url" "os" "runtime" - "strings" "sync" "time"
"git.torproject.org/pluggable-transports/goptlib.git" "gitlab.com/yawning/obfs4.git/transports/base" - utls "gitlab.com/yawning/utls.git" )
const ( - urlArg = "url" - frontArg = "front" - utlsArg = "utls" - disableHPKPArg = "disableHPKP" + urlArg = "url" + frontArg = "front"
maxChanBacklog = 16
@@ -77,9 +73,6 @@ var ( type meekClientArgs struct { url *gourl.URL front string - - utls *utls.ClientHelloID - disableHPKP bool }
func (ca *meekClientArgs) Network() string { @@ -111,25 +104,13 @@ func newClientArgs(args *pt.Args) (ca *meekClientArgs, err error) { // Parse the (optional) front argument. ca.front, _ = args.Get(frontArg)
- // Parse the (optional) utls argument. - utlsOpt, _ := args.Get(utlsArg) - if ca.utls, err = parseClientHelloID(utlsOpt); err != nil { - return nil, err - } - - // Parse the (optional) HPKP disable argument. - hpkpOpt, _ := args.Get(disableHPKPArg) - if strings.ToLower(hpkpOpt) == "true" { - ca.disableHPKP = true - } - return ca, nil }
type meekConn struct { - args *meekClientArgs - sessionID string - roundTripper http.RoundTripper + args *meekClientArgs + sessionID string + transport *http.Transport
closeOnce sync.Once workerWrChan chan []byte @@ -261,7 +242,7 @@ func (c *meekConn) roundTrip(sndBuf []byte) (recvBuf []byte, err error) { req.Header.Set("X-Session-Id", c.sessionID) req.Header.Set("User-Agent", "")
- resp, err = c.roundTripper.RoundTrip(req) + resp, err = c.transport.RoundTrip(req) if err != nil { return nil, err } @@ -362,18 +343,10 @@ func newMeekConn(network, addr string, dialFn base.DialFunc, ca *meekClientArgs) return nil, err }
- var rt http.RoundTripper - switch ca.utls { - case nil: - rt = &http.Transport{Dial: dialFn} - default: - rt = newRoundTripper(dialFn, ca.utls, ca.disableHPKP) - } - conn := &meekConn{ args: ca, sessionID: id, - roundTripper: rt, + transport: &http.Transport{Dial: dialFn}, workerWrChan: make(chan []byte, maxChanBacklog), workerRdChan: make(chan []byte, maxChanBacklog), workerCloseChan: make(chan struct{}), @@ -394,5 +367,7 @@ func newSessionID() (string, error) { return hex.EncodeToString(h[:16]), nil }
-var _ net.Conn = (*meekConn)(nil) -var _ net.Addr = (*meekClientArgs)(nil) +var ( + _ net.Conn = (*meekConn)(nil) + _ net.Addr = (*meekClientArgs)(nil) +) diff --git a/transports/meeklite/transport.go b/transports/meeklite/transport.go deleted file mode 100644 index 8ff02a5..0000000 --- a/transports/meeklite/transport.go +++ /dev/null @@ -1,250 +0,0 @@ -/* - * Copyright (c) 2019 Yawning Angel <yawning at schwanenlied dot me> - * - * 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 https://www.gnu.org/licenses/. - */ - -package meeklite - -import ( - "crypto/tls" - "crypto/x509" - "errors" - "fmt" - "net" - "net/http" - "net/url" - "strconv" - "strings" - "sync" - - "gitlab.com/yawning/obfs4.git/common/log" - "gitlab.com/yawning/obfs4.git/transports/base" - utls "gitlab.com/yawning/utls.git" - "golang.org/x/net/http2" -) - -var ( - errProtocolNegotiated = errors.New("meek_lite: protocol negotiated") - - // This should be kept in sync with what is available in utls. - clientHelloIDMap = map[string]*utls.ClientHelloID{ - "hellogolang": nil, // Don't bother with utls. - "hellorandomized": &utls.HelloRandomized, - "hellorandomizedalpn": &utls.HelloRandomizedALPN, - "hellorandomizednoalpn": &utls.HelloRandomizedNoALPN, - "hellofirefox_auto": &utls.HelloFirefox_Auto, - "hellofirefox_55": &utls.HelloFirefox_55, - "hellofirefox_56": &utls.HelloFirefox_56, - "hellofirefox_63": &utls.HelloFirefox_63, - "hellofirefix_65": &utls.HelloFirefox_65, - "hellochrome_auto": &utls.HelloChrome_Auto, - "hellochrome_58": &utls.HelloChrome_58, - "hellochrome_62": &utls.HelloChrome_62, - "hellochrome_70": &utls.HelloChrome_70, - "hellochrome_72": &utls.HelloChrome_72, - "hellochrome_83": &utls.HelloChrome_83, - "helloios_auto": &utls.HelloIOS_Auto, - "helloios_11_1": &utls.HelloIOS_11_1, - "helloios_12_1": &utls.HelloIOS_12_1, - } - defaultClientHello = &utls.HelloFirefox_Auto -) - -type roundTripper struct { - sync.Mutex - - clientHelloID *utls.ClientHelloID - dialFn base.DialFunc - transport http.RoundTripper - - initConn net.Conn - disableHPKP bool -} - -func (rt *roundTripper) RoundTrip(req *http.Request) (*http.Response, error) { - // Note: This isn't protected with a lock, since the meeklite ioWorker - // serializes RoundTripper requests. - // - // This also assumes that req.URL.Host will remain constant for the - // lifetime of the roundTripper, which is a valid assumption for meeklite. - if rt.transport == nil { - if err := rt.getTransport(req); err != nil { - return nil, err - } - } - return rt.transport.RoundTrip(req) -} - -func (rt *roundTripper) getTransport(req *http.Request) error { - switch strings.ToLower(req.URL.Scheme) { - case "http": - rt.transport = newHTTPTransport(rt.dialFn, nil) - return nil - case "https": - default: - return fmt.Errorf("meek_lite: invalid URL scheme: '%v'", req.URL.Scheme) - } - - _, err := rt.dialTLS("tcp", getDialTLSAddr(req.URL)) - switch err { - case errProtocolNegotiated: - case nil: - // Should never happen. - panic("meek_lite: dialTLS returned no error when determining transport") - default: - return err - } - - return nil -} - -func (rt *roundTripper) dialTLS(network, addr string) (net.Conn, error) { - // Unlike rt.transport, this is protected by a critical section - // since past the initial manual call from getTransport, the HTTP - // client will be the caller. - rt.Lock() - defer rt.Unlock() - - // If we have the connection from when we determined the HTTPS - // transport to use, return that. - if conn := rt.initConn; conn != nil { - rt.initConn = nil - return conn, nil - } - - rawConn, err := rt.dialFn(network, addr) - if err != nil { - return nil, err - } - - var host string - if host, _, err = net.SplitHostPort(addr); err != nil { - host = addr - } - - var verifyPeerCertificateFn func([][]byte, [][]*x509.Certificate) error - if !rt.disableHPKP { - if pinHost, ok := builtinPinDB.HasPins(host); ok { - if rt.transport == nil { - log.Debugf("meek_lite - HPKP enabled for host: %v", pinHost) - } - verifyPeerCertificateFn = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { - if !builtinPinDB.Validate(pinHost, verifiedChains) { - log.Errorf("meek_lite - HPKP validation failure, potential MITM for host: %v", pinHost) - return fmt.Errorf("meek_lite: HPKP validation failure for host: %v", pinHost) - } - return nil - } - } - } else if rt.transport == nil { - log.Warnf("meek_lite - HPKP disabled for host: %v", host) - } - - conn := utls.UClient(rawConn, &utls.Config{ - ServerName: host, - VerifyPeerCertificate: verifyPeerCertificateFn, - - // `crypto/tls` gradually ramps up the record size. While this is - // a good optimization and is a relatively common server feature, - // neither Firefox nor Chromium appear to use such optimizations. - DynamicRecordSizingDisabled: true, - }, *rt.clientHelloID) - if err = conn.Handshake(); err != nil { - conn.Close() - return nil, err - } - - if rt.transport != nil { - return conn, nil - } - - // No http.Transport constructed yet, create one based on the results - // of ALPN. - switch conn.ConnectionState().NegotiatedProtocol { - case http2.NextProtoTLS: - // The remote peer is speaking HTTP 2 + TLS. - rt.transport = &http2.Transport{DialTLS: rt.dialTLSHTTP2} - default: - // Assume the remote peer is speaking HTTP 1.x + TLS. - rt.transport = newHTTPTransport(nil, rt.dialTLS) - } - - // Stash the connection just established for use servicing the - // actual request (should be near-immediate). - rt.initConn = conn - - return nil, errProtocolNegotiated -} - -func (rt *roundTripper) dialTLSHTTP2(network, addr string, cfg *tls.Config) (net.Conn, error) { - return rt.dialTLS(network, addr) -} - -func getDialTLSAddr(u *url.URL) string { - host, port, err := net.SplitHostPort(u.Host) - if err == nil { - return net.JoinHostPort(host, port) - } - pInt, _ := net.LookupPort("tcp", u.Scheme) - - return net.JoinHostPort(u.Host, strconv.Itoa(pInt)) -} - -func newRoundTripper(dialFn base.DialFunc, clientHelloID *utls.ClientHelloID, disableHPKP bool) http.RoundTripper { - return &roundTripper{ - clientHelloID: clientHelloID, - dialFn: dialFn, - disableHPKP: disableHPKP, - } -} - -func parseClientHelloID(s string) (*utls.ClientHelloID, error) { - s = strings.ToLower(s) - switch s { - case "none": - return nil, nil - case "": - return defaultClientHello, nil - default: - if ret := clientHelloIDMap[s]; ret != nil { - return ret, nil - } - } - return nil, fmt.Errorf("invalid ClientHelloID: '%v'", s) -} - -func newHTTPTransport(dialFn, dialTLSFn base.DialFunc) *http.Transport { - base := (http.DefaultTransport).(*http.Transport) - - return &http.Transport{ - Dial: dialFn, - DialTLS: dialTLSFn, - - // Use default configuration values, taken from the runtime. - MaxIdleConns: base.MaxIdleConns, - IdleConnTimeout: base.IdleConnTimeout, - TLSHandshakeTimeout: base.TLSHandshakeTimeout, - ExpectContinueTimeout: base.ExpectContinueTimeout, - } -} - -func init() { - // Attempt to increase compatibility and performance, there's an - // encrypted link underneath, and this doesn't (shouldn't) affect - // the external fingerprint. - utls.EnableWeakCiphers() - utls.EnableVartimeGroups() - utls.EnableVartimeAES() -}