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.
merge-requests/3/head
Yawning Angel 5 years ago
parent bde8b7ff56
commit c65aaf6407

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

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

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

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

Loading…
Cancel
Save