Add cipher suite settings for TLS ≥ 1.0.

This change adds the ability to configure ciphers specifically for
TLS ≥ 1.0. This compliments the existing ability to specify ciphers
for TLS ≥ 1.1.

This is useful because TLS 1.0 is the first version not to suffer from
POODLE. (Assuming that it's implemented correctly[1].) Thus one might
wish to reserve RC4 solely for SSLv3.

[1] https://www.imperialviolet.org/2014/12/08/poodleagain.html

Change-Id: I774d5336fead48f03d8a0a3cf80c369692ee60df
Reviewed-on: https://boringssl-review.googlesource.com/5793
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index 332a62f..bde16a4 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -1265,6 +1265,17 @@
   struct ssl_cipher_preference_list_st *cipher_list;
   /* same as above but sorted for lookup */
   STACK_OF(SSL_CIPHER) *cipher_list_by_id;
+
+  /* cipher_list_tls10 is the list of ciphers when TLS 1.0 or greater is in
+   * use. This only applies to server connections as, for clients, the version
+   * number is known at connect time and so the cipher list can be set then. If
+   * |cipher_list_tls11| is non-NULL then this applies only to TLS 1.0
+   * connections.
+   *
+   * TODO(agl): this exists to assist in the death of SSLv3. It can hopefully
+   * be removed after that. */
+  struct ssl_cipher_preference_list_st *cipher_list_tls10;
+
   /* cipher_list_tls11 is the list of ciphers when TLS 1.1 or greater is in
    * use. This only applies to server connections as, for clients, the version
    * number is known at connect time and so the cipher list can be set then. */
@@ -2099,6 +2110,7 @@
                                    size_t curves_len);
 
 OPENSSL_EXPORT int SSL_CTX_set_cipher_list(SSL_CTX *, const char *str);
+OPENSSL_EXPORT int SSL_CTX_set_cipher_list_tls10(SSL_CTX *, const char *str);
 OPENSSL_EXPORT int SSL_CTX_set_cipher_list_tls11(SSL_CTX *, const char *str);
 OPENSSL_EXPORT long SSL_CTX_set_timeout(SSL_CTX *ctx, long t);
 OPENSSL_EXPORT long SSL_CTX_get_timeout(const SSL_CTX *ctx);
diff --git a/ssl/s3_lib.c b/ssl/s3_lib.c
index 64e31e5..7125215 100644
--- a/ssl/s3_lib.c
+++ b/ssl/s3_lib.c
@@ -451,6 +451,11 @@
     return s->ctx->cipher_list_tls11;
   }
 
+  if (s->version >= TLS1_VERSION && s->ctx != NULL &&
+      s->ctx->cipher_list_tls10 != NULL) {
+    return s->ctx->cipher_list_tls10;
+  }
+
   if (s->ctx != NULL && s->ctx->cipher_list != NULL) {
     return s->ctx->cipher_list;
   }
diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c
index b2eeb37..fee9523 100644
--- a/ssl/ssl_lib.c
+++ b/ssl/ssl_lib.c
@@ -1081,6 +1081,11 @@
     return s->ctx->cipher_list_tls11->ciphers;
   }
 
+  if (s->version >= TLS1_VERSION && s->ctx != NULL &&
+      s->ctx->cipher_list_tls10 != NULL) {
+    return s->ctx->cipher_list_tls10->ciphers;
+  }
+
   if (s->ctx != NULL && s->ctx->cipher_list != NULL) {
     return s->ctx->cipher_list->ciphers;
   }
@@ -1149,6 +1154,20 @@
   return 1;
 }
 
+int SSL_CTX_set_cipher_list_tls10(SSL_CTX *ctx, const char *str) {
+  STACK_OF(SSL_CIPHER) *sk;
+
+  sk = ssl_create_cipher_list(ctx->method, &ctx->cipher_list_tls10, NULL, str);
+  if (sk == NULL) {
+    return 0;
+  } else if (sk_SSL_CIPHER_num(sk) == 0) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_NO_CIPHER_MATCH);
+    return 0;
+  }
+
+  return 1;
+}
+
 int SSL_CTX_set_cipher_list_tls11(SSL_CTX *ctx, const char *str) {
   STACK_OF(SSL_CIPHER) *sk;
 
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc
index fa0cb75..97044ab 100644
--- a/ssl/test/bssl_shim.cc
+++ b/ssl/test/bssl_shim.cc
@@ -599,7 +599,23 @@
     return nullptr;
   }
 
-  if (!SSL_CTX_set_cipher_list(ssl_ctx.get(), "ALL")) {
+  std::string cipher_list = "ALL";
+  if (!config->cipher.empty()) {
+    cipher_list = config->cipher;
+    SSL_CTX_set_options(ssl_ctx.get(), SSL_OP_CIPHER_SERVER_PREFERENCE);
+  }
+  if (!SSL_CTX_set_cipher_list(ssl_ctx.get(), cipher_list.c_str())) {
+    return nullptr;
+  }
+
+  if (!config->cipher_tls10.empty() &&
+      !SSL_CTX_set_cipher_list_tls10(ssl_ctx.get(),
+                                     config->cipher_tls10.c_str())) {
+    return nullptr;
+  }
+  if (!config->cipher_tls11.empty() &&
+      !SSL_CTX_set_cipher_list_tls11(ssl_ctx.get(),
+                                     config->cipher_tls11.c_str())) {
     return nullptr;
   }
 
@@ -1027,10 +1043,6 @@
   if (config->install_ddos_callback) {
     SSL_CTX_set_dos_protection_cb(ssl_ctx, DDoSCallback);
   }
-  if (!config->cipher.empty() &&
-      !SSL_set_cipher_list(ssl.get(), config->cipher.c_str())) {
-    return false;
-  }
   if (!config->reject_peer_renegotiations) {
     /* Renegotiations are disabled by default. */
     SSL_set_reject_peer_renegotiations(ssl.get(), 0);
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 6284ef6..8124382 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -2092,6 +2092,94 @@
 		shouldFail:    true,
 		expectedError: "BAD_DH_P_LENGTH",
 	})
+
+	// versionSpecificCiphersTest specifies a test for the TLS 1.0 and TLS
+	// 1.1 specific cipher suite settings. A server is setup with the given
+	// cipher lists and then a connection is made for each member of
+	// expectations. The cipher suite that the server selects must match
+	// the specified one.
+	var versionSpecificCiphersTest = []struct {
+		ciphersDefault, ciphersTLS10, ciphersTLS11 string
+		// expectations is a map from TLS version to cipher suite id.
+		expectations map[uint16]uint16
+	}{
+		{
+			// Test that the null case (where no version-specific ciphers are set)
+			// works as expected.
+			"RC4-SHA:AES128-SHA", // default ciphers
+			"",                   // no ciphers specifically for TLS ≥ 1.0
+			"",                   // no ciphers specifically for TLS ≥ 1.1
+			map[uint16]uint16{
+				VersionSSL30: TLS_RSA_WITH_RC4_128_SHA,
+				VersionTLS10: TLS_RSA_WITH_RC4_128_SHA,
+				VersionTLS11: TLS_RSA_WITH_RC4_128_SHA,
+				VersionTLS12: TLS_RSA_WITH_RC4_128_SHA,
+			},
+		},
+		{
+			// With ciphers_tls10 set, TLS 1.0, 1.1 and 1.2 should get a different
+			// cipher.
+			"RC4-SHA:AES128-SHA", // default
+			"AES128-SHA",         // these ciphers for TLS ≥ 1.0
+			"",                   // no ciphers specifically for TLS ≥ 1.1
+			map[uint16]uint16{
+				VersionSSL30: TLS_RSA_WITH_RC4_128_SHA,
+				VersionTLS10: TLS_RSA_WITH_AES_128_CBC_SHA,
+				VersionTLS11: TLS_RSA_WITH_AES_128_CBC_SHA,
+				VersionTLS12: TLS_RSA_WITH_AES_128_CBC_SHA,
+			},
+		},
+		{
+			// With ciphers_tls11 set, TLS 1.1 and 1.2 should get a different
+			// cipher.
+			"RC4-SHA:AES128-SHA", // default
+			"",                   // no ciphers specifically for TLS ≥ 1.0
+			"AES128-SHA",         // these ciphers for TLS ≥ 1.1
+			map[uint16]uint16{
+				VersionSSL30: TLS_RSA_WITH_RC4_128_SHA,
+				VersionTLS10: TLS_RSA_WITH_RC4_128_SHA,
+				VersionTLS11: TLS_RSA_WITH_AES_128_CBC_SHA,
+				VersionTLS12: TLS_RSA_WITH_AES_128_CBC_SHA,
+			},
+		},
+		{
+			// With both ciphers_tls10 and ciphers_tls11 set, ciphers_tls11 should
+			// mask ciphers_tls10 for TLS 1.1 and 1.2.
+			"RC4-SHA:AES128-SHA", // default
+			"AES128-SHA",         // these ciphers for TLS ≥ 1.0
+			"AES256-SHA",         // these ciphers for TLS ≥ 1.1
+			map[uint16]uint16{
+				VersionSSL30: TLS_RSA_WITH_RC4_128_SHA,
+				VersionTLS10: TLS_RSA_WITH_AES_128_CBC_SHA,
+				VersionTLS11: TLS_RSA_WITH_AES_256_CBC_SHA,
+				VersionTLS12: TLS_RSA_WITH_AES_256_CBC_SHA,
+			},
+		},
+	}
+
+	for i, test := range versionSpecificCiphersTest {
+		for version, expectedCipherSuite := range test.expectations {
+			flags := []string{"-cipher", test.ciphersDefault}
+			if len(test.ciphersTLS10) > 0 {
+				flags = append(flags, "-cipher-tls10", test.ciphersTLS10)
+			}
+			if len(test.ciphersTLS11) > 0 {
+				flags = append(flags, "-cipher-tls11", test.ciphersTLS11)
+			}
+
+			testCases = append(testCases, testCase{
+				testType: serverTest,
+				name:     fmt.Sprintf("VersionSpecificCiphersTest-%d-%x", i, version),
+				config: Config{
+					MaxVersion:   version,
+					MinVersion:   version,
+					CipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA},
+				},
+				flags:          flags,
+				expectedCipher: expectedCipherSuite,
+			})
+		}
+	}
 }
 
 func addBadECDSASignatureTests() {
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc
index edf3d6e..7a87fd5 100644
--- a/ssl/test/test_config.cc
+++ b/ssl/test/test_config.cc
@@ -112,6 +112,8 @@
   { "-psk-identity", &TestConfig::psk_identity },
   { "-srtp-profiles", &TestConfig::srtp_profiles },
   { "-cipher", &TestConfig::cipher },
+  { "-cipher-tls10", &TestConfig::cipher_tls10 },
+  { "-cipher-tls11", &TestConfig::cipher_tls11 },
   { "-export-label", &TestConfig::export_label },
   { "-export-context", &TestConfig::export_context },
 };
diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h
index d6ccda2..a41af49 100644
--- a/ssl/test/test_config.h
+++ b/ssl/test/test_config.h
@@ -69,6 +69,8 @@
   bool fail_ddos_callback = false;
   bool fail_second_ddos_callback = false;
   std::string cipher;
+  std::string cipher_tls10;
+  std::string cipher_tls11;
   bool handshake_never_done = false;
   int export_keying_material = 0;
   std::string export_label;