Factor out TLS cipher selection to ssl_choose_tls_cipher.

This is factored out since ESNI will need to do its own cipher selection.

Bug: 275
Change-Id: Id87fd91272fbcd9098b3f2a9caa78a2129b154b5
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/36544
Commit-Queue: Steven Valdez <svaldez@google.com>
Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/ssl/internal.h b/ssl/internal.h
index f03271e..bc17af9 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -560,6 +560,12 @@
 // it returns zero.
 size_t ssl_cipher_get_record_split_len(const SSL_CIPHER *cipher);
 
+// ssl_choose_tls13_cipher returns an |SSL_CIPHER| corresponding with the best
+// available from |cipher_suites| compatible with |version| and |group_id|. It
+// returns NULL if there isn't a compatible cipher.
+const SSL_CIPHER *ssl_choose_tls13_cipher(CBS cipher_suites, uint16_t version,
+                                          uint16_t group_id);
+
 
 // Transcript layer.
 
diff --git a/ssl/s3_both.cc b/ssl/s3_both.cc
index 27e9454..842ec67 100644
--- a/ssl/s3_both.cc
+++ b/ssl/s3_both.cc
@@ -116,6 +116,8 @@
 #include <limits.h>
 #include <string.h>
 
+#include <tuple>
+
 #include <openssl/buf.h>
 #include <openssl/bytestring.h>
 #include <openssl/err.h>
@@ -652,4 +654,72 @@
   }
 }
 
+// CipherScorer produces a "score" for each possible cipher suite offered by
+// the client.
+class CipherScorer {
+ public:
+  CipherScorer(uint16_t group_id)
+      : aes_is_fine_(EVP_has_aes_hardware()),
+        security_128_is_fine_(group_id != SSL_CURVE_CECPQ2 &&
+                              group_id != SSL_CURVE_CECPQ2b) {}
+
+  typedef std::tuple<bool, bool, bool> Score;
+
+  // MinScore returns a |Score| that will compare less than the score of all
+  // cipher suites.
+  Score MinScore() const {
+    return Score(false, false, false);
+  }
+
+  Score Evaluate(const SSL_CIPHER *a) const {
+    return Score(
+        // Something is always preferable to nothing.
+        true,
+        // Either 128-bit is fine, or 256-bit is preferred.
+        security_128_is_fine_ || a->algorithm_enc != SSL_AES128GCM,
+        // Either AES is fine, or else ChaCha20 is preferred.
+        aes_is_fine_ || a->algorithm_enc == SSL_CHACHA20POLY1305);
+  }
+
+ private:
+  const bool aes_is_fine_;
+  const bool security_128_is_fine_;
+};
+
+const SSL_CIPHER *ssl_choose_tls13_cipher(CBS cipher_suites, uint16_t version,
+                                          uint16_t group_id) {
+  if (CBS_len(&cipher_suites) % 2 != 0) {
+    return nullptr;
+  }
+
+  const SSL_CIPHER *best = nullptr;
+  CipherScorer scorer(group_id);
+  CipherScorer::Score best_score = scorer.MinScore();
+
+  while (CBS_len(&cipher_suites) > 0) {
+    uint16_t cipher_suite;
+    if (!CBS_get_u16(&cipher_suites, &cipher_suite)) {
+      return nullptr;
+    }
+
+    // Limit to TLS 1.3 ciphers we know about.
+    const SSL_CIPHER *candidate = SSL_get_cipher_by_value(cipher_suite);
+    if (candidate == nullptr ||
+        SSL_CIPHER_get_min_version(candidate) > version ||
+        SSL_CIPHER_get_max_version(candidate) < version) {
+      continue;
+    }
+
+    const CipherScorer::Score candidate_score = scorer.Evaluate(candidate);
+    // |candidate_score| must be larger to displace the current choice. That way
+    // the client's order controls between ciphers with an equal score.
+    if (candidate_score > best_score) {
+      best = candidate;
+      best_score = candidate_score;
+    }
+  }
+
+  return best;
+}
+
 BSSL_NAMESPACE_END
diff --git a/ssl/tls13_server.cc b/ssl/tls13_server.cc
index 8fb0d5c..4ebea5b 100644
--- a/ssl/tls13_server.cc
+++ b/ssl/tls13_server.cc
@@ -103,78 +103,15 @@
   return 1;
 }
 
-// CipherScorer produces a "score" for each possible cipher suite offered by
-// the client.
-class CipherScorer {
- public:
-  CipherScorer(uint16_t group_id)
-      : aes_is_fine_(EVP_has_aes_hardware()),
-        security_128_is_fine_(group_id != SSL_CURVE_CECPQ2 &&
-                              group_id != SSL_CURVE_CECPQ2b) {}
-
-  typedef std::tuple<bool, bool, bool> Score;
-
-  // MinScore returns a |Score| that will compare less than the score of all
-  // cipher suites.
-  Score MinScore() const {
-    return Score(false, false, false);
-  }
-
-  Score Evaluate(const SSL_CIPHER *a) const {
-    return Score(
-        // Something is always preferable to nothing.
-        true,
-        // Either 128-bit is fine, or 256-bit is preferred.
-        security_128_is_fine_ || a->algorithm_enc != SSL_AES128GCM,
-        // Either AES is fine, or else ChaCha20 is preferred.
-        aes_is_fine_ || a->algorithm_enc == SSL_CHACHA20POLY1305);
-  }
-
- private:
-  const bool aes_is_fine_;
-  const bool security_128_is_fine_;
-};
-
 static const SSL_CIPHER *choose_tls13_cipher(
     const SSL *ssl, const SSL_CLIENT_HELLO *client_hello, uint16_t group_id) {
-  if (client_hello->cipher_suites_len % 2 != 0) {
-    return nullptr;
-  }
-
   CBS cipher_suites;
   CBS_init(&cipher_suites, client_hello->cipher_suites,
            client_hello->cipher_suites_len);
 
   const uint16_t version = ssl_protocol_version(ssl);
 
-  const SSL_CIPHER *best = nullptr;
-  CipherScorer scorer(group_id);
-  CipherScorer::Score best_score = scorer.MinScore();
-
-  while (CBS_len(&cipher_suites) > 0) {
-    uint16_t cipher_suite;
-    if (!CBS_get_u16(&cipher_suites, &cipher_suite)) {
-      return nullptr;
-    }
-
-    // Limit to TLS 1.3 ciphers we know about.
-    const SSL_CIPHER *candidate = SSL_get_cipher_by_value(cipher_suite);
-    if (candidate == nullptr ||
-        SSL_CIPHER_get_min_version(candidate) > version ||
-        SSL_CIPHER_get_max_version(candidate) < version) {
-      continue;
-    }
-
-    const CipherScorer::Score candidate_score = scorer.Evaluate(candidate);
-    // |candidate_score| must be larger to displace the current choice. That way
-    // the client's order controls between ciphers with an equal score.
-    if (candidate_score > best_score) {
-      best = candidate;
-      best_score = candidate_score;
-    }
-  }
-
-  return best;
+  return ssl_choose_tls13_cipher(cipher_suites, version, group_id);
 }
 
 static bool add_new_session_tickets(SSL_HANDSHAKE *hs, bool *out_sent_tickets) {