summaryrefslogtreecommitdiffstats
path: root/browser/components/urlbar/tests/browser/browser_speculative_connect_not_with_client_cert.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/urlbar/tests/browser/browser_speculative_connect_not_with_client_cert.js')
-rw-r--r--browser/components/urlbar/tests/browser/browser_speculative_connect_not_with_client_cert.js236
1 files changed, 236 insertions, 0 deletions
diff --git a/browser/components/urlbar/tests/browser/browser_speculative_connect_not_with_client_cert.js b/browser/components/urlbar/tests/browser/browser_speculative_connect_not_with_client_cert.js
new file mode 100644
index 0000000000..de352efb59
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_speculative_connect_not_with_client_cert.js
@@ -0,0 +1,236 @@
+/* eslint-disable mozilla/no-arbitrary-setTimeout */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// Tests that we don't speculatively connect when user certificates are installed
+
+const { MockRegistrar } = ChromeUtils.importESModule(
+ "resource://testing-common/MockRegistrar.sys.mjs"
+);
+
+const certOverrideService = Cc[
+ "@mozilla.org/security/certoverride;1"
+].getService(Ci.nsICertOverrideService);
+
+const host = "localhost";
+let uri;
+let handshakeDone = false;
+let expectingChooseCertificate = false;
+let chooseCertificateCalled = false;
+
+const clientAuthDialogs = {
+ chooseCertificate(
+ hostname,
+ port,
+ organization,
+ issuerOrg,
+ certList,
+ selectedIndex,
+ rememberClientAuthCertificate
+ ) {
+ ok(
+ expectingChooseCertificate,
+ `${
+ expectingChooseCertificate ? "" : "not "
+ }expecting chooseCertificate to be called`
+ );
+ is(certList.length, 1, "should have only one client certificate available");
+ selectedIndex.value = 0;
+ rememberClientAuthCertificate.value = false;
+ ok(
+ !chooseCertificateCalled,
+ "chooseCertificate should only be called once"
+ );
+ chooseCertificateCalled = true;
+ return true;
+ },
+
+ QueryInterface: ChromeUtils.generateQI(["nsIClientAuthDialogs"]),
+};
+
+/**
+ * A helper class to use with nsITLSServerConnectionInfo.setSecurityObserver.
+ * Implements nsITLSServerSecurityObserver and simulates an extremely
+ * rudimentary HTTP server that expects an HTTP/1.1 GET request and responds
+ * with a 200 OK.
+ */
+class SecurityObserver {
+ constructor(input, output) {
+ this.input = input;
+ this.output = output;
+ }
+
+ onHandshakeDone(socket, status) {
+ info("TLS handshake done");
+ handshakeDone = true;
+
+ let output = this.output;
+ this.input.asyncWait(
+ {
+ onInputStreamReady(readyInput) {
+ try {
+ let request = NetUtil.readInputStreamToString(
+ readyInput,
+ readyInput.available()
+ );
+ ok(
+ request.startsWith("GET /") && request.includes("HTTP/1.1"),
+ "expecting an HTTP/1.1 GET request"
+ );
+ let response =
+ "HTTP/1.1 200 OK\r\nContent-Type:text/plain\r\n" +
+ "Connection:Close\r\nContent-Length:2\r\n\r\nOK";
+ output.write(response, response.length);
+ } catch (e) {
+ console.log(e.message);
+ // This will fail when we close the speculative connection.
+ }
+ },
+ },
+ 0,
+ 0,
+ Services.tm.currentThread
+ );
+ }
+}
+
+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 securityObservers = [];
+
+ let listener = {
+ onSocketAccepted(socket, transport) {
+ info("Accepted TLS client connection");
+ let connectionInfo = transport.securityCallbacks.getInterface(
+ Ci.nsITLSServerConnectionInfo
+ );
+ let input = transport.openInputStream(0, 0, 0);
+ let output = transport.openOutputStream(0, 0, 0);
+ connectionInfo.setSecurityObserver(new SecurityObserver(input, output));
+ },
+
+ onStopListening() {
+ info("onStopListening");
+ for (let securityObserver of securityObservers) {
+ securityObserver.input.close();
+ securityObserver.output.close();
+ }
+ },
+ };
+
+ tlsServer.setSessionTickets(false);
+ tlsServer.setRequestClientCertificate(Ci.nsITLSServerSocket.REQUEST_ALWAYS);
+
+ tlsServer.asyncListen(listener);
+
+ return tlsServer;
+}
+
+let server;
+
+function getTestServerCertificate() {
+ const certDB = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ for (const cert of certDB.getCerts()) {
+ if (cert.commonName == "Mochitest client") {
+ return cert;
+ }
+ }
+ return null;
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.urlbar.autoFill", true],
+ // Turn off search suggestion so we won't speculative connect to the search engine.
+ ["browser.search.suggest.enabled", false],
+ ["browser.urlbar.speculativeConnect.enabled", true],
+ // In mochitest this number is 0 by default but we have to turn it on.
+ ["network.http.speculative-parallel-limit", 6],
+ // The http server is using IPv4, so it's better to disable IPv6 to avoid weird
+ // networking problem.
+ ["network.dns.disableIPv6", true],
+ ["security.default_personal_cert", "Ask Every Time"],
+ ],
+ });
+
+ let clientAuthDialogsCID = MockRegistrar.register(
+ "@mozilla.org/nsClientAuthDialogs;1",
+ clientAuthDialogs
+ );
+
+ let cert = getTestServerCertificate();
+ server = startServer(cert);
+ uri = `https://${host}:${server.port}/`;
+ info(`running tls server at ${uri}`);
+ await PlacesTestUtils.addVisits([
+ {
+ uri,
+ title: "test visit for speculative connection",
+ transition: Ci.nsINavHistoryService.TRANSITION_TYPED,
+ },
+ ]);
+
+ certOverrideService.rememberValidityOverride(
+ "localhost",
+ server.port,
+ {},
+ cert,
+ true
+ );
+
+ registerCleanupFunction(async function () {
+ await PlacesUtils.history.clear();
+ MockRegistrar.unregister(clientAuthDialogsCID);
+ certOverrideService.clearValidityOverride("localhost", server.port, {});
+ });
+});
+
+add_task(
+ async function popup_mousedown_no_client_cert_dialog_until_navigate_test() {
+ // To not trigger autofill, search keyword starts from the second character.
+ let searchString = host.substr(1, 4);
+ let completeValue = uri;
+ info(`Searching for '${searchString}'`);
+ await UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ value: searchString,
+ fireInputEvent: true,
+ });
+ let listitem = await UrlbarTestUtils.waitForAutocompleteResultAt(window, 1);
+ let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 1);
+ info(`The url of the second item is ${details.url}`);
+ is(details.url, completeValue, "The second item has the url we visited.");
+
+ expectingChooseCertificate = false;
+ EventUtils.synthesizeMouseAtCenter(listitem, { type: "mousedown" }, window);
+ is(
+ UrlbarTestUtils.getSelectedRow(window),
+ listitem,
+ "The second item is selected"
+ );
+
+ // We shouldn't have triggered a speculative connection, because a client
+ // certificate is installed.
+ SimpleTest.requestFlakyTimeout("Wait for UI");
+ await new Promise(resolve => setTimeout(resolve, 200));
+
+ // Now mouseup, expect that we choose a client certificate, and expect that
+ // we successfully load a page.
+ expectingChooseCertificate = true;
+ EventUtils.synthesizeMouseAtCenter(listitem, { type: "mouseup" }, window);
+ await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ ok(chooseCertificateCalled, "chooseCertificate must have been called");
+ server.close();
+ }
+);