[tor-commits] [obfs4/master] transports/meeklite: Add a lightweight HPKP implementation

yawning at torproject.org yawning at torproject.org
Mon Feb 4 03:30:03 UTC 2019


commit c65aaf6407fe742baabd6ea4d6d92970ef0ba44f
Author: Yawning Angel <yawning at schwanenlied.me>
Date:   Mon Feb 4 03:24:25 2019 +0000

    transports/meeklite: Add a lightweight HPKP implementation
    
    HPKP is effectively dead as far as a standard goes, but the idea has
    merit in certain use cases, this being one of them.
    
    As a TLS MITM essentially will strip whatever obfuscation that the
    transport may provide, the digests of the SubjectPublicKeyInfo fields
    of the Tor Browser Azure meek host are now hardcoded.
    
    The behavior can be disabled by passing `disableHPKP=true` on the bridge
    line, for cases where comaptibility is prefered over security.
---
 ChangeLog                        |  2 +
 transports/meeklite/hpkp_lite.go | 88 ++++++++++++++++++++++++++++++++++++++++
 transports/meeklite/meek.go      | 19 ++++++---
 transports/meeklite/transport.go | 28 +++++++++++--
 4 files changed, 129 insertions(+), 8 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index a0c5991..0df8124 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -2,6 +2,8 @@ Changes in version 0.0.9 - UNRELEASED:
  - Various meek_lite code cleanups and bug fixes.
  - Bug 29077: uTLS for ClientHello camouflage (meek_lite).
  - More fixes to HTTP Basic auth.
+ - (meek_lite) Pin the certificate chain public keys for the default
+   Tor Browser Azure bridge (meek_lite).
 
 Changes in version 0.0.8 - 2019-01-20:
  - Bug 24793: Send the correct authorization HTTP header for basic auth.
diff --git a/transports/meeklite/hpkp_lite.go b/transports/meeklite/hpkp_lite.go
new file mode 100644
index 0000000..9e20d65
--- /dev/null
+++ b/transports/meeklite/hpkp_lite.go
@@ -0,0 +1,88 @@
+/*
+ * 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"
+
+	"golang.org/x/net/idna"
+)
+
+var builtinPinDB *hpkpDatabase
+
+type hpkpDatabase struct {
+	pins map[string]map[string]bool
+}
+
+func (db *hpkpDatabase) HasPins(host string) (string, bool) {
+	h, err := normalizeHost(host)
+	return h, (db.pins[host] != nil && err == nil)
+}
+
+func (db *hpkpDatabase) Validate(host string, chains [][]*x509.Certificate) bool {
+	var ok bool
+	if host, ok = db.HasPins(host); !ok {
+		return false
+	}
+
+	pins := db.pins[host]
+	for _, chain := range chains {
+		for _, cert := range chain {
+			derivedPin := sha256.Sum256(cert.RawSubjectPublicKeyInfo)
+			derivedPinEncoded := base64.StdEncoding.EncodeToString(derivedPin[:])
+			if !pins[derivedPinEncoded] {
+				return false
+			}
+		}
+	}
+
+	return true
+}
+
+func (db *hpkpDatabase) Add(host string, pins []string) {
+	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] = pinMap
+}
+
+func normalizeHost(host string) (string, error) {
+	return idna.Lookup.ToASCII(host)
+}
+
+func init() {
+	builtinPinDB = &hpkpDatabase{
+		pins: make(map[string]map[string]bool),
+	}
+
+	// Generated on 2019-02-04.
+	builtinPinDB.Add("ajax.aspnetcdn.com", []string{
+		"PPjoAKk+kCVr9VNPXJkyHXEKnIyd5t5NqpPL3zCvJOE=",
+		"wBdPad95AU7OgLRs0FU/E6ILO1MSCM84kJ9y0H+TT7s=",
+		"Y9mvm0exBk1JoQ57f9Vm28jKo5lFm/woKcVxrYxu80o=",
+	})
+}
diff --git a/transports/meeklite/meek.go b/transports/meeklite/meek.go
index f86e934..fc97d7f 100644
--- a/transports/meeklite/meek.go
+++ b/transports/meeklite/meek.go
@@ -41,6 +41,7 @@ import (
 	gourl "net/url"
 	"os"
 	"runtime"
+	"strings"
 	"sync"
 	"time"
 
@@ -50,9 +51,10 @@ import (
 )
 
 const (
-	urlArg   = "url"
-	frontArg = "front"
-	utlsArg  = "utls"
+	urlArg         = "url"
+	frontArg       = "front"
+	utlsArg        = "utls"
+	disableHPKPArg = "disableHPKP"
 
 	maxChanBacklog = 16
 
@@ -76,7 +78,8 @@ type meekClientArgs struct {
 	url   *gourl.URL
 	front string
 
-	utls *utls.ClientHelloID
+	utls        *utls.ClientHelloID
+	disableHPKP bool
 }
 
 func (ca *meekClientArgs) Network() string {
@@ -114,6 +117,12 @@ func newClientArgs(args *pt.Args) (ca *meekClientArgs, err error) {
 		return nil, err
 	}
 
+	// Parse the (optional) HPKP disable argument.
+	hpkpOpt, _ := args.Get(disableHPKPArg)
+	if strings.ToLower(hpkpOpt) == "true" {
+		ca.disableHPKP = true
+	}
+
 	return ca, nil
 }
 
@@ -358,7 +367,7 @@ func newMeekConn(network, addr string, dialFn base.DialFunc, ca *meekClientArgs)
 	case nil:
 		rt = &http.Transport{Dial: dialFn}
 	default:
-		rt = newRoundTripper(dialFn, ca.utls)
+		rt = newRoundTripper(dialFn, ca.utls, ca.disableHPKP)
 	}
 
 	conn := &meekConn{
diff --git a/transports/meeklite/transport.go b/transports/meeklite/transport.go
index 2736fe1..5817be0 100644
--- a/transports/meeklite/transport.go
+++ b/transports/meeklite/transport.go
@@ -19,6 +19,7 @@ package meeklite
 
 import (
 	"crypto/tls"
+	"crypto/x509"
 	"errors"
 	"fmt"
 	"net"
@@ -28,6 +29,7 @@ import (
 	"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"
@@ -64,7 +66,8 @@ type roundTripper struct {
 	dialFn        base.DialFunc
 	transport     http.RoundTripper
 
-	initConn net.Conn
+	initConn    net.Conn
+	disableHPKP bool
 }
 
 func (rt *roundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
@@ -128,7 +131,25 @@ func (rt *roundTripper) dialTLS(network, addr string) (net.Conn, error) {
 		host = addr
 	}
 
-	conn := utls.UClient(rawConn, &utls.Config{ServerName: host}, *rt.clientHelloID)
+	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}, *rt.clientHelloID)
 	if err = conn.Handshake(); err != nil {
 		conn.Close()
 		return nil, err
@@ -170,10 +191,11 @@ func getDialTLSAddr(u *url.URL) string {
 	return net.JoinHostPort(u.Host, strconv.Itoa(pInt))
 }
 
-func newRoundTripper(dialFn base.DialFunc, clientHelloID *utls.ClientHelloID) http.RoundTripper {
+func newRoundTripper(dialFn base.DialFunc, clientHelloID *utls.ClientHelloID, disableHPKP bool) http.RoundTripper {
 	return &roundTripper{
 		clientHelloID: clientHelloID,
 		dialFn:        dialFn,
+		disableHPKP:   disableHPKP,
 	}
 }
 



More information about the tor-commits mailing list