Extended master secret support.

This change implements support for the extended master secret. See
https://tools.ietf.org/html/draft-ietf-tls-session-hash-01
https://secure-resumption.com/

Change-Id: Ifc7327763149ab0894b4f1d48cdc35e0f1093b93
Reviewed-on: https://boringssl-review.googlesource.com/1930
Reviewed-by: David Benjamin <davidben@chromium.org>
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc
index 6b27e26..d04c3c0 100644
--- a/ssl/test/bssl_shim.cc
+++ b/ssl/test/bssl_shim.cc
@@ -482,6 +482,13 @@
     }
   }
 
+  if (config->expect_extended_master_secret) {
+    if (!ssl->session->extended_master_secret) {
+      fprintf(stderr, "No EMS for session when expected");
+      return 2;
+    }
+  }
+
   if (config->write_different_record_sizes) {
     if (config->is_dtls) {
       fprintf(stderr, "write_different_record_sizes not supported for DTLS\n");
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index 8b2c750..935fd15 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -71,16 +71,17 @@
 
 // TLS extension numbers
 const (
-	extensionServerName          uint16 = 0
-	extensionStatusRequest       uint16 = 5
-	extensionSupportedCurves     uint16 = 10
-	extensionSupportedPoints     uint16 = 11
-	extensionSignatureAlgorithms uint16 = 13
-	extensionALPN                uint16 = 16
-	extensionSessionTicket       uint16 = 35
-	extensionNextProtoNeg        uint16 = 13172 // not IANA assigned
-	extensionRenegotiationInfo   uint16 = 0xff01
-	extensionChannelID           uint16 = 30032 // not IANA assigned
+	extensionServerName           uint16 = 0
+	extensionStatusRequest        uint16 = 5
+	extensionSupportedCurves      uint16 = 10
+	extensionSupportedPoints      uint16 = 11
+	extensionSignatureAlgorithms  uint16 = 13
+	extensionALPN                 uint16 = 16
+	extensionExtendedMasterSecret uint16 = 23
+	extensionSessionTicket        uint16 = 35
+	extensionNextProtoNeg         uint16 = 13172 // not IANA assigned
+	extensionRenegotiationInfo    uint16 = 0xff01
+	extensionChannelID            uint16 = 30032 // not IANA assigned
 )
 
 // TLS signaling cipher suite values
@@ -189,12 +190,13 @@
 // 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
-	handshakeHash      []byte              // Handshake hash for Channel ID purposes.
-	serverCertificates []*x509.Certificate // Certificate chain presented by the server
+	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
+	handshakeHash        []byte              // Handshake hash for Channel ID purposes.
+	serverCertificates   []*x509.Certificate // Certificate chain presented by the server
+	extendedMasterSecret bool                // Whether an extended master secret was used to generate the session
 }
 
 // ClientSessionCache is a cache of ClientSessionState objects that can be used
@@ -472,6 +474,14 @@
 	// OversizedSessionId causes the session id that is sent with a ticket
 	// resumption attempt to be too large (33 bytes).
 	OversizedSessionId bool
+
+	// RequireExtendedMasterSecret, if true, requires that the peer support
+	// the extended master secret option.
+	RequireExtendedMasterSecret bool
+
+	// NoExtendedMasterSecret causes the client and server to behave is if
+	// they didn't support an extended master secret.
+	NoExtendedMasterSecret bool
 }
 
 func (c *Config) serverInit() {
diff --git a/ssl/test/runner/conn.go b/ssl/test/runner/conn.go
index 9f0c328..3ce6c76 100644
--- a/ssl/test/runner/conn.go
+++ b/ssl/test/runner/conn.go
@@ -29,16 +29,17 @@
 	isClient bool
 
 	// constant after handshake; protected by handshakeMutex
-	handshakeMutex    sync.Mutex // handshakeMutex < in.Mutex, out.Mutex, errMutex
-	handshakeErr      error      // error resulting from handshake
-	vers              uint16     // TLS version
-	haveVers          bool       // version has been negotiated
-	config            *Config    // configuration passed to constructor
-	handshakeComplete bool
-	didResume         bool // whether this connection was a session resumption
-	cipherSuite       uint16
-	ocspResponse      []byte // stapled OCSP response
-	peerCertificates  []*x509.Certificate
+	handshakeMutex       sync.Mutex // handshakeMutex < in.Mutex, out.Mutex, errMutex
+	handshakeErr         error      // error resulting from handshake
+	vers                 uint16     // TLS version
+	haveVers             bool       // version has been negotiated
+	config               *Config    // configuration passed to constructor
+	handshakeComplete    bool
+	didResume            bool // whether this connection was a session resumption
+	extendedMasterSecret bool // whether this session used an extended master secret
+	cipherSuite          uint16
+	ocspResponse         []byte // stapled OCSP response
+	peerCertificates     []*x509.Certificate
 	// verifiedChains contains the certificate chains that we built, as
 	// opposed to the ones presented by the server.
 	verifiedChains [][]*x509.Certificate
diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go
index f4cadc2..2f9fe12 100644
--- a/ssl/test/runner/handshake_client.go
+++ b/ssl/test/runner/handshake_client.go
@@ -56,26 +56,31 @@
 	}
 
 	hello := &clientHelloMsg{
-		isDTLS:              c.isDTLS,
-		vers:                c.config.maxVersion(),
-		compressionMethods:  []uint8{compressionNone},
-		random:              make([]byte, 32),
-		ocspStapling:        true,
-		serverName:          c.config.ServerName,
-		supportedCurves:     c.config.curvePreferences(),
-		supportedPoints:     []uint8{pointFormatUncompressed},
-		nextProtoNeg:        len(c.config.NextProtos) > 0,
-		secureRenegotiation: true,
-		alpnProtocols:       c.config.NextProtos,
-		duplicateExtension:  c.config.Bugs.DuplicateExtension,
-		channelIDSupported:  c.config.ChannelID != nil,
-		npnLast:             c.config.Bugs.SwapNPNAndALPN,
+		isDTLS:               c.isDTLS,
+		vers:                 c.config.maxVersion(),
+		compressionMethods:   []uint8{compressionNone},
+		random:               make([]byte, 32),
+		ocspStapling:         true,
+		serverName:           c.config.ServerName,
+		supportedCurves:      c.config.curvePreferences(),
+		supportedPoints:      []uint8{pointFormatUncompressed},
+		nextProtoNeg:         len(c.config.NextProtos) > 0,
+		secureRenegotiation:  true,
+		alpnProtocols:        c.config.NextProtos,
+		duplicateExtension:   c.config.Bugs.DuplicateExtension,
+		channelIDSupported:   c.config.ChannelID != nil,
+		npnLast:              c.config.Bugs.SwapNPNAndALPN,
+		extendedMasterSecret: c.config.maxVersion() >= VersionTLS10,
 	}
 
 	if c.config.Bugs.SendClientVersion != 0 {
 		hello.vers = c.config.Bugs.SendClientVersion
 	}
 
+	if c.config.Bugs.NoExtendedMasterSecret {
+		hello.extendedMasterSecret = false
+	}
+
 	possibleCipherSuites := c.config.cipherSuites()
 	hello.cipherSuites = make([]uint16, 0, len(possibleCipherSuites))
 
@@ -503,7 +508,15 @@
 		c.writeRecord(recordTypeHandshake, ckx.marshal())
 	}
 
-	hs.masterSecret = masterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret, hs.hello.random, hs.serverHello.random)
+	if hs.serverHello.extendedMasterSecret && c.vers >= VersionTLS10 {
+		hs.masterSecret = extendedMasterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret, hs.finishedHash)
+		c.extendedMasterSecret = true
+	} else {
+		if c.config.Bugs.RequireExtendedMasterSecret {
+			return errors.New("tls: extended master secret required but not supported by peer")
+		}
+		hs.masterSecret = masterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret, hs.hello.random, hs.serverHello.random)
+	}
 
 	if chainToSend != nil {
 		var signed []byte
@@ -629,6 +642,7 @@
 		// Restore masterSecret and peerCerts from previous state
 		hs.masterSecret = hs.session.masterSecret
 		c.peerCertificates = hs.session.serverCertificates
+		c.extendedMasterSecret = hs.session.extendedMasterSecret
 		hs.finishedHash.discardHandshakeBuffer()
 		return true, nil
 	}
diff --git a/ssl/test/runner/handshake_messages.go b/ssl/test/runner/handshake_messages.go
index 136360d..1114a6f 100644
--- a/ssl/test/runner/handshake_messages.go
+++ b/ssl/test/runner/handshake_messages.go
@@ -7,27 +7,28 @@
 import "bytes"
 
 type clientHelloMsg struct {
-	raw                 []byte
-	isDTLS              bool
-	vers                uint16
-	random              []byte
-	sessionId           []byte
-	cookie              []byte
-	cipherSuites        []uint16
-	compressionMethods  []uint8
-	nextProtoNeg        bool
-	serverName          string
-	ocspStapling        bool
-	supportedCurves     []CurveID
-	supportedPoints     []uint8
-	ticketSupported     bool
-	sessionTicket       []uint8
-	signatureAndHashes  []signatureAndHash
-	secureRenegotiation bool
-	alpnProtocols       []string
-	duplicateExtension  bool
-	channelIDSupported  bool
-	npnLast             bool
+	raw                  []byte
+	isDTLS               bool
+	vers                 uint16
+	random               []byte
+	sessionId            []byte
+	cookie               []byte
+	cipherSuites         []uint16
+	compressionMethods   []uint8
+	nextProtoNeg         bool
+	serverName           string
+	ocspStapling         bool
+	supportedCurves      []CurveID
+	supportedPoints      []uint8
+	ticketSupported      bool
+	sessionTicket        []uint8
+	signatureAndHashes   []signatureAndHash
+	secureRenegotiation  bool
+	alpnProtocols        []string
+	duplicateExtension   bool
+	channelIDSupported   bool
+	npnLast              bool
+	extendedMasterSecret bool
 }
 
 func (m *clientHelloMsg) equal(i interface{}) bool {
@@ -56,7 +57,8 @@
 		eqStrings(m.alpnProtocols, m1.alpnProtocols) &&
 		m.duplicateExtension == m1.duplicateExtension &&
 		m.channelIDSupported == m1.channelIDSupported &&
-		m.npnLast == m1.npnLast
+		m.npnLast == m1.npnLast &&
+		m.extendedMasterSecret == m1.extendedMasterSecret
 }
 
 func (m *clientHelloMsg) marshal() []byte {
@@ -118,6 +120,9 @@
 		}
 		numExtensions++
 	}
+	if m.extendedMasterSecret {
+		numExtensions++
+	}
 	if numExtensions > 0 {
 		extensionsLength += 4 * numExtensions
 		length += 2 + extensionsLength
@@ -319,6 +324,12 @@
 		z[1] = 0xff
 		z = z[4:]
 	}
+	if m.extendedMasterSecret {
+		// https://tools.ietf.org/html/draft-ietf-tls-session-hash-01
+		z[0] = byte(extensionExtendedMasterSecret >> 8)
+		z[1] = byte(extensionExtendedMasterSecret & 0xff)
+		z = z[4:]
+	}
 
 	m.raw = x
 
@@ -385,6 +396,7 @@
 	m.sessionTicket = nil
 	m.signatureAndHashes = nil
 	m.alpnProtocols = nil
+	m.extendedMasterSecret = false
 
 	if len(data) == 0 {
 		// ClientHello is optionally followed by extension data
@@ -517,6 +529,11 @@
 				return false
 			}
 			m.channelIDSupported = true
+		case extensionExtendedMasterSecret:
+			if length != 0 {
+				return false
+			}
+			m.extendedMasterSecret = true
 		}
 		data = data[length:]
 	}
@@ -525,21 +542,22 @@
 }
 
 type serverHelloMsg struct {
-	raw                 []byte
-	isDTLS              bool
-	vers                uint16
-	random              []byte
-	sessionId           []byte
-	cipherSuite         uint16
-	compressionMethod   uint8
-	nextProtoNeg        bool
-	nextProtos          []string
-	ocspStapling        bool
-	ticketSupported     bool
-	secureRenegotiation bool
-	alpnProtocol        string
-	duplicateExtension  bool
-	channelIDRequested  bool
+	raw                  []byte
+	isDTLS               bool
+	vers                 uint16
+	random               []byte
+	sessionId            []byte
+	cipherSuite          uint16
+	compressionMethod    uint8
+	nextProtoNeg         bool
+	nextProtos           []string
+	ocspStapling         bool
+	ticketSupported      bool
+	secureRenegotiation  bool
+	alpnProtocol         string
+	duplicateExtension   bool
+	channelIDRequested   bool
+	extendedMasterSecret bool
 }
 
 func (m *serverHelloMsg) equal(i interface{}) bool {
@@ -562,7 +580,8 @@
 		m.secureRenegotiation == m1.secureRenegotiation &&
 		m.alpnProtocol == m1.alpnProtocol &&
 		m.duplicateExtension == m1.duplicateExtension &&
-		m.channelIDRequested == m1.channelIDRequested
+		m.channelIDRequested == m1.channelIDRequested &&
+		m.extendedMasterSecret == m1.extendedMasterSecret
 }
 
 func (m *serverHelloMsg) marshal() []byte {
@@ -606,6 +625,9 @@
 		extensionsLength += 2 + 1 + alpnLen
 		numExtensions++
 	}
+	if m.extendedMasterSecret {
+		numExtensions++
+	}
 
 	if numExtensions > 0 {
 		extensionsLength += 4 * numExtensions
@@ -699,6 +721,11 @@
 		z[1] = 0xff
 		z = z[4:]
 	}
+	if m.extendedMasterSecret {
+		z[0] = byte(extensionExtendedMasterSecret >> 8)
+		z[1] = byte(extensionExtendedMasterSecret & 0xff)
+		z = z[4:]
+	}
 
 	m.raw = x
 
@@ -730,6 +757,7 @@
 	m.ocspStapling = false
 	m.ticketSupported = false
 	m.alpnProtocol = ""
+	m.extendedMasterSecret = false
 
 	if len(data) == 0 {
 		// ServerHello is optionally followed by extension data
@@ -805,6 +833,11 @@
 				return false
 			}
 			m.channelIDRequested = true
+		case extensionExtendedMasterSecret:
+			if length != 0 {
+				return false
+			}
+			m.extendedMasterSecret = true
 		}
 		data = data[length:]
 	}
diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go
index 1eb3f11..645a67c 100644
--- a/ssl/test/runner/handshake_server.go
+++ b/ssl/test/runner/handshake_server.go
@@ -237,6 +237,7 @@
 			hs.hello.nextProtos = config.NextProtos
 		}
 	}
+	hs.hello.extendedMasterSecret = c.vers >= VersionTLS10 && hs.clientHello.extendedMasterSecret && !c.config.Bugs.NoExtendedMasterSecret
 
 	if len(config.Certificates) == 0 {
 		c.sendAlert(alertInternalError)
@@ -373,6 +374,7 @@
 	}
 
 	hs.masterSecret = hs.sessionState.masterSecret
+	c.extendedMasterSecret = hs.sessionState.extendedMasterSecret
 
 	return nil
 }
@@ -387,6 +389,7 @@
 
 	hs.hello.ticketSupported = hs.clientHello.ticketSupported && !config.SessionTicketsDisabled
 	hs.hello.cipherSuite = hs.suite.id
+	c.extendedMasterSecret = hs.hello.extendedMasterSecret
 
 	hs.finishedHash = newFinishedHash(c.vers, hs.suite)
 	hs.writeClientHash(hs.clientHello.marshal())
@@ -502,7 +505,14 @@
 		c.sendAlert(alertHandshakeFailure)
 		return err
 	}
-	hs.masterSecret = masterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret, hs.clientHello.random, hs.hello.random)
+	if c.extendedMasterSecret {
+		hs.masterSecret = extendedMasterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret, hs.finishedHash)
+	} else {
+		if c.config.Bugs.RequireExtendedMasterSecret {
+			return errors.New("tls: extended master secret required but not supported by peer")
+		}
+		hs.masterSecret = masterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret, hs.clientHello.random, hs.hello.random)
+	}
 
 	// If we received a client cert in response to our certificate request message,
 	// the client will send us a certificateVerifyMsg immediately after the
diff --git a/ssl/test/runner/prf.go b/ssl/test/runner/prf.go
index 6d0db97..d45c080 100644
--- a/ssl/test/runner/prf.go
+++ b/ssl/test/runner/prf.go
@@ -117,6 +117,7 @@
 )
 
 var masterSecretLabel = []byte("master secret")
+var extendedMasterSecretLabel = []byte("extended master secret")
 var keyExpansionLabel = []byte("key expansion")
 var clientFinishedLabel = []byte("client finished")
 var serverFinishedLabel = []byte("server finished")
@@ -150,6 +151,15 @@
 	return masterSecret
 }
 
+// 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.
@@ -221,6 +231,16 @@
 	return len(msg), nil
 }
 
+func (h finishedHash) Sum() []byte {
+	if h.version >= VersionTLS12 {
+		return h.client.Sum(nil)
+	}
+
+	out := make([]byte, 0, md5.Size+sha1.Size)
+	out = h.clientMD5.Sum(out)
+	return h.client.Sum(out)
+}
+
 // finishedSum30 calculates the contents of the verify_data member of a SSLv3
 // Finished message given the MD5 and SHA1 hashes of a set of handshake
 // messages.
@@ -264,15 +284,7 @@
 	}
 
 	out := make([]byte, finishedVerifyLength)
-	if h.version >= VersionTLS12 {
-		seed := h.client.Sum(nil)
-		h.prf(out, masterSecret, clientFinishedLabel, seed)
-	} else {
-		seed := make([]byte, 0, md5.Size+sha1.Size)
-		seed = h.clientMD5.Sum(seed)
-		seed = h.client.Sum(seed)
-		h.prf(out, masterSecret, clientFinishedLabel, seed)
-	}
+	h.prf(out, masterSecret, clientFinishedLabel, h.Sum())
 	return out
 }
 
@@ -284,15 +296,7 @@
 	}
 
 	out := make([]byte, finishedVerifyLength)
-	if h.version >= VersionTLS12 {
-		seed := h.server.Sum(nil)
-		h.prf(out, masterSecret, serverFinishedLabel, seed)
-	} else {
-		seed := make([]byte, 0, md5.Size+sha1.Size)
-		seed = h.serverMD5.Sum(seed)
-		seed = h.server.Sum(seed)
-		h.prf(out, masterSecret, serverFinishedLabel, seed)
-	}
+	h.prf(out, masterSecret, serverFinishedLabel, h.Sum())
 	return out
 }
 
@@ -334,14 +338,10 @@
 		return digest[:], crypto.SHA256, nil
 	}
 	if signatureAndHash.signature == signatureECDSA {
-		digest := h.server.Sum(nil)
-		return digest, crypto.SHA1, nil
+		return h.server.Sum(nil), crypto.SHA1, nil
 	}
 
-	digest := make([]byte, 0, 36)
-	digest = h.serverMD5.Sum(digest)
-	digest = h.server.Sum(digest)
-	return digest, crypto.MD5SHA1, nil
+	return h.Sum(), crypto.MD5SHA1, nil
 }
 
 // hashForChannelID returns the hash to be signed for TLS Channel
diff --git a/ssl/test/runner/recordingconn.go b/ssl/test/runner/recordingconn.go
new file mode 100644
index 0000000..a67fa48
--- /dev/null
+++ b/ssl/test/runner/recordingconn.go
@@ -0,0 +1,130 @@
+package main
+
+import (
+	"bufio"
+	"encoding/hex"
+	"errors"
+	"fmt"
+	"io"
+	"net"
+	"strconv"
+	"strings"
+	"sync"
+)
+
+// recordingConn is a net.Conn that records the traffic that passes through it.
+// WriteTo can be used to produce output that can be later be loaded with
+// ParseTestData.
+type recordingConn struct {
+	net.Conn
+	sync.Mutex
+	flows   [][]byte
+	reading bool
+}
+
+func (r *recordingConn) Read(b []byte) (n int, err error) {
+	if n, err = r.Conn.Read(b); n == 0 {
+		return
+	}
+	b = b[:n]
+
+	r.Lock()
+	defer r.Unlock()
+
+	if l := len(r.flows); l == 0 || !r.reading {
+		buf := make([]byte, len(b))
+		copy(buf, b)
+		r.flows = append(r.flows, buf)
+	} else {
+		r.flows[l-1] = append(r.flows[l-1], b[:n]...)
+	}
+	r.reading = true
+	return
+}
+
+func (r *recordingConn) Write(b []byte) (n int, err error) {
+	if n, err = r.Conn.Write(b); n == 0 {
+		return
+	}
+	b = b[:n]
+
+	r.Lock()
+	defer r.Unlock()
+
+	if l := len(r.flows); l == 0 || r.reading {
+		buf := make([]byte, len(b))
+		copy(buf, b)
+		r.flows = append(r.flows, buf)
+	} else {
+		r.flows[l-1] = append(r.flows[l-1], b[:n]...)
+	}
+	r.reading = false
+	return
+}
+
+// WriteTo writes hex dumps to w that contains the recorded traffic.
+func (r *recordingConn) WriteTo(w io.Writer) {
+	// TLS always starts with a client to server flow.
+	clientToServer := true
+
+	for i, flow := range r.flows {
+		source, dest := "client", "server"
+		if !clientToServer {
+			source, dest = dest, source
+		}
+		fmt.Fprintf(w, ">>> Flow %d (%s to %s)\n", i+1, source, dest)
+		dumper := hex.Dumper(w)
+		dumper.Write(flow)
+		dumper.Close()
+		clientToServer = !clientToServer
+	}
+}
+
+func parseTestData(r io.Reader) (flows [][]byte, err error) {
+	var currentFlow []byte
+
+	scanner := bufio.NewScanner(r)
+	for scanner.Scan() {
+		line := scanner.Text()
+		// If the line starts with ">>> " then it marks the beginning
+		// of a new flow.
+		if strings.HasPrefix(line, ">>> ") {
+			if len(currentFlow) > 0 || len(flows) > 0 {
+				flows = append(flows, currentFlow)
+				currentFlow = nil
+			}
+			continue
+		}
+
+		// Otherwise the line is a line of hex dump that looks like:
+		// 00000170  fc f5 06 bf (...)  |.....X{&?......!|
+		// (Some bytes have been omitted from the middle section.)
+
+		if i := strings.IndexByte(line, ' '); i >= 0 {
+			line = line[i:]
+		} else {
+			return nil, errors.New("invalid test data")
+		}
+
+		if i := strings.IndexByte(line, '|'); i >= 0 {
+			line = line[:i]
+		} else {
+			return nil, errors.New("invalid test data")
+		}
+
+		hexBytes := strings.Fields(line)
+		for _, hexByte := range hexBytes {
+			val, err := strconv.ParseUint(hexByte, 16, 8)
+			if err != nil {
+				return nil, errors.New("invalid hex byte in test data: " + err.Error())
+			}
+			currentFlow = append(currentFlow, byte(val))
+		}
+	}
+
+	if len(currentFlow) > 0 {
+		flows = append(flows, currentFlow)
+	}
+
+	return flows, nil
+}
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index b4c2e61..10f86c9 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -22,6 +22,8 @@
 )
 
 var useValgrind = flag.Bool("valgrind", false, "If true, run code under valgrind")
+var useGDB = flag.Bool("gdb", false, "If true, run BoringSSL code under gdb")
+var flagDebug *bool = flag.Bool("debug", false, "Hexdump the contents of the connection")
 
 const (
 	rsaCertificateFile   = "cert.pem"
@@ -693,10 +695,11 @@
 	var shim *exec.Cmd
 	if *useValgrind {
 		shim = valgrindOf(false, shim_path, flags...)
+	} else if *useGDB {
+		shim = gdbOf(shim_path, flags...)
 	} else {
 		shim = exec.Command(shim_path, flags...)
 	}
-	// shim = gdbOf(shim_path, flags...)
 	shim.ExtraFiles = []*os.File{shimEnd, shimEndResume}
 	shim.Stdin = os.Stdin
 	var stdoutBuf, stderrBuf bytes.Buffer
@@ -717,8 +720,19 @@
 		}
 	}
 
+	var connDebug *recordingConn
+	if *flagDebug {
+		connDebug = &recordingConn{Conn: conn}
+		conn = connDebug
+	}
+
 	err := doExchange(test, &config, conn, test.messageLen,
 		false /* not a resumption */)
+
+	if *flagDebug {
+		connDebug.WriteTo(os.Stdout)
+	}
+
 	conn.Close()
 	if err == nil && test.resumeSession {
 		var resumeConfig Config
@@ -1070,6 +1084,62 @@
 	}
 }
 
+func addExtendedMasterSecretTests() {
+	const expectEMSFlag = "-expect-extended-master-secret"
+
+	for _, with := range []bool{false, true} {
+		prefix := "No"
+		var flags []string
+		if with {
+			prefix = ""
+			flags = []string{expectEMSFlag}
+		}
+
+		for _, isClient := range []bool{false, true} {
+			suffix := "-Server"
+			testType := serverTest
+			if isClient {
+				suffix = "-Client"
+				testType = clientTest
+			}
+
+			for _, ver := range tlsVersions {
+				test := testCase{
+					testType: testType,
+					name:     prefix + "ExtendedMasterSecret-" + ver.name + suffix,
+					config: Config{
+						MinVersion: ver.version,
+						MaxVersion: ver.version,
+						Bugs: ProtocolBugs{
+							NoExtendedMasterSecret:      !with,
+							RequireExtendedMasterSecret: with,
+						},
+					},
+					flags:              flags,
+					shouldFail:         ver.version == VersionSSL30 && with,
+				}
+				if test.shouldFail {
+					test.expectedLocalError = "extended master secret required but not supported by peer"
+				}
+				testCases = append(testCases, test)
+			}
+		}
+	}
+
+	// When a session is resumed, it should still be aware that its master
+	// secret was generated via EMS and thus it's safe to use tls-unique.
+	testCases = append(testCases, testCase{
+		name: "ExtendedMasterSecret-Resume",
+		config: Config{
+			Bugs: ProtocolBugs{
+				RequireExtendedMasterSecret: true,
+			},
+		},
+		flags:         []string{expectEMSFlag},
+		resumeSession: true,
+	})
+}
+
 // Adds tests that try to cover the range of the handshake state machine, under
 // various conditions. Some of these are redundant with other tests, but they
 // only cover the synchronous case.
@@ -1568,7 +1638,7 @@
 			},
 		},
 		resumeSession: true,
-		shouldFail: true,
+		shouldFail:    true,
 		expectedError: ":DECODE_ERROR:",
 	})
 }
@@ -1690,6 +1760,7 @@
 	addD5BugTests()
 	addExtensionTests()
 	addResumptionVersionTests()
+	addExtendedMasterSecretTests()
 	for _, async := range []bool{false, true} {
 		for _, splitHandshake := range []bool{false, true} {
 			for _, protocol := range []protocol{tls, dtls} {
diff --git a/ssl/test/runner/ticket.go b/ssl/test/runner/ticket.go
index 74791d6..8355822 100644
--- a/ssl/test/runner/ticket.go
+++ b/ssl/test/runner/ticket.go
@@ -18,11 +18,12 @@
 // sessionState contains the information that is serialized into a session
 // ticket in order to later resume a connection.
 type sessionState struct {
-	vers          uint16
-	cipherSuite   uint16
-	masterSecret  []byte
-	handshakeHash []byte
-	certificates  [][]byte
+	vers                 uint16
+	cipherSuite          uint16
+	masterSecret         []byte
+	handshakeHash        []byte
+	certificates         [][]byte
+	extendedMasterSecret bool
 }
 
 func (s *sessionState) equal(i interface{}) bool {
@@ -34,7 +35,8 @@
 	if s.vers != s1.vers ||
 		s.cipherSuite != s1.cipherSuite ||
 		!bytes.Equal(s.masterSecret, s1.masterSecret) ||
-		!bytes.Equal(s.handshakeHash, s1.handshakeHash) {
+		!bytes.Equal(s.handshakeHash, s1.handshakeHash) ||
+		s.extendedMasterSecret != s1.extendedMasterSecret {
 		return false
 	}
 
@@ -56,6 +58,7 @@
 	for _, cert := range s.certificates {
 		length += 4 + len(cert)
 	}
+	length++
 
 	ret := make([]byte, length)
 	x := ret
@@ -88,6 +91,11 @@
 		x = x[4+len(cert):]
 	}
 
+	if s.extendedMasterSecret {
+		x[0] = 1
+	}
+	x = x[1:]
+
 	return ret
 }
 
@@ -144,6 +152,16 @@
 		data = data[certLen:]
 	}
 
+	if len(data) < 1 {
+		return false
+	}
+
+	s.extendedMasterSecret = false
+	if data[0] == 1 {
+		s.extendedMasterSecret = true
+	}
+	data = data[1:]
+
 	if len(data) > 0 {
 		return false
 	}
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc
index 270fbfb..737c78d 100644
--- a/ssl/test/test_config.cc
+++ b/ssl/test/test_config.cc
@@ -57,6 +57,8 @@
   { "-shim-writes-first", &TestConfig::shim_writes_first },
   { "-tls-d5-bug", &TestConfig::tls_d5_bug },
   { "-expect-session-miss", &TestConfig::expect_session_miss },
+  { "-expect-extended-master-secret",
+    &TestConfig::expect_extended_master_secret },
 };
 
 const size_t kNumBoolFlags = sizeof(kBoolFlags) / sizeof(kBoolFlags[0]);
@@ -105,7 +107,8 @@
       cookie_exchange(false),
       shim_writes_first(false),
       tls_d5_bug(false),
-      expect_session_miss(false) {
+      expect_session_miss(false),
+      expect_extended_master_secret(false) {
 }
 
 bool ParseConfig(int argc, char **argv, TestConfig *out_config) {
diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h
index acce504..d7f1be8 100644
--- a/ssl/test/test_config.h
+++ b/ssl/test/test_config.h
@@ -53,6 +53,7 @@
   std::string expected_advertised_alpn;
   std::string select_alpn;
   bool expect_session_miss;
+  bool expect_extended_master_secret;
 };
 
 bool ParseConfig(int argc, char **argv, TestConfig *out_config);