Просмотр исходного кода

Merge branch 'master' into packet-tunnel

Rod Hynes 8 лет назад
Родитель
Сommit
186eeb0cdf

+ 2 - 0
.travis.yml

@@ -13,6 +13,7 @@ script:
 - go test -race -v ./common
 - go test -race -v ./common
 - go test -race -v ./common/osl
 - go test -race -v ./common/osl
 - go test -race -v ./common/protocol
 - go test -race -v ./common/protocol
+- go test -race -v -run TestObfuscatedSessionTicket ./common/tls
 # TODO: enable once known race condition is addressed
 # TODO: enable once known race condition is addressed
 #       also, see comment below
 #       also, see comment below
 #- sudo -E env "PATH=$PATH" go test -race -v ./common/tun
 #- sudo -E env "PATH=$PATH" go test -race -v ./common/tun
@@ -23,6 +24,7 @@ script:
 - go test -v -covermode=count -coverprofile=common.coverprofile ./common
 - go test -v -covermode=count -coverprofile=common.coverprofile ./common
 - go test -v -covermode=count -coverprofile=osl.coverprofile ./common/osl
 - go test -v -covermode=count -coverprofile=osl.coverprofile ./common/osl
 - go test -v -covermode=count -coverprofile=protocol.coverprofile ./common/protocol
 - go test -v -covermode=count -coverprofile=protocol.coverprofile ./common/protocol
+- go test -v -covermode=count -coverprofile=tls.coverprofile -run TestObfuscatedSessionTicket ./common/tls
 # TODO: fix and reenable test, which is failing in TravisCI environment:
 # TODO: fix and reenable test, which is failing in TravisCI environment:
 # --- FAIL: TestTunneledTCPIPv4
 # --- FAIL: TestTunneledTCPIPv4
 #    tun_test.go:226: startTestTCPClient failed: syscall.Connect failed: connection timed out
 #    tun_test.go:226: startTestTCPClient failed: syscall.Connect failed: connection timed out

+ 2 - 17
psiphon/common/tls/cipher_suites.go

@@ -407,23 +407,8 @@ const (
 	// https://tools.ietf.org/html/rfc7507.
 	// https://tools.ietf.org/html/rfc7507.
 	TLS_FALLBACK_SCSV uint16 = 0x5600
 	TLS_FALLBACK_SCSV uint16 = 0x5600
 
 
-	// Psiphon suites for indistinguishable TLS.
+	// [Psiphon]
+	// TLS_..._CHACHA20_POLY1305_OLD are required for EmulateChrome.
 	TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_OLD   uint16 = 0xcc13
 	TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_OLD   uint16 = 0xcc13
 	TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_OLD uint16 = 0xcc14
 	TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_OLD uint16 = 0xcc14
-	TLS_GREASE_0A0A                            uint16 = 0x1A1A
-	TLS_GREASE_1A1A                            uint16 = 0x2A2A
-	TLS_GREASE_2A2A                            uint16 = 0x3A3A
-	TLS_GREASE_3A3A                            uint16 = 0x4A4A
-	TLS_GREASE_4A4A                            uint16 = 0x5A5A
-	TLS_GREASE_5A5A                            uint16 = 0x6A6A
-	TLS_GREASE_6A6A                            uint16 = 0x7A7A
-	TLS_GREASE_7A7A                            uint16 = 0x8A8A
-	TLS_GREASE_8A8A                            uint16 = 0x9A9A
-	TLS_GREASE_9A9A                            uint16 = 0xAAAA
-	TLS_GREASE_AAAA                            uint16 = 0xBABA
-	TLS_GREASE_BABA                            uint16 = 0xCACA
-	TLS_GREASE_CACA                            uint16 = 0xDADA
-	TLS_GREASE_DADA                            uint16 = 0xEAEA
-	TLS_GREASE_EAEA                            uint16 = 0xFAFA
-	TLS_GREASE_FAFA                            uint16 = 0x0A0A
 )
 )

+ 19 - 12
psiphon/common/tls/handshake_client.go

@@ -7,12 +7,9 @@ package tls
 import (
 import (
 	"bytes"
 	"bytes"
 	"crypto"
 	"crypto"
-	"crypto/rand"
-	"crypto/sha256"
-	"math/big"
-
 	"crypto/ecdsa"
 	"crypto/ecdsa"
 	"crypto/rsa"
 	"crypto/rsa"
+	"crypto/sha256"
 	"crypto/subtle"
 	"crypto/subtle"
 	"crypto/x509"
 	"crypto/x509"
 	"errors"
 	"errors"
@@ -183,14 +180,14 @@ NextCipherSuite:
 		}
 		}
 
 
 		hello.supportedCurves = []CurveID{
 		hello.supportedCurves = []CurveID{
-			CurveID(randomGREASEValue()),
+			CurveID(getGREASEValue(hello.random, greaseGroup)),
 			X25519,
 			X25519,
 			CurveP256, // secp256r1
 			CurveP256, // secp256r1
 			CurveP384, // secp384r1
 			CurveP384, // secp384r1
 		}
 		}
 
 
 		hello.cipherSuites = []uint16{
 		hello.cipherSuites = []uint16{
-			randomGREASEValue(),
+			getGREASEValue(hello.random, greaseCipher),
 			TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
 			TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
 			TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
 			TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
 			TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
 			TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
@@ -212,7 +209,7 @@ NextCipherSuite:
 			TLS_RSA_WITH_3DES_EDE_CBC_SHA,
 			TLS_RSA_WITH_3DES_EDE_CBC_SHA,
 		}
 		}
 
 
-		// From: https://github.com/google/boringssl/blob/7d7554b6b3c79e707e25521e61e066ce2b996e4c/ssl/t1_lib.c#L442
+		// From: https://github.com/google/boringssl/blob/46db7af2c998cf8514d606408546d9be9699f03c/ssl/t1_lib.c#L442
 		// TODO: handle RSA-PSS (0x08)
 		// TODO: handle RSA-PSS (0x08)
 		hello.signatureAndHashes = []signatureAndHash{
 		hello.signatureAndHashes = []signatureAndHash{
 			{hashSHA256, signatureECDSA},
 			{hashSHA256, signatureECDSA},
@@ -244,7 +241,7 @@ NextCipherSuite:
 
 
 		// In BoringSSL, the session ID is a SHA256 digest of the
 		// In BoringSSL, the session ID is a SHA256 digest of the
 		// session ticket:
 		// session ticket:
-		// https://github.com/google/boringssl/blob/33fe4a0d1406f423e7424ea7367e1d1a51c2edc1/ssl/handshake_client.c#L1901-L1908
+		// https://github.com/google/boringssl/blob/46db7af2c998cf8514d606408546d9be9699f03c/ssl/handshake_client.c#L1895-L1902
 		if session != nil {
 		if session != nil {
 			hello.sessionTicket = session.sessionTicket
 			hello.sessionTicket = session.sessionTicket
 			sessionId := sha256.Sum256(session.sessionTicket)
 			sessionId := sha256.Sum256(session.sessionTicket)
@@ -356,10 +353,20 @@ NextCipherSuite:
 	return nil
 	return nil
 }
 }
 
 
-func randomGREASEValue() uint16 {
-	values := []uint16{0x0A0A, 0x1A1A, 0x2A2A, 0x3A3A, 0x4A4A, 0x5A5A, 0x6A6A, 0x7A7A, 0x8A8A, 0x9A9A, 0xAAAA, 0xBABA, 0xCACA, 0xDADA, 0xEAEA, 0xFAFA}
-	i, _ := rand.Int(rand.Reader, big.NewInt(int64(len(values))))
-	return values[int(i.Int64())]
+// From: https://github.com/google/boringssl/blob/46db7af2c998cf8514d606408546d9be9699f03c/ssl/internal.h#L1225-L1231
+const (
+	greaseCipher     = 0
+	greaseGroup      = 1
+	greaseExtension1 = 2
+	greaseExtension2 = 3
+)
+
+func getGREASEValue(random []byte, index int) uint16 {
+	// From: https://github.com/google/boringssl/blob/46db7af2c998cf8514d606408546d9be9699f03c/ssl/handshake_client.c#L545-L555
+	value := uint16(random[index])
+	value = (value & 0xf0) | 0x0a
+	value |= value << 8
+	return value
 }
 }
 
 
 func (hs *clientHandshakeState) doFullHandshake() error {
 func (hs *clientHandshakeState) doFullHandshake() error {

+ 21 - 17
psiphon/common/tls/handshake_messages.go

@@ -138,20 +138,21 @@ func (m *clientHelloMsg) marshal() []byte {
 	// Padding extension required for EmulateChrome.
 	// Padding extension required for EmulateChrome.
 	// Logic from:
 	// Logic from:
 	//
 	//
-	// https://github.com/google/boringssl/blob/7d7554b6b3c79e707e25521e61e066ce2b996e4c/ssl/t1_lib.c#L2803
+	// https://github.com/google/boringssl/blob/46db7af2c998cf8514d606408546d9be9699f03c/ssl/t1_lib.c#L2803
 	// https://github.com/google/boringssl/blob/master/LICENSE
 	// https://github.com/google/boringssl/blob/master/LICENSE
-
-	unpaddedLength := length + 2 + 4*numExtensions + extensionsLength
 	paddingLength := uint16(0)
 	paddingLength := uint16(0)
-	if unpaddedLength > 0xff && unpaddedLength < 0x200 {
-		paddingLength = 0x200 - uint16(unpaddedLength)
-		if paddingLength >= 4+1 {
-			paddingLength -= 4
-		} else {
-			paddingLength = 1
+	if m.emulateChrome {
+		unpaddedLength := length + 2 + 4*numExtensions + extensionsLength
+		if unpaddedLength > 0xff && unpaddedLength < 0x200 {
+			paddingLength = 0x200 - uint16(unpaddedLength)
+			if paddingLength >= 4+1 {
+				paddingLength -= 4
+			} else {
+				paddingLength = 1
+			}
+			extensionsLength += int(paddingLength)
+			numExtensions++
 		}
 		}
-		extensionsLength += int(paddingLength)
-		numExtensions++
 	}
 	}
 
 
 	if numExtensions > 0 {
 	if numExtensions > 0 {
@@ -393,8 +394,8 @@ func (m *clientHelloMsg) marshal() []byte {
 		// of extensions as required for EmulateChrome is handled
 		// of extensions as required for EmulateChrome is handled
 		// in Conn.clientHandshae().
 		// in Conn.clientHandshae().
 
 
-		value := randomGREASEValue()
-		marshalGREASE(value, true)
+		greaseValue := getGREASEValue(m.random, greaseExtension1)
+		marshalGREASE(greaseValue, true)
 
 
 		if m.secureRenegotiationSupported {
 		if m.secureRenegotiationSupported {
 			marshalRenegotiationInfo()
 			marshalRenegotiationInfo()
@@ -433,11 +434,14 @@ func (m *clientHelloMsg) marshal() []byte {
 			marshalSupportedCurves()
 			marshalSupportedCurves()
 		}
 		}
 
 
-		previousGREASEValue := value
-		for value == previousGREASEValue {
-			value = randomGREASEValue()
+		previousValue := greaseValue
+		greaseValue = getGREASEValue(m.random, greaseExtension2)
+		if greaseValue == previousValue {
+			// See: https://github.com/google/boringssl/blob/46db7af2c998cf8514d606408546d9be9699f03c/ssl/t1_lib.c#L2787-L2792
+			greaseValue ^= 0x1010
 		}
 		}
-		marshalGREASE(value, false)
+
+		marshalGREASE(greaseValue, false)
 
 
 		if paddingLength > 0 {
 		if paddingLength > 0 {
 			marshalPadding(paddingLength)
 			marshalPadding(paddingLength)

+ 17 - 1
psiphon/common/tls/obfuscated_test.go

@@ -37,6 +37,14 @@ import (
 // [Psiphon]
 // [Psiphon]
 // TestObfuscatedSessionTicket exercises the Obfuscated Session Tickets facility.
 // TestObfuscatedSessionTicket exercises the Obfuscated Session Tickets facility.
 func TestObfuscatedSessionTicket(t *testing.T) {
 func TestObfuscatedSessionTicket(t *testing.T) {
+	runObfuscatedSessionTicket(t, false)
+}
+
+func TestObfuscatedSessionTicketEmulateChrome(t *testing.T) {
+	runObfuscatedSessionTicket(t, true)
+}
+
+func runObfuscatedSessionTicket(t *testing.T, emulateChrome bool) {
 
 
 	var standardSessionTicketKey [32]byte
 	var standardSessionTicketKey [32]byte
 	rand.Read(standardSessionTicketKey[:])
 	rand.Read(standardSessionTicketKey[:])
@@ -46,7 +54,8 @@ func TestObfuscatedSessionTicket(t *testing.T) {
 
 
 	// Note: SNI and certificate CN don't match
 	// Note: SNI and certificate CN don't match
 	clientConfig := &Config{
 	clientConfig := &Config{
-		ServerName: "www.example.com",
+		ServerName:    "www.example.com",
+		EmulateChrome: emulateChrome,
 		ClientSessionCache: NewObfuscatedClientSessionCache(
 		ClientSessionCache: NewObfuscatedClientSessionCache(
 			obfuscatedSessionTicketSharedSecret),
 			obfuscatedSessionTicketSharedSecret),
 	}
 	}
@@ -72,9 +81,14 @@ func TestObfuscatedSessionTicket(t *testing.T) {
 
 
 	result := make(chan error, 1)
 	result := make(chan error, 1)
 
 
+	listening := make(chan struct{}, 1)
+
 	go func() {
 	go func() {
 
 
 		listener, err := Listen("tcp", serverAddress, serverConfig)
 		listener, err := Listen("tcp", serverAddress, serverConfig)
+		defer listener.Close()
+
+		listening <- *new(struct{})
 
 
 		var conn net.Conn
 		var conn net.Conn
 		if err == nil {
 		if err == nil {
@@ -103,6 +117,8 @@ func TestObfuscatedSessionTicket(t *testing.T) {
 
 
 	go func() {
 	go func() {
 
 
+		<-listening
+
 		conn, err := Dial("tcp", serverAddress, clientConfig)
 		conn, err := Dial("tcp", serverAddress, clientConfig)
 
 
 		if err == nil {
 		if err == nil {