forked from mirrors/gecko-dev
As initially implemented, nsITLSServerSocket by default enabled the use of the TLS session cache provided by NSS. However, no consumers of nsITLSServerSocket actually used it. Because it was an option, though, PSM had to jump through some hoops to a) make it work in the first place and b) not have NSS panic on shutdown. Furthermore, it meant increased memory usage for every user of Firefox (and again, nothing actually used the feature, so this was for naught). In bug 1479918, we discovered that if PSM shut down before Necko, NSS could attempt to acquire a lock on the session cache that had been deleted, causing a shutdown hang. We probably should make it less easy to make this mistake in NSS, but in the meantime bug 1479918 needs uplifting and this workaround is the safest, most straight-forward way to achieve this. Differential Revision: https://phabricator.services.mozilla.com/D3919 --HG-- extra : moz-landing-system : lando
244 lines
7.8 KiB
JavaScript
244 lines
7.8 KiB
JavaScript
/* 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 } = ChromeUtils.import("resource://gre/modules/Services.jsm", {});
|
|
const { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm", {});
|
|
const { PromiseUtils } = ChromeUtils.import("resource://gre/modules/PromiseUtils.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);
|
|
|
|
const prefs = Cc["@mozilla.org/preferences-service;1"]
|
|
.getService(Ci.nsIPrefBranch);
|
|
|
|
function run_test() {
|
|
run_next_test();
|
|
}
|
|
|
|
function getCert() {
|
|
return new Promise((resolve, reject) => {
|
|
certService.getOrCreateCert("tls-test", {
|
|
handleCert: function(c, rv) {
|
|
if (rv) {
|
|
reject(rv);
|
|
return;
|
|
}
|
|
resolve(c);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
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: function(socket, transport) {
|
|
info("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) {
|
|
info("TLS handshake done");
|
|
if (expectingPeerCert) {
|
|
ok(!!status.peerCert, "Has peer cert");
|
|
ok(status.peerCert.equals(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_ECDSA_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: function(input) {
|
|
NetUtil.asyncCopy(input, output);
|
|
}
|
|
}, 0, 0, Services.tm.currentThread);
|
|
},
|
|
onStopListening: function() {
|
|
info("onStopListening");
|
|
input.close();
|
|
output.close();
|
|
}
|
|
};
|
|
|
|
tlsServer.setSessionTickets(false);
|
|
tlsServer.setRequestClientCertificate(clientCertificateConfig);
|
|
|
|
tlsServer.asyncListen(listener);
|
|
|
|
return tlsServer;
|
|
}
|
|
|
|
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, cert, expectingBadCertAlert) {
|
|
let SSL_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SSL_ERROR_BASE;
|
|
let SSL_ERROR_BAD_CERT_ALERT = SSL_ERROR_BASE + 17;
|
|
let transport =
|
|
socketTransportService.createTransport(["ssl"], 1, "127.0.0.1", port, null);
|
|
let input;
|
|
let output;
|
|
|
|
let inputDeferred = PromiseUtils.defer();
|
|
let outputDeferred = PromiseUtils.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();
|
|
ok(!expectingBadCertAlert, "No bad cert alert expected");
|
|
inputDeferred.resolve();
|
|
} catch (e) {
|
|
let errorCode = -1 * (e.result & 0xFFFF);
|
|
if (expectingBadCertAlert && errorCode == SSL_ERROR_BAD_CERT_ALERT) {
|
|
info("Got bad cert alert as expected");
|
|
input.close();
|
|
output.close();
|
|
inputDeferred.resolve();
|
|
} else {
|
|
inputDeferred.reject(e);
|
|
}
|
|
}
|
|
},
|
|
|
|
onOutputStreamReady: function(output) {
|
|
try {
|
|
// Set the client certificate as appropriate.
|
|
if (cert) {
|
|
let clientSecInfo = transport.securityInfo;
|
|
let tlsControl = clientSecInfo.QueryInterface(Ci.nsISSLSocketControl);
|
|
tlsControl.clientCert = cert;
|
|
}
|
|
|
|
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.
|
|
do_load_manifest("client_cert_chooser.manifest");
|
|
|
|
const tests = [{
|
|
expectingPeerCert: true,
|
|
clientCertificateConfig: Ci.nsITLSServerSocket.REQUIRE_ALWAYS,
|
|
sendClientCert: true,
|
|
expectingBadCertAlert: false
|
|
}, {
|
|
expectingPeerCert: true,
|
|
clientCertificateConfig: Ci.nsITLSServerSocket.REQUIRE_ALWAYS,
|
|
sendClientCert: false,
|
|
expectingBadCertAlert: true
|
|
}, {
|
|
expectingPeerCert: true,
|
|
clientCertificateConfig: Ci.nsITLSServerSocket.REQUEST_ALWAYS,
|
|
sendClientCert: true,
|
|
expectingBadCertAlert: false
|
|
}, {
|
|
expectingPeerCert: false,
|
|
clientCertificateConfig: Ci.nsITLSServerSocket.REQUEST_ALWAYS,
|
|
sendClientCert: false,
|
|
expectingBadCertAlert: false
|
|
}, {
|
|
expectingPeerCert: false,
|
|
clientCertificateConfig: Ci.nsITLSServerSocket.REQUEST_NEVER,
|
|
sendClientCert: true,
|
|
expectingBadCertAlert: false
|
|
}, {
|
|
expectingPeerCert: false,
|
|
clientCertificateConfig: Ci.nsITLSServerSocket.REQUEST_NEVER,
|
|
sendClientCert: false,
|
|
expectingBadCertAlert: 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 = await getCert();
|
|
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 ? cert : null,
|
|
t.expectingBadCertAlert);
|
|
server.close();
|
|
}
|
|
}
|
|
});
|
|
|
|
registerCleanupFunction(function() {
|
|
prefs.clearUserPref("security.tls.version.max");
|
|
});
|