Add test coverage for TLS version negotiation.

Test all pairs of client and server version, except for the ones that require
SSLv3 client support in runner.go. That is, as yet, still missing.

Change-Id: I601ab49c5526cd2eb4f85d5d535570e32f218d5b
Reviewed-on: https://boringssl-review.googlesource.com/1450
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc
index 33298af..c0cfcc3 100644
--- a/ssl/test/bssl_shim.cc
+++ b/ssl/test/bssl_shim.cc
@@ -271,6 +271,14 @@
       SSL_set_mode(ssl, SSL_MODE_CBC_RECORD_SPLITTING);
     } else if (strcmp(argv[i], "-partial-write") == 0) {
       SSL_set_mode(ssl, SSL_MODE_ENABLE_PARTIAL_WRITE);
+    } else if (strcmp(argv[i], "-no-tls12") == 0) {
+      SSL_set_options(ssl, SSL_OP_NO_TLSv1_2);
+    } else if (strcmp(argv[i], "-no-tls11") == 0) {
+      SSL_set_options(ssl, SSL_OP_NO_TLSv1_1);
+    } else if (strcmp(argv[i], "-no-tls1") == 0) {
+      SSL_set_options(ssl, SSL_OP_NO_TLSv1);
+    } else if (strcmp(argv[i], "-no-ssl3") == 0) {
+      SSL_set_options(ssl, SSL_OP_NO_SSLv3);
     } else {
       fprintf(stderr, "Unknown argument: %s\n", argv[i]);
       return 1;
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index f2e268e..701de81 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -72,6 +72,9 @@
 	// expectedLocalError, if not empty, contains a substring that must be
 	// found in the local error.
 	expectedLocalError string
+	// expectedVersion, if non-zero, specifies the TLS version that must be
+	// negotiated.
+	expectedVersion uint16
 	// messageLen is the length, in bytes, of the test message that will be
 	// sent.
 	messageLen int
@@ -382,9 +385,9 @@
 	},
 }
 
-func doExchange(testType testType, config *Config, conn net.Conn, messageLen int) error {
+func doExchange(test *testCase, config *Config, conn net.Conn, messageLen int) error {
 	var tlsConn *Conn
-	if testType == clientTest {
+	if test.testType == clientTest {
 		tlsConn = Server(conn, config)
 	} else {
 		config.InsecureSkipVerify = true
@@ -395,6 +398,10 @@
 		return err
 	}
 
+	if vers := tlsConn.ConnectionState().Version; test.expectedVersion != 0 && vers != test.expectedVersion {
+		return fmt.Errorf("got version %x, expected %x", vers, test.expectedVersion)
+	}
+
 	if messageLen < 0 {
 		// Read until EOF.
 		_, err := io.Copy(ioutil.Discard, tlsConn)
@@ -527,10 +534,10 @@
 		}
 	}
 
-	err := doExchange(test.testType, &config, conn, test.messageLen)
+	err := doExchange(test, &config, conn, test.messageLen)
 	conn.Close()
 	if err == nil && test.resumeSession {
-		err = doExchange(test.testType, &config, connResume, test.messageLen)
+		err = doExchange(test, &config, connResume, test.messageLen)
 		connResume.Close()
 	}
 
@@ -579,11 +586,12 @@
 var tlsVersions = []struct {
 	name    string
 	version uint16
+	flag    string
 }{
-	{"SSL3", VersionSSL30},
-	{"TLS1", VersionTLS10},
-	{"TLS11", VersionTLS11},
-	{"TLS12", VersionTLS12},
+	{"SSL3", VersionSSL30, "-no-ssl3"},
+	{"TLS1", VersionTLS10, "-no-tls1"},
+	{"TLS11", VersionTLS11, "-no-tls11"},
+	{"TLS12", VersionTLS12, "-no-tls12"},
 }
 
 var testCipherSuites = []struct {
@@ -969,6 +977,47 @@
 	})
 }
 
+func addVersionNegotiationTests() {
+	for i, shimVers := range tlsVersions {
+		// Assemble flags to disable all newer versions on the shim.
+		var flags []string
+		for _, vers := range tlsVersions[i+1:] {
+			flags = append(flags, vers.flag)
+		}
+
+		for _, runnerVers := range tlsVersions {
+			expectedVersion := shimVers.version
+			if runnerVers.version < shimVers.version {
+				expectedVersion = runnerVers.version
+			}
+			suffix := shimVers.name + "-" + runnerVers.name
+
+			testCases = append(testCases, testCase{
+				testType: clientTest,
+				name:     "VersionNegotiation-Client-" + suffix,
+				config: Config{
+					MaxVersion: runnerVers.version,
+				},
+				flags:           flags,
+				expectedVersion: expectedVersion,
+			})
+
+			// TODO(davidben): Implement SSLv3 as a client in the runner.
+			if expectedVersion > VersionSSL30 {
+				testCases = append(testCases, testCase{
+					testType: serverTest,
+					name:     "VersionNegotiation-Server-" + suffix,
+					config: Config{
+						MaxVersion: runnerVers.version,
+					},
+					flags:           flags,
+					expectedVersion: expectedVersion,
+				})
+			}
+		}
+	}
+}
+
 func worker(statusChan chan statusMsg, c chan *testCase, buildDir string, wg *sync.WaitGroup) {
 	defer wg.Done()
 
@@ -1020,6 +1069,7 @@
 	addCBCPaddingTests()
 	addCBCSplittingTests()
 	addClientAuthTests()
+	addVersionNegotiationTests()
 	for _, async := range []bool{false, true} {
 		for _, splitHandshake := range []bool{false, true} {
 			addStateMachineCoverageTests(async, splitHandshake)