Add support for probabilistically choosing server ciphers
authorAnirudh Ramachandran <avr@fb.com>
Mon, 21 Mar 2016 23:25:46 +0000 (16:25 -0700)
committerFacebook Github Bot 4 <facebook-github-bot-4-bot@fb.com>
Mon, 21 Mar 2016 23:35:22 +0000 (16:35 -0700)
Summary:Since SSLContextManager sets SSL_OP_CIPHER_SERVER_PREFERENCE on the SSL_CTX
when it creates contexts, we may be unable to accommodate any clients who
prefer a different ciphersuite. Having differently weighted cipher preference
lists allows SSLContext to set a list with a different most-preferred cipher
for some fraction of new handshakes.

Note: resumption will work with the previously negotiated ciphersuite even if
the server doesn't explicitly prefer/support it anymore, provided the cipher is
supported in OpenSSL.

Reviewed By: knekritz

Differential Revision: D3050496

fb-gh-sync-id: 1c3b77ce3af87f939f8b8c6fe72b6a64eeaeeeb4
shipit-source-id: 1c3b77ce3af87f939f8b8c6fe72b6a64eeaeeeb4

folly/io/async/SSLContext.cpp
folly/io/async/SSLContext.h

index 5232b68..eb9920a 100644 (file)
@@ -86,9 +86,7 @@ SSLContext::SSLContext(SSLVersion version) {
   SSL_CTX_set_tlsext_servername_arg(ctx_, this);
 #endif
 
-#ifdef OPENSSL_NPN_NEGOTIATED
-  Random::seed(nextProtocolPicker_);
-#endif
+  Random::seed(randomGenerator_);
 }
 
 SSLContext::~SSLContext() {
@@ -338,20 +336,44 @@ int SSLContext::baseServerNameOpenSSLCallback(SSL* ssl, int* al, void* data) {
 
 void SSLContext::switchCiphersIfTLS11(
     SSL* ssl,
-    const std::string& tls11CipherString) {
-
-  CHECK(!tls11CipherString.empty()) << "Shouldn't call if empty alt ciphers";
+    const std::string& tls11CipherString,
+    const std::vector<std::pair<std::string, int>>& tls11AltCipherlist) {
+  CHECK(!(tls11CipherString.empty() && tls11AltCipherlist.empty()))
+      << "Shouldn't call if empty ciphers / alt ciphers";
 
   if (TLS1_get_client_version(ssl) <= TLS1_VERSION) {
     // We only do this for TLS v 1.1 and later
     return;
   }
 
+  const std::string* ciphers = &tls11CipherString;
+  if (!tls11AltCipherlist.empty()) {
+    if (!cipherListPicker_) {
+      std::vector<int> weights;
+      std::for_each(
+          tls11AltCipherlist.begin(),
+          tls11AltCipherlist.end(),
+          [&](const std::pair<std::string, int>& e) {
+            weights.push_back(e.second);
+          });
+      cipherListPicker_.reset(
+          new std::discrete_distribution<int>(weights.begin(), weights.end()));
+    }
+    auto index = (*cipherListPicker_)(randomGenerator_);
+    if ((size_t)index >= tls11AltCipherlist.size()) {
+      LOG(ERROR) << "Trying to pick alt TLS11 cipher index " << index
+                 << ", but tls11AltCipherlist is of length "
+                 << tls11AltCipherlist.size();
+    } else {
+      ciphers = &tls11AltCipherlist[index].first;
+    }
+  }
+
   // Prefer AES for TLS versions 1.1 and later since these are not
   // vulnerable to BEAST attacks on AES.  Note that we're setting the
   // cipher list on the SSL object, not the SSL_CTX object, so it will
   // only last for this request.
-  int rc = SSL_set_cipher_list(ssl, tls11CipherString.c_str());
+  int rc = SSL_set_cipher_list(ssl, ciphers->c_str());
   if ((rc == 0) || ERR_peek_error() != 0) {
     // This shouldn't happen since we checked for this when proxygen
     // started up.
@@ -477,7 +499,7 @@ void SSLContext::unsetNextProtocols() {
 
 size_t SSLContext::pickNextProtocols() {
   CHECK(!advertisedNextProtocols_.empty()) << "Failed to pickNextProtocols";
-  return nextProtocolDistribution_(nextProtocolPicker_);
+  return nextProtocolDistribution_(randomGenerator_);
 }
 
 int SSLContext::advertisedNextProtocolCallback(SSL* ssl,
index 65b572c..7b1df4a 100644 (file)
@@ -431,11 +431,14 @@ class SSLContext {
 
   /**
    * We want to vary which cipher we'll use based on the client's TLS version.
+   *
+   * XXX: The refernces to tls11CipherString and tls11AltCipherlist are reused
+   * for * each >= TLS 1.1 handshake, so we expect these fields to not change.
    */
   void switchCiphersIfTLS11(
-    SSL* ssl,
-    const std::string& tls11CipherString
-  );
+      SSL* ssl,
+      const std::string& tls11CipherString,
+      const std::vector<std::pair<std::string, int>>& tls11AltCipherlist);
 
   bool checkPeerName() { return checkPeerName_; }
   std::string peerFixedName() { return peerFixedName_; }
@@ -491,6 +494,11 @@ class SSLContext {
 
   static bool initialized_;
 
+  // Used in randomized next-proto pick / randomized cipherlist
+  Random::DefaultGenerator randomGenerator_;
+  // To provide control over choice of server ciphersuites
+  std::unique_ptr<std::discrete_distribution<int>> cipherListPicker_;
+
 #ifdef OPENSSL_NPN_NEGOTIATED
 
   struct AdvertisedNextProtocolsItem {
@@ -504,7 +512,6 @@ class SSLContext {
   std::vector<AdvertisedNextProtocolsItem> advertisedNextProtocols_;
   std::vector<int> advertisedNextProtocolWeights_;
   std::discrete_distribution<int> nextProtocolDistribution_;
-  Random::DefaultGenerator nextProtocolPicker_;
 
   static int sNextProtocolsExDataIndex_;