Bug 1100917 - Add config options for SSL3/RC4 only servers to ssltunnel. r=ted
authorMasatoshi Kimura <VYV03354@nifty.ne.jp>
Wed, 26 Nov 2014 20:37:18 +0900
changeset 217591 5e415846b95144826d3f625849403d51c2a225a1
parent 217590 b5dda6f0177021dcbf696de326ccf3327c14c4db
child 217592 baaf68711ae793de22954a85197e9e52c96c0902
push id52324
push userVYV03354@nifty.ne.jp
push dateWed, 26 Nov 2014 11:37:40 +0000
treeherdermozilla-inbound@5e415846b951 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersted
bugs1100917
milestone36.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1100917 - Add config options for SSL3/RC4 only servers to ssltunnel. r=ted
testing/mochitest/runtests.py
testing/mochitest/ssltunnel/ssltunnel.cpp
--- a/testing/mochitest/runtests.py
+++ b/testing/mochitest/runtests.py
@@ -883,16 +883,19 @@ class SSLTunnel:
                      (loc.host, loc.port, self.sslPort, clientauth))
 
       match = self.redirRE.match(option)
       if match:
         redirhost = match.group("redirhost")
         config.write("redirhost:%s:%s:%s:%s\n" %
                      (loc.host, loc.port, self.sslPort, redirhost))
 
+      if option in ('ssl3', 'rc4'):
+        config.write("%s:%s:%s:%s\n" % (option, loc.host, loc.port, self.sslPort))
+
   def buildConfig(self, locations):
     """Create the ssltunnel configuration file"""
     configFd, self.configFile = tempfile.mkstemp(prefix="ssltunnel", suffix=".cfg")
     with os.fdopen(configFd, "w") as config:
       config.write("httpproxy:1\n")
       config.write("certdbdir:%s\n" % self.certPath)
       config.write("forward:127.0.0.1:%s\n" % self.httpPort)
       config.write("websocketserver:%s:%s\n" % (self.webServer, self.webSocketPort))
--- a/testing/mochitest/ssltunnel/ssltunnel.cpp
+++ b/testing/mochitest/ssltunnel/ssltunnel.cpp
@@ -23,16 +23,17 @@
 #include "prerror.h"
 #include "prenv.h"
 #include "prnetdb.h"
 #include "prtpool.h"
 #include "nsAlgorithm.h"
 #include "nss.h"
 #include "key.h"
 #include "ssl.h"
+#include "sslproto.h"
 #include "plhash.h"
 
 using namespace mozilla;
 using namespace mozilla::psm;
 using std::string;
 using std::vector;
 
 #define IS_DELIM(m, c)          ((m)[(c) >> 3] & (1 << ((c) & 7)))
@@ -147,16 +148,18 @@ enum client_auth_option {
 
 // Structs for passing data into jobs on the thread pool
 typedef struct {
   int32_t listen_port;
   string cert_nickname;
   PLHashTable* host_cert_table;
   PLHashTable* host_clientauth_table;
   PLHashTable* host_redir_table;
+  PLHashTable* host_ssl3_table;
+  PLHashTable* host_rc4_table;
 } server_info_t;
 
 typedef struct {
   PRFileDesc* client_sock;
   PRNetAddr client_addr;
   server_info_t* server_info;
   // the original host in the Host: header for this connection is
   // stored here, for proxied connections
@@ -253,17 +256,18 @@ void SignalShutdown()
 {
   PR_Lock(shutdown_lock);
   PR_NotifyCondVar(shutdown_condvar);
   PR_Unlock(shutdown_lock);
 }
 
 bool ReadConnectRequest(server_info_t* server_info, 
     relayBuffer& buffer, int32_t* result, string& certificate,
-    client_auth_option* clientauth, string& host, string& location)
+    client_auth_option* clientauth, string& host, string& location,
+    bool* ssl3, bool* rc4)
 {
   if (buffer.present() < 4) {
     LOG_DEBUG((" !! only %d bytes present in the buffer", (int)buffer.present()));
     return false;
   }
   if (strncmp(buffer.buffertail-4, "\r\n\r\n", 4)) {
     LOG_ERRORD((" !! request is not tailed with CRLFCRLF but with %x %x %x %x", 
                *(buffer.buffertail-4),
@@ -302,27 +306,32 @@ bool ReadConnectRequest(server_info_t* s
     *clientauth = *static_cast<client_auth_option*>(c);
   else
     *clientauth = caNone;
 
   void *redir = PL_HashTableLookup(server_info->host_redir_table, token);
   if (redir)
     location = static_cast<char*>(redir);
 
+  *ssl3 = !!PL_HashTableLookup(server_info->host_ssl3_table, token);
+
+  *rc4 = !!PL_HashTableLookup(server_info->host_rc4_table, token);
+
   token = strtok2(_caret, "/", &_caret);
   if (strcmp(token, "HTTP")) {  
     LOG_ERRORD((" not tailed with HTTP but with %s", token));
     return true;
   }
 
   *result = (redir) ? 302 : 200;
   return true;
 }
 
-bool ConfigureSSLServerSocket(PRFileDesc* socket, server_info_t* si, string &certificate, client_auth_option clientAuth)
+bool ConfigureSSLServerSocket(PRFileDesc* socket, server_info_t* si, const string &certificate,
+                              const client_auth_option clientAuth, bool ssl3, bool rc4)
 {
   const char* certnick = certificate.empty() ?
       si->cert_nickname.c_str() : certificate.c_str();
 
   ScopedCERTCertificate cert(PK11_FindCertFromNickname(certnick, nullptr));
   if (!cert) {
     LOG_ERROR(("Failed to find cert %s\n", certnick));
     return false;
@@ -352,16 +361,40 @@ bool ConfigureSSLServerSocket(PRFileDesc
   SSL_OptionSet(ssl_socket, SSL_HANDSHAKE_AS_SERVER, true);
 
   if (clientAuth != caNone)
   {
     SSL_OptionSet(ssl_socket, SSL_REQUEST_CERTIFICATE, true);
     SSL_OptionSet(ssl_socket, SSL_REQUIRE_CERTIFICATE, clientAuth == caRequire);
   }
 
+  if (ssl3) {
+    SSLVersionRange range = { SSL_LIBRARY_VERSION_3_0,
+                              SSL_LIBRARY_VERSION_3_0 };
+    SSL_VersionRangeSet(ssl_socket, &range);
+  }
+
+  if (rc4) {
+    for (uint16_t i = 0; i < SSL_NumImplementedCiphers; ++i) {
+      uint16_t cipher_id = SSL_ImplementedCiphers[i];
+      switch (cipher_id) {
+      case TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
+      case TLS_ECDHE_RSA_WITH_RC4_128_SHA:
+      case TLS_RSA_WITH_RC4_128_SHA:
+      case TLS_RSA_WITH_RC4_128_MD5:
+        SSL_CipherPrefSet(ssl_socket, cipher_id, true);
+        break;
+
+      default:
+        SSL_CipherPrefSet(ssl_socket, cipher_id, false);
+        break;
+      }
+    }
+  }
+
   SSL_ResetHandshake(ssl_socket, true);
 
   return true;
 }
 
 /**
  * This function examines the buffer for a Sec-WebSocket-Location: field, 
  * and if it's present, it replaces the hostname in that field with the
@@ -542,30 +575,33 @@ void HandleConnection(void* data)
   bool client_error = false;
   bool connect_accepted = !do_http_proxy;
   bool ssl_updated = !do_http_proxy;
   bool expect_request_start = do_http_proxy;
   string certificateToUse;
   string locationHeader;
   client_auth_option clientAuth;
   string fullHost;
+  bool ssl3 = false;
+  bool rc4 = false;
 
   LOG_DEBUG(("SSLTUNNEL(%p)): incoming connection csock(0)=%p, ssock(1)=%p\n",
          static_cast<void*>(data),
          static_cast<void*>(ci->client_sock),
          static_cast<void*>(other_sock)));
   if (other_sock) 
   {
     int32_t numberOfSockets = 1;
 
     relayBuffer buffers[2];
 
     if (!do_http_proxy)
     {
-      if (!ConfigureSSLServerSocket(ci->client_sock, ci->server_info, certificateToUse, caNone))
+      if (!ConfigureSSLServerSocket(ci->client_sock, ci->server_info, certificateToUse, caNone,
+                                    ssl3, rc4))
         client_error = true;
       else if (!ConnectSocket(other_sock, &remote_addr, connect_timeout))
         client_error = true;
       else
         numberOfSockets = 2;
     }
 
     PRPollDesc sockets[2] = 
@@ -673,32 +709,39 @@ void HandleConnection(void* data)
             }
 
             buffers[s].buffertail += bytesRead;
             LOG_DEBUG((", read %d bytes", bytesRead));
 
             // We have to accept and handle the initial CONNECT request here
             int32_t response;
             if (!connect_accepted && ReadConnectRequest(ci->server_info, buffers[s],
-                &response, certificateToUse, &clientAuth, fullHost, locationHeader))
+                &response, certificateToUse, &clientAuth, fullHost, locationHeader,
+                &ssl3, &rc4))
             {
               // Mark this as a proxy-only connection (no SSL) if the CONNECT
               // request didn't come for port 443 or from any of the server's
               // cert or clientauth hostnames.
               if (fullHost.find(":443") == string::npos)
               {
                 server_match_t match;
                 match.fullHost = fullHost;
                 match.matched = false;
                 PL_HashTableEnumerateEntries(ci->server_info->host_cert_table, 
                                              match_hostname, 
                                              &match);
                 PL_HashTableEnumerateEntries(ci->server_info->host_clientauth_table, 
                                              match_hostname, 
                                              &match);
+                PL_HashTableEnumerateEntries(ci->server_info->host_ssl3_table, 
+                                             match_hostname, 
+                                             &match);
+                PL_HashTableEnumerateEntries(ci->server_info->host_rc4_table, 
+                                             match_hostname, 
+                                             &match);
                 ci->http_proxy_only = !match.matched;
               }
               else
               {
                 ci->http_proxy_only = false;
               }
 
               // Clean the request as it would be read
@@ -824,17 +867,17 @@ void HandleConnection(void* data)
                 LOG_DEBUG((" proxy response sent to the client"));
                 // Proxy response has just been writen, update to ssl
                 ssl_updated = true;
                 if (ci->http_proxy_only)
                 {
                   LOG_DEBUG((" not updating to SSL based on http_proxy_only for this socket"));
                 }
                 else if (!ConfigureSSLServerSocket(ci->client_sock, ci->server_info, 
-                                                   certificateToUse, clientAuth))
+                                                   certificateToUse, clientAuth, ssl3, rc4))
                 {
                   LOG_ERRORD((" failed to config server socket\n"));
                   client_error = true;
                   break;
                 }
                 else
                 {
                   LOG_DEBUG((" client socket updated to SSL"));
@@ -946,16 +989,68 @@ server_info_t* findServerInfo(int portnu
   {
     if (it->listen_port == portnumber)
       return &(*it);
   }
 
   return nullptr;
 }
 
+PLHashTable* get_ssl3_table(server_info_t* server)
+{
+  return server->host_ssl3_table;
+}
+
+PLHashTable* get_rc4_table(server_info_t* server)
+{
+  return server->host_rc4_table;
+}
+
+int parseWeakCryptoConfig(char* const& keyword, char*& _caret,
+                          PLHashTable* (*get_table)(server_info_t*))
+{
+  char* hostname = strtok2(_caret, ":", &_caret);
+  char* hostportstring = strtok2(_caret, ":", &_caret);
+  char* serverportstring = strtok2(_caret, "\n", &_caret);
+
+  int port = atoi(serverportstring);
+  if (port <= 0) {
+    LOG_ERROR(("Invalid port specified: %s\n", serverportstring));
+    return 1;
+  }
+
+  if (server_info_t* existingServer = findServerInfo(port))
+  {
+    any_host_spec_config = true;
+
+    char *hostname_copy = new char[strlen(hostname)+strlen(hostportstring)+2];
+    if (!hostname_copy) {
+      LOG_ERROR(("Out of memory"));
+      return 1;
+    }
+
+    strcpy(hostname_copy, hostname);
+    strcat(hostname_copy, ":");
+    strcat(hostname_copy, hostportstring);
+
+    PLHashEntry* entry = PL_HashTableAdd(get_table(existingServer), hostname_copy, keyword);
+    if (!entry) {
+      LOG_ERROR(("Out of memory"));
+      return 1;
+    }
+  }
+  else
+  {
+    LOG_ERROR(("Server on port %d for redirhost option is not defined, use 'listen' option first", port));
+    return 1;
+  }
+
+  return 0;
+}
+
 int processConfigLine(char* configLine)
 {
   if (*configLine == 0 || *configLine == '#')
     return 0;
 
   char* _caret;
   char* keyword = strtok2(configLine, ":", &_caret);
 
@@ -1062,16 +1157,33 @@ int processConfigLine(char* configLine)
       }
       server.host_redir_table = PL_NewHashTable(0, PL_HashString, PL_CompareStrings,
                                                 PL_CompareStrings, nullptr, nullptr);
       if (!server.host_redir_table)
       {
         LOG_ERROR(("Internal, could not create hash table\n"));
         return 1;
       }
+
+      server.host_ssl3_table = PL_NewHashTable(0, PL_HashString, PL_CompareStrings,
+                                               PL_CompareStrings, nullptr, nullptr);;
+      if (!server.host_ssl3_table)
+      {
+        LOG_ERROR(("Internal, could not create hash table\n"));
+        return 1;
+      }
+
+      server.host_rc4_table = PL_NewHashTable(0, PL_HashString, PL_CompareStrings,
+                                              PL_CompareStrings, nullptr, nullptr);;
+      if (!server.host_rc4_table)
+      {
+        LOG_ERROR(("Internal, could not create hash table\n"));
+        return 1;
+      }
+
       servers.push_back(server);
     }
 
     return 0;
   }
   
   if (!strcmp(keyword, "clientauth"))
   {
@@ -1173,16 +1285,24 @@ int processConfigLine(char* configLine)
     {
       LOG_ERROR(("Server on port %d for redirhost option is not defined, use 'listen' option first", port));
       return 1;
     }
 
     return 0;
   }
 
+  if (!strcmp(keyword, "ssl3")) {
+    return parseWeakCryptoConfig(keyword, _caret, get_ssl3_table);
+  }
+
+  if (!strcmp(keyword, "rc4")) {
+    return parseWeakCryptoConfig(keyword, _caret, get_rc4_table);
+  }
+
   // Configure the NSS certificate database directory
   if (!strcmp(keyword, "certdbdir"))
   {
     nssconfigdir = strtok2(_caret, "\n", &_caret);
     return 0;
   }
 
   LOG_ERROR(("Error: keyword \"%s\" unexpected\n", keyword));
@@ -1247,16 +1367,28 @@ int freeHostRedirHashItems(PLHashEntry *
 
 int freeClientAuthHashItems(PLHashEntry *he, int i, void *arg)
 {
   delete [] (char*)he->key;
   delete (client_auth_option*)he->value;
   return HT_ENUMERATE_REMOVE;
 }
 
+int freeSSL3HashItems(PLHashEntry *he, int i, void *arg)
+{
+  delete [] (char*)he->key;
+  return HT_ENUMERATE_REMOVE;
+}
+
+int freeRC4HashItems(PLHashEntry *he, int i, void *arg)
+{
+  delete [] (char*)he->key;
+  return HT_ENUMERATE_REMOVE;
+}
+
 int main(int argc, char** argv)
 {
   const char* configFilePath;
   
   const char* logLevelEnv = PR_GetEnv("SSLTUNNEL_LOG_LEVEL");
   gLogLevel = logLevelEnv ? (LogLevel)atoi(logLevelEnv) : LEVEL_INFO;
   
   if (argc == 1)
@@ -1379,16 +1511,20 @@ int main(int argc, char** argv)
   }
   
   for (vector<server_info_t>::iterator it = servers.begin();
        it != servers.end(); it++) 
   {
     PL_HashTableEnumerateEntries(it->host_cert_table, freeHostCertHashItems, nullptr);
     PL_HashTableEnumerateEntries(it->host_clientauth_table, freeClientAuthHashItems, nullptr);
     PL_HashTableEnumerateEntries(it->host_redir_table, freeHostRedirHashItems, nullptr);
+    PL_HashTableEnumerateEntries(it->host_ssl3_table, freeSSL3HashItems, nullptr);
+    PL_HashTableEnumerateEntries(it->host_rc4_table, freeRC4HashItems, nullptr);
     PL_HashTableDestroy(it->host_cert_table);
     PL_HashTableDestroy(it->host_clientauth_table);
     PL_HashTableDestroy(it->host_redir_table);
+    PL_HashTableDestroy(it->host_ssl3_table);
+    PL_HashTableDestroy(it->host_rc4_table);
   }
 
   PR_Cleanup();
   return 0;
 }