Add tests for client version negotiation and session resumption.

BUG=chromium:417134

Change-Id: If5914be98026d899000fde267b2d329861ca3136
Reviewed-on: https://boringssl-review.googlesource.com/1822
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index 7dbc1f0..cf244bc 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -460,6 +460,10 @@
 	// ALPN on the server. This is to test that server preference
 	// of ALPN works regardless of their relative order.
 	SwapNPNAndALPN bool
+
+	// AllowSessionVersionMismatch causes the server to resume sessions
+	// regardless of the version associated with the session.
+	AllowSessionVersionMismatch bool
 }
 
 func (c *Config) serverInit() {
diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go
index b03f165..1eb3f11 100644
--- a/ssl/test/runner/handshake_server.go
+++ b/ssl/test/runner/handshake_server.go
@@ -311,11 +311,13 @@
 		return false
 	}
 
-	if hs.sessionState.vers > hs.clientHello.vers {
-		return false
-	}
-	if vers, ok := c.config.mutualVersion(hs.sessionState.vers); !ok || vers != hs.sessionState.vers {
-		return false
+	if !c.config.Bugs.AllowSessionVersionMismatch {
+		if hs.sessionState.vers > hs.clientHello.vers {
+			return false
+		}
+		if vers, ok := c.config.mutualVersion(hs.sessionState.vers); !ok || vers != hs.sessionState.vers {
+			return false
+		}
 	}
 
 	cipherSuiteOk := false
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 7a87b91..323f43f 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -115,6 +115,9 @@
 	// expectedVersion, if non-zero, specifies the TLS version that must be
 	// negotiated.
 	expectedVersion uint16
+	// expectedResumeVersion, if non-zero, specifies the TLS version that
+	// must be negotiated on resumption. If zero, expectedVersion is used.
+	expectedResumeVersion uint16
 	// expectChannelID controls whether the connection should have
 	// negotiated a Channel ID with channelIDKey.
 	expectChannelID bool
@@ -132,8 +135,13 @@
 	// keyFile is the path to the private key to use for the server.
 	keyFile string
 	// resumeSession controls whether a second connection should be tested
-	// which resumes the first session.
+	// which attempts to resume the first session.
 	resumeSession bool
+	// resumeConfig, if not nil, points to a Config to be used on
+	// resumption. SessionTicketKey and ClientSessionCache are copied from
+	// the initial connection's config. If nil, the initial connection's
+	// config is used.
+	resumeConfig *Config
 	// sendPrefix sends a prefix on the socket before actually performing a
 	// handshake.
 	sendPrefix string
@@ -478,7 +486,7 @@
 	},
 }
 
-func doExchange(test *testCase, config *Config, conn net.Conn, messageLen int) error {
+func doExchange(test *testCase, config *Config, conn net.Conn, messageLen int, isResume bool) error {
 	if test.protocol == dtls {
 		conn = newPacketAdaptor(conn)
 	}
@@ -509,8 +517,15 @@
 		return err
 	}
 
-	if vers := tlsConn.ConnectionState().Version; test.expectedVersion != 0 && vers != test.expectedVersion {
-		return fmt.Errorf("got version %x, expected %x", vers, test.expectedVersion)
+	// TODO(davidben): move all per-connection expectations into a dedicated
+	// expectations struct that can be specified separately for the two
+	// legs.
+	expectedVersion := test.expectedVersion
+	if isResume && test.expectedResumeVersion != 0 {
+		expectedVersion = test.expectedResumeVersion
+	}
+	if vers := tlsConn.ConnectionState().Version; expectedVersion != 0 && vers != expectedVersion {
+		return fmt.Errorf("got version %x, expected %x", vers, expectedVersion)
 	}
 
 	if test.expectChannelID {
@@ -698,10 +713,23 @@
 		}
 	}
 
-	err := doExchange(test, &config, conn, test.messageLen)
+	err := doExchange(test, &config, conn, test.messageLen,
+		false /* not a resumption */)
 	conn.Close()
 	if err == nil && test.resumeSession {
-		err = doExchange(test, &config, connResume, test.messageLen)
+		var resumeConfig Config
+		if test.resumeConfig != nil {
+			resumeConfig = *test.resumeConfig
+			if len(resumeConfig.Certificates) == 0 {
+				resumeConfig.Certificates = []Certificate{getRSACertificate()}
+			}
+			resumeConfig.SessionTicketKey = config.SessionTicketKey
+			resumeConfig.ClientSessionCache = config.ClientSessionCache
+		} else {
+			resumeConfig = config
+		}
+		err = doExchange(test, &resumeConfig, connResume, test.messageLen,
+			true /* resumption */)
 	}
 	connResume.Close()
 
@@ -1516,6 +1544,68 @@
 	})
 }
 
+func addResumptionVersionTests() {
+	// TODO(davidben): Once DTLS 1.2 is working, test that as well.
+	for _, sessionVers := range tlsVersions {
+		// TODO(davidben): SSLv3 is omitted here because runner does not
+		// support resumption with session IDs.
+		if sessionVers.version == VersionSSL30 {
+			continue
+		}
+		for _, resumeVers := range tlsVersions {
+			if resumeVers.version == VersionSSL30 {
+				continue
+			}
+			suffix := "-" + sessionVers.name + "-" + resumeVers.name
+
+			// TODO(davidben): Write equivalent tests for the server
+			// and clean up the server's logic. This requires being
+			// able to give the shim a different set of SSL_OP_NO_*
+			// flags between the initial connection and the
+			// resume. Perhaps resumption should be tested by
+			// serializing the SSL_SESSION and starting a second
+			// shim.
+			testCases = append(testCases, testCase{
+				name:          "Resume-Client" + suffix,
+				resumeSession: true,
+				config: Config{
+					MaxVersion:   sessionVers.version,
+					CipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA},
+					Bugs: ProtocolBugs{
+						AllowSessionVersionMismatch: true,
+					},
+				},
+				expectedVersion: sessionVers.version,
+				resumeConfig: &Config{
+					MaxVersion:   resumeVers.version,
+					CipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA},
+					Bugs: ProtocolBugs{
+						AllowSessionVersionMismatch: true,
+					},
+				},
+				expectedResumeVersion: resumeVers.version,
+			})
+
+			testCases = append(testCases, testCase{
+				name:          "Resume-Client-NoResume" + suffix,
+				flags:         []string{"-expect-session-miss"},
+				resumeSession: true,
+				config: Config{
+					MaxVersion:   sessionVers.version,
+					CipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA},
+				},
+				expectedVersion: sessionVers.version,
+				resumeConfig: &Config{
+					MaxVersion:             resumeVers.version,
+					CipherSuites:           []uint16{TLS_RSA_WITH_RC4_128_SHA},
+					SessionTicketsDisabled: true,
+				},
+				expectedResumeVersion: resumeVers.version,
+			})
+		}
+	}
+}
+
 func worker(statusChan chan statusMsg, c chan *testCase, buildDir string, wg *sync.WaitGroup) {
 	defer wg.Done()
 
@@ -1570,6 +1660,7 @@
 	addVersionNegotiationTests()
 	addD5BugTests()
 	addExtensionTests()
+	addResumptionVersionTests()
 	for _, async := range []bool{false, true} {
 		for _, splitHandshake := range []bool{false, true} {
 			for _, protocol := range []protocol{tls, dtls} {