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/bssl_shim.cc b/ssl/test/bssl_shim.cc
index 846850c..5ee7c65 100644
--- a/ssl/test/bssl_shim.cc
+++ b/ssl/test/bssl_shim.cc
@@ -411,8 +411,9 @@
     return 2;
   }
 
-  if (is_resume && !SSL_session_reused(ssl)) {
-    fprintf(stderr, "session was not reused\n");
+  if (is_resume && (SSL_session_reused(ssl) == config->expect_session_miss)) {
+    fprintf(stderr, "session was%s reused\n",
+            SSL_session_reused(ssl) ? "" : " not");
     return 2;
   }
 
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} {
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc
index e96b242..70543cc 100644
--- a/ssl/test/test_config.cc
+++ b/ssl/test/test_config.cc
@@ -55,6 +55,7 @@
   { "-cookie-exchange", &TestConfig::cookie_exchange },
   { "-shim-writes-first", &TestConfig::shim_writes_first },
   { "-tls-d5-bug", &TestConfig::tls_d5_bug },
+  { "-expect-session-miss", &TestConfig::expect_session_miss },
 };
 
 const size_t kNumBoolFlags = sizeof(kBoolFlags) / sizeof(kBoolFlags[0]);
@@ -102,7 +103,8 @@
       no_ssl3(false),
       cookie_exchange(false),
       shim_writes_first(false),
-      tls_d5_bug(false) {
+      tls_d5_bug(false),
+      expect_session_miss(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 62c5c9e..acce504 100644
--- a/ssl/test/test_config.h
+++ b/ssl/test/test_config.h
@@ -52,6 +52,7 @@
   std::string expected_alpn;
   std::string expected_advertised_alpn;
   std::string select_alpn;
+  bool expect_session_miss;
 };
 
 bool ParseConfig(int argc, char **argv, TestConfig *out_config);