diff options
Diffstat (limited to 'devtools/shared/security/tests')
8 files changed, 696 insertions, 0 deletions
diff --git a/devtools/shared/security/tests/chrome/chrome.ini b/devtools/shared/security/tests/chrome/chrome.ini new file mode 100644 index 000000000..581251103 --- /dev/null +++ b/devtools/shared/security/tests/chrome/chrome.ini @@ -0,0 +1,4 @@ +[DEFAULT] +tags = devtools + +[test_websocket-transport.html] diff --git a/devtools/shared/security/tests/chrome/test_websocket-transport.html b/devtools/shared/security/tests/chrome/test_websocket-transport.html new file mode 100644 index 000000000..542206ecf --- /dev/null +++ b/devtools/shared/security/tests/chrome/test_websocket-transport.html @@ -0,0 +1,76 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test the WebSocket debugger transport</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +</head> +<body> +<script> +window.onload = function() { + const {require} = Components.utils.import("resource://devtools/shared/Loader.jsm", {}); + const Services = require("Services"); + const {DebuggerClient} = require("devtools/shared/client/main"); + const {DebuggerServer} = require("devtools/server/main"); + + Services.prefs.setBoolPref("devtools.debugger.remote-enabled", true); + Services.prefs.setBoolPref("devtools.debugger.prompt-connection", false); + + SimpleTest.registerCleanupFunction(() => { + Services.prefs.clearUserPref("devtools.debugger.remote-enabled"); + Services.prefs.clearUserPref("devtools.debugger.prompt-connection"); + }); + + add_task(function* () { + if (!DebuggerServer.initialized) { + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + } + + is(DebuggerServer.listeningSockets, 0, "0 listening sockets"); + + let listener = DebuggerServer.createListener(); + ok(listener, "Socket listener created"); + listener.portOrPath = -1; + listener.webSocket = true; + yield listener.open(); + is(DebuggerServer.listeningSockets, 1, "1 listening socket"); + + let transport = yield DebuggerClient.socketConnect({ + host: "127.0.0.1", + port: listener.port, + webSocket: true + }); + ok(transport, "Client transport created"); + + let client = new DebuggerClient(transport); + let onUnexpectedClose = () => { + do_throw("Closed unexpectedly"); + }; + client.addListener("closed", onUnexpectedClose); + + yield client.connect(); + yield client.listTabs(); + + // Send a message the server that will echo back + let message = "message"; + let reply = yield client.request({ + to: "root", + type: "echo", + message + }); + is(reply.message, message, "Echo message matches"); + + client.removeListener("closed", onUnexpectedClose); + transport.close(); + listener.close(); + is(DebuggerServer.listeningSockets, 0, "0 listening sockets"); + + DebuggerServer.destroy(); + }); +} +</script> +</body> +</html> 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 |