mirror of https://gitlab.com/yawning/obfs4
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
parent
d5a51619eb
commit
83f01d5a74
@ -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),
|
||||
)
|
||||
}
|
@ -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…
Reference in New Issue