Add a test to assert parsing V2ClientHellos works.

Should have test coverage there as long as we care about supporting it.

Change-Id: Ic67539228b550f2ebd0b543d5a58640913b0474b
Reviewed-on: https://boringssl-review.googlesource.com/1371
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index 7b8d964..1cfdda9 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -383,6 +383,10 @@
 	// SkipNewSessionTicket causes the server to skip sending the
 	// NewSessionTicket message despite promising to in ServerHello.
 	SkipNewSessionTicket bool
+
+	// SendV2ClientHello causes the client to send a V2ClientHello
+	// instead of a normal ClientHello.
+	SendV2ClientHello bool
 }
 
 func (c *Config) serverInit() {
diff --git a/ssl/test/runner/conn.go b/ssl/test/runner/conn.go
index 49224a2..02ed8f0 100644
--- a/ssl/test/runner/conn.go
+++ b/ssl/test/runner/conn.go
@@ -700,6 +700,15 @@
 	return c.sendAlertLocked(err)
 }
 
+// writeV2Record writes a record for a V2ClientHello.
+func (c *Conn) writeV2Record(data []byte) (n int, err error) {
+	record := make([]byte, 2+len(data))
+	record[0] = uint8(len(data)>>8) | 0x80
+	record[1] = uint8(len(data))
+	copy(record[2:], data)
+	return c.conn.Write(record)
+}
+
 // writeRecord writes a TLS record with the given type and payload
 // to the connection and updates the record layer state.
 // c.out.Mutex <= L.
diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go
index 80208dd..0b9d772 100644
--- a/ssl/test/runner/handshake_client.go
+++ b/ssl/test/runner/handshake_client.go
@@ -126,7 +126,21 @@
 		}
 	}
 
-	c.writeRecord(recordTypeHandshake, hello.marshal())
+	var helloBytes []byte
+	if c.config.Bugs.SendV2ClientHello {
+		v2Hello := &v2ClientHelloMsg{
+			vers:         hello.vers,
+			cipherSuites: hello.cipherSuites,
+			// No session resumption for V2ClientHello.
+			sessionId: nil,
+			challenge: hello.random,
+		}
+		helloBytes = v2Hello.marshal()
+		c.writeV2Record(helloBytes)
+	} else {
+		helloBytes = hello.marshal()
+		c.writeRecord(recordTypeHandshake, helloBytes)
+	}
 
 	msg, err := c.readHandshake()
 	if err != nil {
@@ -162,7 +176,7 @@
 		session:      session,
 	}
 
-	hs.finishedHash.Write(hs.hello.marshal())
+	hs.finishedHash.Write(helloBytes)
 	hs.finishedHash.Write(hs.serverHello.marshal())
 
 	if c.config.Bugs.EarlyChangeCipherSpec > 0 {
diff --git a/ssl/test/runner/handshake_messages.go b/ssl/test/runner/handshake_messages.go
index edb45d8..1c633bb 100644
--- a/ssl/test/runner/handshake_messages.go
+++ b/ssl/test/runner/handshake_messages.go
@@ -1314,6 +1314,60 @@
 	return true
 }
 
+type v2ClientHelloMsg struct {
+	raw          []byte
+	vers         uint16
+	cipherSuites []uint16
+	sessionId    []byte
+	challenge    []byte
+}
+
+func (m *v2ClientHelloMsg) equal(i interface{}) bool {
+	m1, ok := i.(*v2ClientHelloMsg)
+	if !ok {
+		return false
+	}
+
+	return bytes.Equal(m.raw, m1.raw) &&
+		m.vers == m1.vers &&
+		eqUint16s(m.cipherSuites, m1.cipherSuites) &&
+		bytes.Equal(m.sessionId, m1.sessionId) &&
+		bytes.Equal(m.challenge, m1.challenge)
+}
+
+func (m *v2ClientHelloMsg) marshal() []byte {
+	if m.raw != nil {
+		return m.raw
+	}
+
+	length := 1 + 2 + 2 + 2 + 2 + len(m.cipherSuites)*3 + len(m.sessionId) + len(m.challenge)
+
+	x := make([]byte, length)
+	x[0] = 1
+	x[1] = uint8(m.vers >> 8)
+	x[2] = uint8(m.vers)
+	x[3] = uint8((len(m.cipherSuites) * 3) >> 8)
+	x[4] = uint8(len(m.cipherSuites) * 3)
+	x[5] = uint8(len(m.sessionId) >> 8)
+	x[6] = uint8(len(m.sessionId))
+	x[7] = uint8(len(m.challenge) >> 8)
+	x[8] = uint8(len(m.challenge))
+	y := x[9:]
+	for i, spec := range m.cipherSuites {
+		y[i*3] = 0
+		y[i*3+1] = uint8(spec >> 8)
+		y[i*3+2] = uint8(spec)
+	}
+	y = y[len(m.cipherSuites)*3:]
+	copy(y, m.sessionId)
+	y = y[len(m.sessionId):]
+	copy(y, m.challenge)
+
+	m.raw = x
+
+	return x
+}
+
 func eqUint16s(x, y []uint16) bool {
 	if len(x) != len(y) {
 		return false
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index eedb2f1..bec89b1 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -347,7 +347,7 @@
 		name: "FalseStart",
 		config: Config{
 			CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
-			NextProtos: []string{"foo"},
+			NextProtos:   []string{"foo"},
 		},
 		flags: []string{
 			"-false-start",
@@ -358,8 +358,8 @@
 	{
 		name: "FalseStart-SessionTicketsDisabled",
 		config: Config{
-			CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
-			NextProtos: []string{"foo"},
+			CipherSuites:           []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+			NextProtos:             []string{"foo"},
 			SessionTicketsDisabled: true,
 		},
 		flags: []string{
@@ -367,6 +367,19 @@
 			"-select-next-proto", "foo",
 		},
 	},
+	{
+		testType: serverTest,
+		name:     "SendV2ClientHello",
+		config: Config{
+			// Choose a cipher suite that does not involve
+			// elliptic curves, so no extensions are
+			// involved.
+			CipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA},
+			Bugs: ProtocolBugs{
+				SendV2ClientHello: true,
+			},
+		},
+	},
 }
 
 func doExchange(testType testType, config *Config, conn net.Conn, messageLen int) error {