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.
merge-requests/5/head
Yawning Angel 2 years ago
parent d5a51619eb
commit 83f01d5a74

@ -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

@ -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
)

@ -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=

@ -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)
)

@ -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-changes
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),
)
}

@ -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)
)

@ -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()
}
Loading…
Cancel
Save