Browse Source

Implement client-side extended master secret
- Allows use of EmulateChrome mode with 3rd
party servers, which may choose to use
extended master secret

Rod Hynes 9 years ago
parent
commit
76ae06efe7

+ 7 - 6
psiphon/common/tls/common.go

@@ -205,12 +205,13 @@ const (
 // ClientSessionState contains the state needed by clients to resume TLS
 // sessions.
 type ClientSessionState struct {
-	sessionTicket      []uint8               // Encrypted ticket used for session resumption with server
-	vers               uint16                // SSL/TLS version negotiated for the session
-	cipherSuite        uint16                // Ciphersuite negotiated for the session
-	masterSecret       []byte                // MasterSecret generated by client on a full handshake
-	serverCertificates []*x509.Certificate   // Certificate chain presented by the server
-	verifiedChains     [][]*x509.Certificate // Certificate chains we built for verification
+	sessionTicket        []uint8               // Encrypted ticket used for session resumption with server
+	vers                 uint16                // SSL/TLS version negotiated for the session
+	cipherSuite          uint16                // Ciphersuite negotiated for the session
+	masterSecret         []byte                // MasterSecret generated by client on a full handshake
+	serverCertificates   []*x509.Certificate   // Certificate chain presented by the server
+	verifiedChains       [][]*x509.Certificate // Certificate chains we built for verification
+	extendedMasterSecret bool                  // [Psiphon] Whether an extended master secret was used to generate the session
 }
 
 // ClientSessionCache is a cache of ClientSessionState objects that can be used

+ 7 - 6
psiphon/common/tls/conn.go

@@ -43,12 +43,13 @@ type Conn struct {
 	// handshakes counts the number of handshakes performed on the
 	// connection so far. If renegotiation is disabled then this is either
 	// zero or one.
-	handshakes       int
-	didResume        bool // whether this connection was a session resumption
-	cipherSuite      uint16
-	ocspResponse     []byte   // stapled OCSP response
-	scts             [][]byte // signed certificate timestamps from server
-	peerCertificates []*x509.Certificate
+	handshakes           int
+	didResume            bool // whether this connection was a session resumption
+	extendedMasterSecret bool // [Psiphon] whether this session used an extended master secret
+	cipherSuite          uint16
+	ocspResponse         []byte   // stapled OCSP response
+	scts                 [][]byte // signed certificate timestamps from server
+	peerCertificates     []*x509.Certificate
 	// verifiedChains contains the certificate chains that we built, as
 	// opposed to the ones presented by the server.
 	verifiedChains [][]*x509.Certificate

+ 13 - 4
psiphon/common/tls/handshake_client.go

@@ -163,7 +163,8 @@ NextCipherSuite:
 		hello.emulateChrome = true
 
 		// Sanity check that expected and required configuration is present
-		if len(hello.compressionMethods) != 1 ||
+		if hello.vers != VersionTLS12 ||
+			len(hello.compressionMethods) != 1 ||
 			hello.compressionMethods[0] != compressionNone ||
 			!hello.ticketSupported ||
 			!hello.ocspStapling ||
@@ -227,8 +228,6 @@ NextCipherSuite:
 		// https://github.com/google/boringssl/blob/master/LICENSE
 
 		hello.extendedMasterSecretSupported = true
-		// TODO: implement actual support, in case negotiated
-		// https://github.com/google/boringssl/commit/7571292eaca1745f3ecda2374ba1e8163b58c3b5
 
 		hello.channelIDSupported = true
 		// TODO: implement actual support, in case negotiated
@@ -563,7 +562,15 @@ func (hs *clientHandshakeState) doFullHandshake() error {
 		}
 	}
 
-	hs.masterSecret = masterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret, hs.hello.random, hs.serverHello.random)
+	// [Psiphon]
+	// extended master secret implementation from https://github.com/google/boringssl/commit/7571292eaca1745f3ecda2374ba1e8163b58c3b5
+	if hs.serverHello.extendedMasterSecret && c.config.EmulateChrome {
+		hs.masterSecret = extendedMasterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret, hs.finishedHash)
+		c.extendedMasterSecret = true
+	} else {
+		hs.masterSecret = masterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret, hs.hello.random, hs.serverHello.random)
+	}
+
 	if err := c.config.writeKeyLog(hs.hello.random, hs.masterSecret); err != nil {
 		c.sendAlert(alertInternalError)
 		return errors.New("tls: failed to write to key log: " + err.Error())
@@ -673,6 +680,8 @@ func (hs *clientHandshakeState) processServerHello() (bool, error) {
 	hs.masterSecret = hs.session.masterSecret
 	c.peerCertificates = hs.session.serverCertificates
 	c.verifiedChains = hs.session.verifiedChains
+	// [Psiphon]
+	c.extendedMasterSecret = hs.session.extendedMasterSecret
 	return true, nil
 }
 

+ 13 - 0
psiphon/common/tls/handshake_messages.go

@@ -653,6 +653,11 @@ type serverHelloMsg struct {
 	secureRenegotiation          []byte
 	secureRenegotiationSupported bool
 	alpnProtocol                 string
+
+	// [Psiphon]
+	// Additional extensions required for EmulateChrome.
+	// Note: omitted from serverHelloMsg.equal()
+	extendedMasterSecret bool
 }
 
 func (m *serverHelloMsg) equal(i interface{}) bool {
@@ -855,6 +860,8 @@ func (m *serverHelloMsg) unmarshal(data []byte) bool {
 	m.scts = nil
 	m.ticketSupported = false
 	m.alpnProtocol = ""
+	// [Psiphon]
+	m.extendedMasterSecret = false
 
 	if len(data) == 0 {
 		// ServerHello is optionally followed by extension data
@@ -962,6 +969,12 @@ func (m *serverHelloMsg) unmarshal(data []byte) bool {
 				m.scts = append(m.scts, d[:sctLen])
 				d = d[sctLen:]
 			}
+		// [Psiphon]
+		case extensionExtendedMasterSecret:
+			if length != 0 {
+				return false
+			}
+			m.extendedMasterSecret = true
 		}
 		data = data[length:]
 	}

+ 13 - 0
psiphon/common/tls/prf.go

@@ -117,6 +117,7 @@ const (
 )
 
 var masterSecretLabel = []byte("master secret")
+var extendedMasterSecretLabel = []byte("extended master secret") // [Psiphon]
 var keyExpansionLabel = []byte("key expansion")
 var clientFinishedLabel = []byte("client finished")
 var serverFinishedLabel = []byte("server finished")
@@ -154,6 +155,18 @@ func masterFromPreMasterSecret(version uint16, suite *cipherSuite, preMasterSecr
 	return masterSecret
 }
 
+// [Psiphon]
+// from: https://github.com/google/boringssl/commit/7571292eaca1745f3ecda2374ba1e8163b58c3b5
+//
+// extendedMasterFromPreMasterSecret generates the master secret from the
+// pre-master secret when the Triple Handshake fix is in effect. See
+// https://tools.ietf.org/html/draft-ietf-tls-session-hash-01
+func extendedMasterFromPreMasterSecret(version uint16, suite *cipherSuite, preMasterSecret []byte, h finishedHash) []byte {
+	masterSecret := make([]byte, masterSecretLength)
+	prfForVersion(version, suite)(masterSecret, preMasterSecret, extendedMasterSecretLabel, h.Sum())
+	return masterSecret
+}
+
 // keysFromMasterSecret generates the connection keys from the master
 // secret, given the lengths of the MAC key, cipher key and IV, as defined in
 // RFC 2246, section 6.3.