diff options
Diffstat (limited to 'netwerk/test/unit/test_tls_server.js')
-rw-r--r-- | netwerk/test/unit/test_tls_server.js | 343 |
1 files changed, 343 insertions, 0 deletions
diff --git a/netwerk/test/unit/test_tls_server.js b/netwerk/test/unit/test_tls_server.js new file mode 100644 index 0000000000..37648eee0e --- /dev/null +++ b/netwerk/test/unit/test_tls_server.js @@ -0,0 +1,343 @@ +/* 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 { MockRegistrar } = ChromeUtils.importESModule( + "resource://testing-common/MockRegistrar.sys.mjs" +); +const { PromiseUtils } = ChromeUtils.importESModule( + "resource://gre/modules/PromiseUtils.sys.mjs" +); +const certOverrideService = Cc[ + "@mozilla.org/security/certoverride;1" +].getService(Ci.nsICertOverrideService); +const socketTransportService = Cc[ + "@mozilla.org/network/socket-transport-service;1" +].getService(Ci.nsISocketTransportService); + +const prefs = Services.prefs; + +function areCertsEqual(certA, certB) { + let derA = certA.getRawDER(); + let derB = certB.getRawDER(); + if (derA.length != derB.length) { + return false; + } + for (let i = 0; i < derA.length; i++) { + if (derA[i] != derB[i]) { + return false; + } + } + return true; +} + +function startServer( + cert, + expectingPeerCert, + clientCertificateConfig, + expectedVersion, + expectedVersionStr +) { + 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(socket, transport) { + info("Accept TLS client connection"); + let connectionInfo = transport.securityCallbacks.getInterface( + Ci.nsITLSServerConnectionInfo + ); + connectionInfo.setSecurityObserver(listener); + input = transport.openInputStream(0, 0, 0); + output = transport.openOutputStream(0, 0, 0); + }, + onHandshakeDone(socket, status) { + info("TLS handshake done"); + if (expectingPeerCert) { + ok(!!status.peerCert, "Has peer cert"); + ok( + areCertsEqual(status.peerCert, cert), + "Peer cert matches expected cert" + ); + } else { + ok(!status.peerCert, "No peer cert (as expected)"); + } + + equal( + status.tlsVersionUsed, + expectedVersion, + "Using " + expectedVersionStr + ); + let expectedCipher; + if (expectedVersion >= 772) { + expectedCipher = "TLS_AES_128_GCM_SHA256"; + } else { + expectedCipher = "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"; + } + equal(status.cipherName, expectedCipher, "Using expected cipher"); + equal(status.keyLength, 128, "Using 128-bit key"); + equal(status.macLength, 128, "Using 128-bit MAC"); + + input.asyncWait( + { + onInputStreamReady(input) { + NetUtil.asyncCopy(input, output); + }, + }, + 0, + 0, + Services.tm.currentThread + ); + }, + onStopListening() { + info("onStopListening"); + input.close(); + output.close(); + }, + }; + + tlsServer.setSessionTickets(false); + tlsServer.setRequestClientCertificate(clientCertificateConfig); + + tlsServer.asyncListen(listener); + + return tlsServer; +} + +function storeCertOverride(port, cert) { + certOverrideService.rememberValidityOverride( + "127.0.0.1", + port, + {}, + cert, + true + ); +} + +function startClient(port, sendClientCert, expectingAlert, tlsVersion) { + gClientAuthDialogs.selectCertificate = sendClientCert; + let SSL_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SSL_ERROR_BASE; + let SSL_ERROR_BAD_CERT_ALERT = SSL_ERROR_BASE + 17; + let SSL_ERROR_RX_CERTIFICATE_REQUIRED_ALERT = SSL_ERROR_BASE + 181; + let transport = socketTransportService.createTransport( + ["ssl"], + "127.0.0.1", + port, + null, + null + ); + let input; + let output; + + let inputDeferred = PromiseUtils.defer(); + let outputDeferred = PromiseUtils.defer(); + + let handler = { + onTransportStatus(transport, status) { + if (status === Ci.nsISocketTransport.STATUS_CONNECTED_TO) { + output.asyncWait(handler, 0, 0, Services.tm.currentThread); + } + }, + + onInputStreamReady(input) { + try { + let data = NetUtil.readInputStreamToString(input, input.available()); + equal(data, "HELLO", "Echoed data received"); + input.close(); + output.close(); + ok(!expectingAlert, "No cert alert expected"); + inputDeferred.resolve(); + } catch (e) { + let errorCode = -1 * (e.result & 0xffff); + if (expectingAlert) { + if ( + tlsVersion == Ci.nsITLSClientStatus.TLS_VERSION_1_2 && + errorCode == SSL_ERROR_BAD_CERT_ALERT + ) { + info("Got bad cert alert as expected for tls 1.2"); + input.close(); + output.close(); + inputDeferred.resolve(); + return; + } + if ( + tlsVersion == Ci.nsITLSClientStatus.TLS_VERSION_1_3 && + errorCode == SSL_ERROR_RX_CERTIFICATE_REQUIRED_ALERT + ) { + info("Got cert required alert as expected for tls 1.3"); + input.close(); + output.close(); + inputDeferred.resolve(); + return; + } + } + inputDeferred.reject(e); + } + }, + + onOutputStreamReady(output) { + try { + output.write("HELLO", 5); + info("Output to server written"); + outputDeferred.resolve(); + input = transport.openInputStream(0, 0, 0); + input.asyncWait(handler, 0, 0, Services.tm.currentThread); + } catch (e) { + let errorCode = -1 * (e.result & 0xffff); + if (errorCode == SSL_ERROR_BAD_CERT_ALERT) { + info("Server doesn't like client cert"); + } + outputDeferred.reject(e); + } + }, + }; + + transport.setEventSink(handler, Services.tm.currentThread); + output = transport.openOutputStream(0, 0, 0); + + return Promise.all([inputDeferred.promise, outputDeferred.promise]); +} + +// Replace the UI dialog that prompts the user to pick a client certificate. +const gClientAuthDialogs = { + _selectCertificate: false, + + set selectCertificate(value) { + this._selectCertificate = value; + }, + + chooseCertificate( + hostname, + port, + organization, + issuerOrg, + certList, + selectedIndex, + rememberClientAuthCertificate + ) { + rememberClientAuthCertificate.value = false; + if (this._selectCertificate) { + selectedIndex.value = 0; + return true; + } + return false; + }, + + QueryInterface: ChromeUtils.generateQI([Ci.nsIClientAuthDialogs]), +}; + +const ClientAuthDialogsContractID = "@mozilla.org/nsClientAuthDialogs;1"; +// On all platforms but Android, this component already exists, so this replaces +// it. On Android, the component does not exist, so this registers it. +if (AppConstants.platform != "android") { + MockRegistrar.register(ClientAuthDialogsContractID, gClientAuthDialogs); +} else { + const factory = { + createInstance(iid) { + return gClientAuthDialogs.QueryInterface(iid); + }, + + QueryInterface: ChromeUtils.generateQI(["nsIFactory"]), + }; + const Cm = Components.manager; + const registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar); + const cid = Services.uuid.generateUUID(); + registrar.registerFactory( + cid, + "A Mock for " + ClientAuthDialogsContractID, + ClientAuthDialogsContractID, + factory + ); +} + +const tests = [ + { + expectingPeerCert: true, + clientCertificateConfig: Ci.nsITLSServerSocket.REQUIRE_ALWAYS, + sendClientCert: true, + expectingAlert: false, + }, + { + expectingPeerCert: true, + clientCertificateConfig: Ci.nsITLSServerSocket.REQUIRE_ALWAYS, + sendClientCert: false, + expectingAlert: true, + }, + { + expectingPeerCert: true, + clientCertificateConfig: Ci.nsITLSServerSocket.REQUEST_ALWAYS, + sendClientCert: true, + expectingAlert: false, + }, + { + expectingPeerCert: false, + clientCertificateConfig: Ci.nsITLSServerSocket.REQUEST_ALWAYS, + sendClientCert: false, + expectingAlert: false, + }, + { + expectingPeerCert: false, + clientCertificateConfig: Ci.nsITLSServerSocket.REQUEST_NEVER, + sendClientCert: true, + expectingAlert: false, + }, + { + expectingPeerCert: false, + clientCertificateConfig: Ci.nsITLSServerSocket.REQUEST_NEVER, + sendClientCert: false, + expectingAlert: false, + }, +]; + +const versions = [ + { + prefValue: 3, + version: Ci.nsITLSClientStatus.TLS_VERSION_1_2, + versionStr: "TLS 1.2", + }, + { + prefValue: 4, + version: Ci.nsITLSClientStatus.TLS_VERSION_1_3, + versionStr: "TLS 1.3", + }, +]; + +add_task(async function() { + let cert = getTestServerCertificate(); + ok(!!cert, "Got self-signed cert"); + for (let v of versions) { + prefs.setIntPref("security.tls.version.max", v.prefValue); + for (let t of tests) { + let server = startServer( + cert, + t.expectingPeerCert, + t.clientCertificateConfig, + v.version, + v.versionStr + ); + storeCertOverride(server.port, cert); + await startClient( + server.port, + t.sendClientCert, + t.expectingAlert, + v.version + ); + server.close(); + } + } +}); + +registerCleanupFunction(function() { + prefs.clearUserPref("security.tls.version.max"); +}); |