summaryrefslogtreecommitdiffstats
path: root/devtools/shared/security/tests/unit
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/shared/security/tests/unit')
-rw-r--r--devtools/shared/security/tests/unit/.eslintrc.js6
-rw-r--r--devtools/shared/security/tests/unit/head_dbg.js96
-rw-r--r--devtools/shared/security/tests/unit/test_encryption.js110
-rw-r--r--devtools/shared/security/tests/unit/test_oob_cert_auth.js261
-rw-r--r--devtools/shared/security/tests/unit/testactors.js131
-rw-r--r--devtools/shared/security/tests/unit/xpcshell.ini12
6 files changed, 616 insertions, 0 deletions
diff --git a/devtools/shared/security/tests/unit/.eslintrc.js b/devtools/shared/security/tests/unit/.eslintrc.js
new file mode 100644
index 000000000..59adf410a
--- /dev/null
+++ b/devtools/shared/security/tests/unit/.eslintrc.js
@@ -0,0 +1,6 @@
+"use strict";
+
+module.exports = {
+ // Extend from the common devtools xpcshell eslintrc config.
+ "extends": "../../../../.eslintrc.xpcshell.js"
+};
diff --git a/devtools/shared/security/tests/unit/head_dbg.js b/devtools/shared/security/tests/unit/head_dbg.js
new file mode 100644
index 000000000..f3b2a9a97
--- /dev/null
+++ b/devtools/shared/security/tests/unit/head_dbg.js
@@ -0,0 +1,96 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+var Cr = Components.results;
+var CC = Components.Constructor;
+
+const { require } =
+ Cu.import("resource://devtools/shared/Loader.jsm", {});
+const promise = require("promise");
+const defer = require("devtools/shared/defer");
+const { Task } = require("devtools/shared/task");
+
+const Services = require("Services");
+const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const xpcInspector = require("xpcInspector");
+const { DebuggerServer } = require("devtools/server/main");
+const { DebuggerClient } = require("devtools/shared/client/main");
+
+// We do not want to log packets by default, because in some tests,
+// we can be sending large amounts of data. The test harness has
+// trouble dealing with logging all the data, and we end up with
+// intermittent time outs (e.g. bug 775924).
+// Services.prefs.setBoolPref("devtools.debugger.log", true);
+// Services.prefs.setBoolPref("devtools.debugger.log.verbose", true);
+// Enable remote debugging for the relevant tests.
+Services.prefs.setBoolPref("devtools.debugger.remote-enabled", true);
+// Fast timeout for TLS tests
+Services.prefs.setIntPref("devtools.remote.tls-handshake-timeout", 1000);
+
+// Convert an nsIScriptError 'aFlags' value into an appropriate string.
+function scriptErrorFlagsToKind(aFlags) {
+ var kind;
+ if (aFlags & Ci.nsIScriptError.warningFlag)
+ kind = "warning";
+ if (aFlags & Ci.nsIScriptError.exceptionFlag)
+ kind = "exception";
+ else
+ kind = "error";
+
+ if (aFlags & Ci.nsIScriptError.strictFlag)
+ kind = "strict " + kind;
+
+ return kind;
+}
+
+// Register a console listener, so console messages don't just disappear
+// into the ether.
+var errorCount = 0;
+var listener = {
+ observe: function (aMessage) {
+ errorCount++;
+ try {
+ // If we've been given an nsIScriptError, then we can print out
+ // something nicely formatted, for tools like Emacs to pick up.
+ var scriptError = aMessage.QueryInterface(Ci.nsIScriptError);
+ dump(aMessage.sourceName + ":" + aMessage.lineNumber + ": " +
+ scriptErrorFlagsToKind(aMessage.flags) + ": " +
+ aMessage.errorMessage + "\n");
+ var string = aMessage.errorMessage;
+ } catch (x) {
+ // Be a little paranoid with message, as the whole goal here is to lose
+ // no information.
+ try {
+ var string = "" + aMessage.message;
+ } catch (x) {
+ var string = "<error converting error message to string>";
+ }
+ }
+
+ // Make sure we exit all nested event loops so that the test can finish.
+ while (xpcInspector.eventLoopNestLevel > 0) {
+ xpcInspector.exitNestedEventLoop();
+ }
+
+ // Print in most cases, but ignore the "strict" messages
+ if (!(aMessage.flags & Ci.nsIScriptError.strictFlag)) {
+ do_print("head_dbg.js got console message: " + string + "\n");
+ }
+ }
+};
+
+var consoleService = Cc["@mozilla.org/consoleservice;1"]
+ .getService(Ci.nsIConsoleService);
+consoleService.registerListener(listener);
+
+/**
+ * Initialize the testing debugger server.
+ */
+function initTestDebuggerServer() {
+ DebuggerServer.registerModule("xpcshell-test/testactors");
+ DebuggerServer.init();
+}
diff --git a/devtools/shared/security/tests/unit/test_encryption.js b/devtools/shared/security/tests/unit/test_encryption.js
new file mode 100644
index 000000000..e7fc80f5d
--- /dev/null
+++ b/devtools/shared/security/tests/unit/test_encryption.js
@@ -0,0 +1,110 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test basic functionality of DevTools client and server TLS encryption mode
+function run_test() {
+ // Need profile dir to store the key / cert
+ do_get_profile();
+ // Ensure PSM is initialized
+ Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
+ run_next_test();
+}
+
+function connectClient(client) {
+ let deferred = defer();
+ client.connect(() => {
+ client.listTabs(deferred.resolve);
+ });
+ return deferred.promise;
+}
+
+add_task(function* () {
+ initTestDebuggerServer();
+});
+
+// Client w/ encryption connects successfully to server w/ encryption
+add_task(function* () {
+ equal(DebuggerServer.listeningSockets, 0, "0 listening sockets");
+
+ let AuthenticatorType = DebuggerServer.Authenticators.get("PROMPT");
+ let authenticator = new AuthenticatorType.Server();
+ authenticator.allowConnection = () => {
+ return DebuggerServer.AuthenticationResult.ALLOW;
+ };
+
+ let listener = DebuggerServer.createListener();
+ ok(listener, "Socket listener created");
+ listener.portOrPath = -1;
+ listener.authenticator = authenticator;
+ listener.encryption = true;
+ yield listener.open();
+ equal(DebuggerServer.listeningSockets, 1, "1 listening socket");
+
+ let transport = yield DebuggerClient.socketConnect({
+ host: "127.0.0.1",
+ port: listener.port,
+ encryption: true
+ });
+ ok(transport, "Client transport created");
+
+ let client = new DebuggerClient(transport);
+ let onUnexpectedClose = () => {
+ do_throw("Closed unexpectedly");
+ };
+ client.addListener("closed", onUnexpectedClose);
+ yield connectClient(client);
+
+ // Send a message the server will echo back
+ let message = "secrets";
+ let reply = yield client.request({
+ to: "root",
+ type: "echo",
+ message
+ });
+ equal(reply.message, message, "Encrypted echo matches");
+
+ client.removeListener("closed", onUnexpectedClose);
+ transport.close();
+ listener.close();
+ equal(DebuggerServer.listeningSockets, 0, "0 listening sockets");
+});
+
+// Client w/o encryption fails to connect to server w/ encryption
+add_task(function* () {
+ equal(DebuggerServer.listeningSockets, 0, "0 listening sockets");
+
+ let AuthenticatorType = DebuggerServer.Authenticators.get("PROMPT");
+ let authenticator = new AuthenticatorType.Server();
+ authenticator.allowConnection = () => {
+ return DebuggerServer.AuthenticationResult.ALLOW;
+ };
+
+ let listener = DebuggerServer.createListener();
+ ok(listener, "Socket listener created");
+ listener.portOrPath = -1;
+ listener.authenticator = authenticator;
+ listener.encryption = true;
+ yield listener.open();
+ equal(DebuggerServer.listeningSockets, 1, "1 listening socket");
+
+ try {
+ yield DebuggerClient.socketConnect({
+ host: "127.0.0.1",
+ port: listener.port
+ // encryption: false is the default
+ });
+ } catch (e) {
+ ok(true, "Client failed to connect as expected");
+ listener.close();
+ equal(DebuggerServer.listeningSockets, 0, "0 listening sockets");
+ return;
+ }
+
+ do_throw("Connection unexpectedly succeeded");
+});
+
+add_task(function* () {
+ DebuggerServer.destroy();
+});
diff --git a/devtools/shared/security/tests/unit/test_oob_cert_auth.js b/devtools/shared/security/tests/unit/test_oob_cert_auth.js
new file mode 100644
index 000000000..1e820af52
--- /dev/null
+++ b/devtools/shared/security/tests/unit/test_oob_cert_auth.js
@@ -0,0 +1,261 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var cert = require("devtools/shared/security/cert");
+
+// Test basic functionality of DevTools client and server OOB_CERT auth (used
+// with WiFi debugging)
+function run_test() {
+ // Need profile dir to store the key / cert
+ do_get_profile();
+ // Ensure PSM is initialized
+ Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
+ run_next_test();
+}
+
+function connectClient(client) {
+ return client.connect(() => {
+ return client.listTabs();
+ });
+}
+
+add_task(function* () {
+ initTestDebuggerServer();
+});
+
+// Client w/ OOB_CERT auth connects successfully to server w/ OOB_CERT auth
+add_task(function* () {
+ equal(DebuggerServer.listeningSockets, 0, "0 listening sockets");
+
+ // Grab our cert, instead of relying on a discovery advertisement
+ let serverCert = yield cert.local.getOrCreate();
+
+ let oobData = defer();
+ let AuthenticatorType = DebuggerServer.Authenticators.get("OOB_CERT");
+ let serverAuth = new AuthenticatorType.Server();
+ serverAuth.allowConnection = () => {
+ return DebuggerServer.AuthenticationResult.ALLOW;
+ };
+ serverAuth.receiveOOB = () => oobData.promise; // Skip prompt for tests
+
+ let listener = DebuggerServer.createListener();
+ ok(listener, "Socket listener created");
+ listener.portOrPath = -1;
+ listener.encryption = true;
+ listener.authenticator = serverAuth;
+ yield listener.open();
+ equal(DebuggerServer.listeningSockets, 1, "1 listening socket");
+
+ let clientAuth = new AuthenticatorType.Client();
+ clientAuth.sendOOB = ({ oob }) => {
+ do_print(oob);
+ // Pass to server, skipping prompt for tests
+ oobData.resolve(oob);
+ };
+
+ let transport = yield DebuggerClient.socketConnect({
+ host: "127.0.0.1",
+ port: listener.port,
+ encryption: true,
+ authenticator: clientAuth,
+ cert: {
+ sha256: serverCert.sha256Fingerprint
+ }
+ });
+ ok(transport, "Client transport created");
+
+ let client = new DebuggerClient(transport);
+ let onUnexpectedClose = () => {
+ do_throw("Closed unexpectedly");
+ };
+ client.addListener("closed", onUnexpectedClose);
+ yield connectClient(client);
+
+ // Send a message the server will echo back
+ let message = "secrets";
+ let reply = yield client.request({
+ to: "root",
+ type: "echo",
+ message
+ });
+ equal(reply.message, message, "Encrypted echo matches");
+
+ client.removeListener("closed", onUnexpectedClose);
+ transport.close();
+ listener.close();
+ equal(DebuggerServer.listeningSockets, 0, "0 listening sockets");
+});
+
+// Client w/o OOB_CERT auth fails to connect to server w/ OOB_CERT auth
+add_task(function* () {
+ equal(DebuggerServer.listeningSockets, 0, "0 listening sockets");
+
+ let oobData = defer();
+ let AuthenticatorType = DebuggerServer.Authenticators.get("OOB_CERT");
+ let serverAuth = new AuthenticatorType.Server();
+ serverAuth.allowConnection = () => {
+ return DebuggerServer.AuthenticationResult.ALLOW;
+ };
+ serverAuth.receiveOOB = () => oobData.promise; // Skip prompt for tests
+
+ let listener = DebuggerServer.createListener();
+ ok(listener, "Socket listener created");
+ listener.portOrPath = -1;
+ listener.encryption = true;
+ listener.authenticator = serverAuth;
+ yield listener.open();
+ equal(DebuggerServer.listeningSockets, 1, "1 listening socket");
+
+ // This will succeed, but leaves the client in confused state, and no data is
+ // actually accessible
+ let transport = yield DebuggerClient.socketConnect({
+ host: "127.0.0.1",
+ port: listener.port,
+ encryption: true
+ // authenticator: PROMPT is the default
+ });
+
+ // Attempt to use the transport
+ let deferred = defer();
+ let client = new DebuggerClient(transport);
+ client.onPacket = packet => {
+ // Client did not authenticate, so it ends up seeing the server's auth data
+ // which is effectively malformed data from the client's perspective
+ ok(!packet.from && packet.authResult, "Got auth packet instead of data");
+ deferred.resolve();
+ };
+ client.connect();
+ yield deferred.promise;
+
+ // Try to send a message the server will echo back
+ let message = "secrets";
+ try {
+ yield client.request({
+ to: "root",
+ type: "echo",
+ message
+ });
+ } catch (e) {
+ ok(true, "Sending a message failed");
+ transport.close();
+ listener.close();
+ equal(DebuggerServer.listeningSockets, 0, "0 listening sockets");
+ return;
+ }
+
+ do_throw("Connection unexpectedly succeeded");
+});
+
+// Client w/ invalid K value fails to connect
+add_task(function* () {
+ equal(DebuggerServer.listeningSockets, 0, "0 listening sockets");
+
+ // Grab our cert, instead of relying on a discovery advertisement
+ let serverCert = yield cert.local.getOrCreate();
+
+ let oobData = defer();
+ let AuthenticatorType = DebuggerServer.Authenticators.get("OOB_CERT");
+ let serverAuth = new AuthenticatorType.Server();
+ serverAuth.allowConnection = () => {
+ return DebuggerServer.AuthenticationResult.ALLOW;
+ };
+ serverAuth.receiveOOB = () => oobData.promise; // Skip prompt for tests
+
+ let clientAuth = new AuthenticatorType.Client();
+ clientAuth.sendOOB = ({ oob }) => {
+ do_print(oob);
+ do_print("Modifying K value, should fail");
+ // Pass to server, skipping prompt for tests
+ oobData.resolve({
+ k: oob.k + 1,
+ sha256: oob.sha256
+ });
+ };
+
+ let listener = DebuggerServer.createListener();
+ ok(listener, "Socket listener created");
+ listener.portOrPath = -1;
+ listener.encryption = true;
+ listener.authenticator = serverAuth;
+ yield listener.open();
+ equal(DebuggerServer.listeningSockets, 1, "1 listening socket");
+
+ try {
+ yield DebuggerClient.socketConnect({
+ host: "127.0.0.1",
+ port: listener.port,
+ encryption: true,
+ authenticator: clientAuth,
+ cert: {
+ sha256: serverCert.sha256Fingerprint
+ }
+ });
+ } catch (e) {
+ ok(true, "Client failed to connect as expected");
+ listener.close();
+ equal(DebuggerServer.listeningSockets, 0, "0 listening sockets");
+ return;
+ }
+
+ do_throw("Connection unexpectedly succeeded");
+});
+
+// Client w/ invalid cert hash fails to connect
+add_task(function* () {
+ equal(DebuggerServer.listeningSockets, 0, "0 listening sockets");
+
+ // Grab our cert, instead of relying on a discovery advertisement
+ let serverCert = yield cert.local.getOrCreate();
+
+ let oobData = defer();
+ let AuthenticatorType = DebuggerServer.Authenticators.get("OOB_CERT");
+ let serverAuth = new AuthenticatorType.Server();
+ serverAuth.allowConnection = () => {
+ return DebuggerServer.AuthenticationResult.ALLOW;
+ };
+ serverAuth.receiveOOB = () => oobData.promise; // Skip prompt for tests
+
+ let clientAuth = new AuthenticatorType.Client();
+ clientAuth.sendOOB = ({ oob }) => {
+ do_print(oob);
+ do_print("Modifying cert hash, should fail");
+ // Pass to server, skipping prompt for tests
+ oobData.resolve({
+ k: oob.k,
+ sha256: oob.sha256 + 1
+ });
+ };
+
+ let listener = DebuggerServer.createListener();
+ ok(listener, "Socket listener created");
+ listener.portOrPath = -1;
+ listener.encryption = true;
+ listener.authenticator = serverAuth;
+ yield listener.open();
+ equal(DebuggerServer.listeningSockets, 1, "1 listening socket");
+
+ try {
+ yield DebuggerClient.socketConnect({
+ host: "127.0.0.1",
+ port: listener.port,
+ encryption: true,
+ authenticator: clientAuth,
+ cert: {
+ sha256: serverCert.sha256Fingerprint
+ }
+ });
+ } catch (e) {
+ ok(true, "Client failed to connect as expected");
+ listener.close();
+ equal(DebuggerServer.listeningSockets, 0, "0 listening sockets");
+ return;
+ }
+
+ do_throw("Connection unexpectedly succeeded");
+});
+
+add_task(function* () {
+ DebuggerServer.destroy();
+});
diff --git a/devtools/shared/security/tests/unit/testactors.js b/devtools/shared/security/tests/unit/testactors.js
new file mode 100644
index 000000000..80d5d4e18
--- /dev/null
+++ b/devtools/shared/security/tests/unit/testactors.js
@@ -0,0 +1,131 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { ActorPool, appendExtraActors, createExtraActors } =
+ require("devtools/server/actors/common");
+const { RootActor } = require("devtools/server/actors/root");
+const { ThreadActor } = require("devtools/server/actors/script");
+const { DebuggerServer } = require("devtools/server/main");
+const promise = require("promise");
+
+var gTestGlobals = [];
+DebuggerServer.addTestGlobal = function (aGlobal) {
+ gTestGlobals.push(aGlobal);
+};
+
+// A mock tab list, for use by tests. This simply presents each global in
+// gTestGlobals as a tab, and the list is fixed: it never calls its
+// onListChanged handler.
+//
+// As implemented now, we consult gTestGlobals when we're constructed, not
+// when we're iterated over, so tests have to add their globals before the
+// root actor is created.
+function TestTabList(aConnection) {
+ this.conn = aConnection;
+
+ // An array of actors for each global added with
+ // DebuggerServer.addTestGlobal.
+ this._tabActors = [];
+
+ // A pool mapping those actors' names to the actors.
+ this._tabActorPool = new ActorPool(aConnection);
+
+ for (let global of gTestGlobals) {
+ let actor = new TestTabActor(aConnection, global);
+ actor.selected = false;
+ this._tabActors.push(actor);
+ this._tabActorPool.addActor(actor);
+ }
+ if (this._tabActors.length > 0) {
+ this._tabActors[0].selected = true;
+ }
+
+ aConnection.addActorPool(this._tabActorPool);
+}
+
+TestTabList.prototype = {
+ constructor: TestTabList,
+ getList: function () {
+ return promise.resolve([...this._tabActors]);
+ }
+};
+
+function createRootActor(aConnection) {
+ let root = new RootActor(aConnection, {
+ tabList: new TestTabList(aConnection),
+ globalActorFactories: DebuggerServer.globalActorFactories
+ });
+ root.applicationType = "xpcshell-tests";
+ return root;
+}
+
+function TestTabActor(aConnection, aGlobal) {
+ this.conn = aConnection;
+ this._global = aGlobal;
+ this._threadActor = new ThreadActor(this, this._global);
+ this.conn.addActor(this._threadActor);
+ this._attached = false;
+ this._extraActors = {};
+}
+
+TestTabActor.prototype = {
+ constructor: TestTabActor,
+ actorPrefix: "TestTabActor",
+
+ get window() {
+ return { wrappedJSObject: this._global };
+ },
+
+ get url() {
+ return this._global.__name;
+ },
+
+ form: function () {
+ let response = { actor: this.actorID, title: this._global.__name };
+
+ // Walk over tab actors added by extensions and add them to a new ActorPool.
+ let actorPool = new ActorPool(this.conn);
+ this._createExtraActors(DebuggerServer.tabActorFactories, actorPool);
+ if (!actorPool.isEmpty()) {
+ this._tabActorPool = actorPool;
+ this.conn.addActorPool(this._tabActorPool);
+ }
+
+ this._appendExtraActors(response);
+
+ return response;
+ },
+
+ onAttach: function (aRequest) {
+ this._attached = true;
+
+ let response = { type: "tabAttached", threadActor: this._threadActor.actorID };
+ this._appendExtraActors(response);
+
+ return response;
+ },
+
+ onDetach: function (aRequest) {
+ if (!this._attached) {
+ return { "error":"wrongState" };
+ }
+ return { type: "detached" };
+ },
+
+ /* Support for DebuggerServer.addTabActor. */
+ _createExtraActors: createExtraActors,
+ _appendExtraActors: appendExtraActors
+};
+
+TestTabActor.prototype.requestTypes = {
+ "attach": TestTabActor.prototype.onAttach,
+ "detach": TestTabActor.prototype.onDetach
+};
+
+exports.register = function (handle) {
+ handle.setRootActor(createRootActor);
+};
+
+exports.unregister = function (handle) {
+ handle.setRootActor(null);
+};
diff --git a/devtools/shared/security/tests/unit/xpcshell.ini b/devtools/shared/security/tests/unit/xpcshell.ini
new file mode 100644
index 000000000..f2b3e7151
--- /dev/null
+++ b/devtools/shared/security/tests/unit/xpcshell.ini
@@ -0,0 +1,12 @@
+[DEFAULT]
+tags = devtools
+head = head_dbg.js
+tail =
+firefox-appdir = browser
+
+support-files=
+ testactors.js
+
+[test_encryption.js]
+[test_oob_cert_auth.js]
+skip-if = (toolkit == 'android' && !debug) # Bug 1141544: Re-enable when buildbot tests are gone