Implement TLS_FALLBACK_SCSV support for the client.

With this change, calling SSL_enable_fallback_scsv on a client SSL* will
cause the fallback SCSV to be sent.

This is intended to be set when the client is performing TLS fallback
after a failed connection. (This only happens if the application itself
implements this behaviour: OpenSSL does not do fallback automatically.)

The fallback SCSV indicates to the server that it should reject the
connection if the version indicated by the client is less than the
version supported by the server.

See http://tools.ietf.org/html/draft-bmoeller-tls-downgrade-scsv-02.

Change-Id: I478d6d5135016f1b7c4aaa6c306a1a64b1d215a6
diff --git a/ssl/test/runner/cipher_suites.go b/ssl/test/runner/cipher_suites.go
index 11c8bdd..bf1a755 100644
--- a/ssl/test/runner/cipher_suites.go
+++ b/ssl/test/runner/cipher_suites.go
@@ -287,4 +287,5 @@
 	TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256   uint16 = 0xc02f
 	TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 uint16 = 0xc02b
 	TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384   uint16 = 0xc030
+	fallbackSCSV                            uint16 = 0x5600
 )
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index 0905e9f..fd78eb6 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -341,6 +341,10 @@
 	// PaddingFirstByteBadIf255 causes the first byte of padding to be
 	// incorrect if there's a maximum amount of padding (i.e. 255 bytes).
 	PaddingFirstByteBadIf255 bool
+
+	// FailIfNotFallbackSCSV causes a server handshake to fail if the
+	// client doesn't send the fallback SCSV value.
+	FailIfNotFallbackSCSV bool
 }
 
 func (c *Config) serverInit() {
diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go
index 7a44a94..854c7ff 100644
--- a/ssl/test/runner/handshake_server.go
+++ b/ssl/test/runner/handshake_server.go
@@ -184,6 +184,21 @@
 		return true, nil
 	}
 
+	var scsvFound bool
+
+	for _, cipherSuite := range hs.clientHello.cipherSuites {
+		if cipherSuite == fallbackSCSV {
+			scsvFound = true
+			break
+		}
+	}
+
+	if !scsvFound && config.Bugs.FailIfNotFallbackSCSV {
+		return false, errors.New("tls: no fallback SCSV found when expected")
+	} else if scsvFound && !config.Bugs.FailIfNotFallbackSCSV {
+		return false, errors.New("tls: fallback SCSV found when not expected")
+	}
+
 	var preferenceList, supportedList []uint16
 	if c.config.PreferServerCipherSuites {
 		preferenceList = c.config.cipherSuites()
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index b5743c5..1ec3795 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -47,9 +47,15 @@
 	config        Config
 	shouldFail    bool
 	expectedError string
+	// expectedLocalError, if not empty, contains a substring that must be
+	// found in the local error.
+	expectedLocalError string
 	// messageLen is the length, in bytes, of the test message that will be
 	// sent.
 	messageLen int
+	// flag, if not nil, contains a command line flag that will be passed
+	// to the shim program.
+	flag string
 }
 
 var clientTests = []testCase{
@@ -88,6 +94,16 @@
 		shouldFail:    true,
 		expectedError: ":WRONG_CURVE:",
 	},
+	{
+		name: "FallbackSCSV",
+		config: Config{
+			Bugs: ProtocolBugs{
+				FailIfNotFallbackSCSV: true,
+			},
+		},
+		shouldFail:         true,
+		expectedLocalError: "no fallback SCSV found",
+	},
 }
 
 func doExchange(tlsConn *Conn, messageLen int) error {
@@ -185,13 +201,16 @@
 	stderr := string(stderrBuf.Bytes())
 	failed := err != nil || childErr != nil
 	correctFailure := len(test.expectedError) == 0 || strings.Contains(stdout, test.expectedError)
+	localError := "none"
+	if err != nil {
+		localError = err.Error()
+	}
+	if len(test.expectedLocalError) != 0 {
+		correctFailure = correctFailure && strings.Contains(localError, test.expectedLocalError)
+	}
 
 	if failed != test.shouldFail || failed && !correctFailure {
-		localError := "none"
 		childError := "none"
-		if err != nil {
-			localError = err.Error()
-		}
 		if childErr != nil {
 			childError = childErr.Error()
 		}
@@ -203,7 +222,7 @@
 		case !failed && test.shouldFail:
 			msg = "unexpected success"
 		case failed && !correctFailure:
-			msg = "bad error (wanted '" + test.expectedError + "')"
+			msg = "bad error (wanted '" + test.expectedError + "' / '" + test.expectedLocalError + "')"
 		default:
 			panic("internal error")
 		}