bug 1273677 - ensure session cache is properly configured and torn down for TLSServerSocket r?mcmanus draft
authorDavid Keeler <dkeeler@mozilla.com>
Mon, 23 May 2016 13:58:56 -0700
changeset 369905 e64a1ed7d8edc6902c20816640ff32773441d79a
parent 368911 1806d405c8715949b39fa3a4fc142d14a60df590
child 521649 233c3c1177c0d1becea675a4e078313d7c277112
push id18949
push userdkeeler@mozilla.com
push dateMon, 23 May 2016 23:43:51 +0000
reviewersmcmanus
bugs1273677
milestone49.0a1
bug 1273677 - ensure session cache is properly configured and torn down for TLSServerSocket r?mcmanus MozReview-Commit-ID: 6i7HxTdLcID
config/external/nss/nss.symbols
netwerk/test/unit/test_tls_server_multiple_clients.js
netwerk/test/unit/xpcshell.ini
security/manager/ssl/nsNSSComponent.cpp
--- a/config/external/nss/nss.symbols
+++ b/config/external/nss/nss.symbols
@@ -685,16 +685,17 @@ SSL_ResetHandshake
 SSL_SetCanFalseStartCallback
 SSL_SetDowngradeCheckVersion
 SSL_SetNextProtoNego
 SSL_SetPKCS11PinArg
 SSL_SetSockPeerID
 SSL_SetSRTPCiphers
 SSL_SetStapledOCSPResponses
 SSL_SetURL
+SSL_ShutdownServerSessionIDCache
 SSL_SNISocketConfigHook
 SSL_VersionRangeGet
 SSL_VersionRangeGetDefault
 SSL_VersionRangeGetSupported
 SSL_VersionRangeSet
 SSL_VersionRangeSetDefault
 UTIL_SetForkState
 VFY_Begin
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_tls_server_multiple_clients.js
@@ -0,0 +1,141 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Need profile dir to store the key / cert
+do_get_profile();
+// Ensure PSM is initialized
+Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
+
+const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
+const { Promise: promise } =
+  Cu.import("resource://gre/modules/Promise.jsm", {});
+const certService = Cc["@mozilla.org/security/local-cert-service;1"]
+                    .getService(Ci.nsILocalCertService);
+const certOverrideService = Cc["@mozilla.org/security/certoverride;1"]
+                            .getService(Ci.nsICertOverrideService);
+const socketTransportService =
+  Cc["@mozilla.org/network/socket-transport-service;1"]
+  .getService(Ci.nsISocketTransportService);
+
+function run_test() {
+  run_next_test();
+}
+
+function getCert() {
+  let deferred = promise.defer();
+  certService.getOrCreateCert("tls-test", {
+    handleCert: function(c, rv) {
+      if (rv) {
+        deferred.reject(rv);
+        return;
+      }
+      deferred.resolve(c);
+    }
+  });
+  return deferred.promise;
+}
+
+function startServer(cert) {
+  let tlsServer = Cc["@mozilla.org/network/tls-server-socket;1"]
+                  .createInstance(Ci.nsITLSServerSocket);
+  tlsServer.init(-1, true, -1);
+  tlsServer.serverCert = cert;
+
+  let input, output;
+
+  let listener = {
+    onSocketAccepted: function(socket, transport) {
+      do_print("Accept TLS client connection");
+      let connectionInfo = transport.securityInfo
+                           .QueryInterface(Ci.nsITLSServerConnectionInfo);
+      connectionInfo.setSecurityObserver(listener);
+      input = transport.openInputStream(0, 0, 0);
+      output = transport.openOutputStream(0, 0, 0);
+    },
+    onHandshakeDone: function(socket, status) {
+      do_print("TLS handshake done");
+
+      input.asyncWait({
+        onInputStreamReady: function(input) {
+          NetUtil.asyncCopy(input, output);
+        }
+      }, 0, 0, Services.tm.currentThread);
+    },
+    onStopListening: function() {}
+  };
+
+  tlsServer.setSessionCache(true);
+  tlsServer.setSessionTickets(false);
+
+  tlsServer.asyncListen(listener);
+
+  return tlsServer.port;
+}
+
+function storeCertOverride(port, cert) {
+  let overrideBits = Ci.nsICertOverrideService.ERROR_UNTRUSTED |
+                     Ci.nsICertOverrideService.ERROR_MISMATCH;
+  certOverrideService.rememberValidityOverride("127.0.0.1", port, cert,
+                                               overrideBits, true);
+}
+
+function startClient(port) {
+  let transport =
+    socketTransportService.createTransport(["ssl"], 1, "127.0.0.1", port, null);
+  let input;
+  let output;
+
+  let inputDeferred = promise.defer();
+  let outputDeferred = promise.defer();
+
+  let handler = {
+
+    onTransportStatus: function(transport, status) {
+      if (status === Ci.nsISocketTransport.STATUS_CONNECTED_TO) {
+        output.asyncWait(handler, 0, 0, Services.tm.currentThread);
+      }
+    },
+
+    onInputStreamReady: function(input) {
+      try {
+        let data = NetUtil.readInputStreamToString(input, input.available());
+        equal(data, "HELLO", "Echoed data received");
+        input.close();
+        output.close();
+        inputDeferred.resolve();
+      } catch (e) {
+        inputDeferred.reject(e);
+      }
+    },
+
+    onOutputStreamReady: function(output) {
+      try {
+        output.write("HELLO", 5);
+        do_print("Output to server written");
+        outputDeferred.resolve();
+        input = transport.openInputStream(0, 0, 0);
+        input.asyncWait(handler, 0, 0, Services.tm.currentThread);
+      } catch (e) {
+        outputDeferred.reject(e);
+      }
+    }
+
+  };
+
+  transport.setEventSink(handler, Services.tm.currentThread);
+  output = transport.openOutputStream(0, 0, 0);
+
+  return promise.all([inputDeferred.promise, outputDeferred.promise]);
+}
+
+add_task(function*() {
+  let cert = yield getCert();
+  ok(!!cert, "Got self-signed cert");
+  let port = startServer(cert);
+  storeCertOverride(port, cert);
+  yield startClient(port);
+  yield startClient(port);
+});
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -331,16 +331,19 @@ skip-if = os != "win"
 [test_udp_multicast.js]
 [test_redirect_history.js]
 [test_reply_without_content_type.js]
 [test_websocket_offline.js]
 [test_tls_server.js]
 # The local cert service used by this test is not currently shipped on Android
 skip-if = os == "android"
 firefox-appdir = browser
+[test_tls_server_multiple_clients.js]
+# The local cert service used by this test is not currently shipped on Android
+skip-if = os == "android"
 [test_1073747.js]
 [test_multipart_streamconv_application_package.js]
 [test_safeoutputstream_append.js]
 [test_packaged_app_service.js]
 [test_packaged_app_verifier.js]
 [test_packaged_app_utils.js]
 [test_suspend_channel_before_connect.js]
 [test_inhibit_caching.js]
--- a/security/manager/ssl/nsNSSComponent.cpp
+++ b/security/manager/ssl/nsNSSComponent.cpp
@@ -1582,16 +1582,27 @@ nsNSSComponent::InitializeNSS()
                        Preferences::GetBool("security.ssl.enable_alpn",
                                             ALPN_ENABLED_DEFAULT));
 
   if (NS_FAILED(InitializeCipherSuite())) {
     MOZ_LOG(gPIPNSSLog, LogLevel::Error, ("Unable to initialize cipher suite settings\n"));
     return NS_ERROR_FAILURE;
   }
 
+  // TLSServerSocket may be run with the session cache enabled. It is necessary
+  // to call this once before that can happen. This specifies a maximum of 1000
+  // cache entries (the default number of cache entries is 10000, which seems a
+  // little excessive as there probably won't be that many clients connecting to
+  // any TLSServerSockets the browser runs.)
+  // Note that this must occur before any calls to SSL_ClearSessionCache
+  // (otherwise memory will leak).
+  if (SSL_ConfigServerSessionIDCache(1000, 0, 0, nullptr) != SECSuccess) {
+    return NS_ERROR_FAILURE;
+  }
+
   // ensure the CertBlocklist is initialised
   nsCOMPtr<nsICertBlocklist> certList = do_GetService(NS_CERTBLOCKLIST_CONTRACTID);
   if (!certList) {
     return NS_ERROR_FAILURE;
   }
 
   // dynamic options from prefs
   setValidationOptions(true, lock);
@@ -1641,16 +1652,19 @@ nsNSSComponent::ShutdownNSS()
     PK11_SetPasswordFunc((PK11PasswordFunc)nullptr);
 
     Preferences::RemoveObserver(this, "security.");
 
 #ifndef MOZ_NO_SMART_CARDS
     ShutdownSmartCardThreads();
 #endif
     SSL_ClearSessionCache();
+    // TLSServerSocket may be run with the session cache enabled. This ensures
+    // those resources are cleaned up.
+    Unused << SSL_ShutdownServerSessionIDCache();
     UnloadLoadableRoots();
 #ifndef MOZ_NO_EV_CERTS
     CleanupIdentityInfo();
 #endif
     MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("evaporating psm resources\n"));
     nsNSSShutDownList::evaporateAllNSSResources();
     EnsureNSSInitialized(nssShutdown);
     if (SECSuccess != ::NSS_Shutdown()) {