diff --git a/config/external/nss/nss.symbols b/config/external/nss/nss.symbols index e0db9b105f2f..6e878ec8b9be 100644 --- a/config/external/nss/nss.symbols +++ b/config/external/nss/nss.symbols @@ -690,6 +690,7 @@ SSL_SetSockPeerID SSL_SetSRTPCiphers SSL_SetStapledOCSPResponses SSL_SetURL +SSL_ShutdownServerSessionIDCache SSL_SNISocketConfigHook SSL_VersionRangeGet SSL_VersionRangeGetDefault diff --git a/netwerk/test/unit/test_tls_server_multiple_clients.js b/netwerk/test/unit/test_tls_server_multiple_clients.js new file mode 100644 index 000000000000..b63c0189bd20 --- /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); +}); diff --git a/netwerk/test/unit/xpcshell.ini b/netwerk/test/unit/xpcshell.ini index ca75b659eae0..3e5294bd0413 100644 --- a/netwerk/test/unit/xpcshell.ini +++ b/netwerk/test/unit/xpcshell.ini @@ -336,6 +336,9 @@ skip-if = os != "win" # 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] diff --git a/security/manager/ssl/nsNSSComponent.cpp b/security/manager/ssl/nsNSSComponent.cpp index f6510d0e6fa9..fad1076d624a 100644 --- a/security/manager/ssl/nsNSSComponent.cpp +++ b/security/manager/ssl/nsNSSComponent.cpp @@ -1591,6 +1591,17 @@ nsNSSComponent::InitializeNSS() 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 certList = do_GetService(NS_CERTBLOCKLIST_CONTRACTID); if (!certList) { @@ -1650,6 +1661,9 @@ nsNSSComponent::ShutdownNSS() 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();