Implement draft 16 HelloRetryRequest and cookie.

We'll never send cookies, but we'll echo them on request. Implement it
in runner as well and test.

BUG=98

Change-Id: Idd3799f1eaccd52ac42f5e2e5ae07c209318c270
Reviewed-on: https://boringssl-review.googlesource.com/11565
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Steven Valdez <svaldez@google.com>
Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go
index 7b26cb6..eea6bf5 100644
--- a/ssl/test/runner/handshake_server.go
+++ b/ssl/test/runner/handshake_server.go
@@ -543,11 +543,32 @@
 		hs.hello.hasKeyShare = false
 	}
 
-	// Resolve ECDHE and compute the handshake secret.
-	var ecdheSecret []byte
+	firstHelloRetryRequest := true
+
+ResendHelloRetryRequest:
+	var sendHelloRetryRequest bool
+	helloRetryRequest := &helloRetryRequestMsg{
+		vers:                versionToWire(c.vers, c.isDTLS),
+		duplicateExtensions: config.Bugs.DuplicateHelloRetryRequestExtensions,
+	}
+
+	if config.Bugs.AlwaysSendHelloRetryRequest {
+		sendHelloRetryRequest = true
+	}
+
+	if config.Bugs.SendHelloRetryRequestCookie != nil {
+		sendHelloRetryRequest = true
+		helloRetryRequest.cookie = config.Bugs.SendHelloRetryRequestCookie
+	}
+
+	if len(config.Bugs.CustomHelloRetryRequestExtension) > 0 {
+		sendHelloRetryRequest = true
+		helloRetryRequest.customExtension = config.Bugs.CustomHelloRetryRequestExtension
+	}
+
+	var selectedKeyShare *keyShareEntry
 	if hs.hello.hasKeyShare {
 		// Look for the key share corresponding to our selected curve.
-		var selectedKeyShare *keyShareEntry
 		for i := range hs.clientHello.keyShares {
 			if hs.clientHello.keyShares[i].group == selectedCurve {
 				selectedKeyShare = &hs.clientHello.keyShares[i]
@@ -559,67 +580,80 @@
 			return errors.New("tls: expected missing key share")
 		}
 
-		sendHelloRetryRequest := selectedKeyShare == nil
-		if config.Bugs.UnnecessaryHelloRetryRequest {
+		if selectedKeyShare == nil {
+			helloRetryRequest.hasSelectedGroup = true
+			helloRetryRequest.selectedGroup = selectedCurve
 			sendHelloRetryRequest = true
 		}
-		if config.Bugs.SkipHelloRetryRequest {
-			sendHelloRetryRequest = false
+	}
+
+	if config.Bugs.SendHelloRetryRequestCurve != 0 {
+		helloRetryRequest.hasSelectedGroup = true
+		helloRetryRequest.selectedGroup = config.Bugs.SendHelloRetryRequestCurve
+		sendHelloRetryRequest = true
+	}
+
+	if config.Bugs.SkipHelloRetryRequest {
+		sendHelloRetryRequest = false
+	}
+
+	if sendHelloRetryRequest {
+		hs.writeServerHash(helloRetryRequest.marshal())
+		c.writeRecord(recordTypeHandshake, helloRetryRequest.marshal())
+		c.flushHandshake()
+
+		// Read new ClientHello.
+		newMsg, err := c.readHandshake()
+		if err != nil {
+			return err
 		}
-		if sendHelloRetryRequest {
-			firstTime := true
-		ResendHelloRetryRequest:
-			// Send HelloRetryRequest.
-			helloRetryRequestMsg := helloRetryRequestMsg{
-				vers:          versionToWire(c.vers, c.isDTLS),
-				cipherSuite:   hs.hello.cipherSuite,
-				selectedGroup: selectedCurve,
-			}
-			if config.Bugs.SendHelloRetryRequestCurve != 0 {
-				helloRetryRequestMsg.selectedGroup = config.Bugs.SendHelloRetryRequestCurve
-			}
-			hs.writeServerHash(helloRetryRequestMsg.marshal())
-			c.writeRecord(recordTypeHandshake, helloRetryRequestMsg.marshal())
-			c.flushHandshake()
+		newClientHello, ok := newMsg.(*clientHelloMsg)
+		if !ok {
+			c.sendAlert(alertUnexpectedMessage)
+			return unexpectedMessageError(newClientHello, newMsg)
+		}
+		hs.writeClientHash(newClientHello.marshal())
 
-			// Read new ClientHello.
-			newMsg, err := c.readHandshake()
-			if err != nil {
-				return err
-			}
-			newClientHello, ok := newMsg.(*clientHelloMsg)
-			if !ok {
-				c.sendAlert(alertUnexpectedMessage)
-				return unexpectedMessageError(newClientHello, newMsg)
-			}
-			hs.writeClientHash(newClientHello.marshal())
+		// Check that the new ClientHello matches the old ClientHello,
+		// except for relevant modifications.
+		//
+		// TODO(davidben): Make this check more precise.
+		oldClientHelloCopy := *hs.clientHello
+		oldClientHelloCopy.raw = nil
+		oldClientHelloCopy.hasEarlyData = false
+		oldClientHelloCopy.earlyDataContext = nil
+		newClientHelloCopy := *newClientHello
+		newClientHelloCopy.raw = nil
 
-			// Check that the new ClientHello matches the old ClientHello, except for
-			// the addition of the new KeyShareEntry at the end of the list, and
-			// removing the EarlyDataIndication extension (if present).
-			newKeyShares := newClientHello.keyShares
-			if len(newKeyShares) == 0 || newKeyShares[len(newKeyShares)-1].group != selectedCurve {
+		if helloRetryRequest.hasSelectedGroup {
+			newKeyShares := newClientHelloCopy.keyShares
+			if len(newKeyShares) == 0 || newKeyShares[len(newKeyShares)-1].group != helloRetryRequest.selectedGroup {
 				return errors.New("tls: KeyShare from HelloRetryRequest not present in new ClientHello")
 			}
-			oldClientHelloCopy := *hs.clientHello
-			oldClientHelloCopy.raw = nil
-			oldClientHelloCopy.hasEarlyData = false
-			oldClientHelloCopy.earlyDataContext = nil
-			newClientHelloCopy := *newClientHello
-			newClientHelloCopy.raw = nil
-			newClientHelloCopy.keyShares = newKeyShares[:len(newKeyShares)-1]
-			if !oldClientHelloCopy.equal(&newClientHelloCopy) {
-				return errors.New("tls: new ClientHello does not match")
-			}
-
-			if firstTime && config.Bugs.SecondHelloRetryRequest {
-				firstTime = false
-				goto ResendHelloRetryRequest
-			}
-
 			selectedKeyShare = &newKeyShares[len(newKeyShares)-1]
+			newClientHelloCopy.keyShares = newKeyShares[:len(newKeyShares)-1]
 		}
 
+		if len(helloRetryRequest.cookie) > 0 {
+			if !bytes.Equal(newClientHelloCopy.tls13Cookie, helloRetryRequest.cookie) {
+				return errors.New("tls: cookie from HelloRetryRequest not present in new ClientHello")
+			}
+			newClientHelloCopy.tls13Cookie = nil
+		}
+
+		if !oldClientHelloCopy.equal(&newClientHelloCopy) {
+			return errors.New("tls: new ClientHello does not match")
+		}
+
+		if firstHelloRetryRequest && config.Bugs.SecondHelloRetryRequest {
+			firstHelloRetryRequest = false
+			goto ResendHelloRetryRequest
+		}
+	}
+
+	// Resolve ECDHE and compute the handshake secret.
+	var ecdheSecret []byte
+	if hs.hello.hasKeyShare {
 		// Once a curve has been selected and a key share identified,
 		// the server needs to generate a public value and send it in
 		// the ServerHello.