summaryrefslogtreecommitdiffstats
path: root/toolkit/mozapps/extensions/test/xpcshell/test_signed_install.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/mozapps/extensions/test/xpcshell/test_signed_install.js')
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/test_signed_install.js337
1 files changed, 337 insertions, 0 deletions
diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_signed_install.js b/toolkit/mozapps/extensions/test/xpcshell/test_signed_install.js
new file mode 100644
index 0000000000..065463864d
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_signed_install.js
@@ -0,0 +1,337 @@
+// Enable signature checks for these tests
+gUseRealCertChecks = true;
+
+const DATA = "data/signing_checks/";
+const ADDONS = {
+ unsigned: "unsigned.xpi",
+ signed1: "signed1.xpi",
+ signed2: "signed2.xpi",
+ privileged: "privileged.xpi",
+
+ // Bug 1509093
+ // sha256Signed: "signed_bootstrap_sha256_1.xpi",
+};
+
+// The ID in signed1.xpi and signed2.xpi
+const ID = "test@somewhere.com";
+const PR_USEC_PER_MSEC = 1000;
+
+let testserver = createHttpServer({ hosts: ["example.com"] });
+
+Services.prefs.setCharPref(
+ "extensions.update.background.url",
+ "http://example.com/update.json"
+);
+Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);
+
+// Creates an add-on with a broken signature by changing an existing file
+function createBrokenAddonModify(file) {
+ let brokenFile = gTmpD.clone();
+ brokenFile.append("broken.xpi");
+ file.copyTo(brokenFile.parent, brokenFile.leafName);
+
+ var stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+ stream.setData("FOOBAR", -1);
+ var zipW = Cc["@mozilla.org/zipwriter;1"].createInstance(Ci.nsIZipWriter);
+ zipW.open(brokenFile, FileUtils.MODE_RDWR | FileUtils.MODE_APPEND);
+ zipW.removeEntry("test.txt", false);
+ zipW.addEntryStream(
+ "test.txt",
+ new Date() * PR_USEC_PER_MSEC,
+ Ci.nsIZipWriter.COMPRESSION_NONE,
+ stream,
+ false
+ );
+ zipW.close();
+
+ return brokenFile;
+}
+
+// Creates an add-on with a broken signature by adding a new file
+function createBrokenAddonAdd(file) {
+ let brokenFile = gTmpD.clone();
+ brokenFile.append("broken.xpi");
+ file.copyTo(brokenFile.parent, brokenFile.leafName);
+
+ var stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+ stream.setData("FOOBAR", -1);
+ var zipW = Cc["@mozilla.org/zipwriter;1"].createInstance(Ci.nsIZipWriter);
+ zipW.open(brokenFile, FileUtils.MODE_RDWR | FileUtils.MODE_APPEND);
+ zipW.addEntryStream(
+ "test2.txt",
+ new Date() * PR_USEC_PER_MSEC,
+ Ci.nsIZipWriter.COMPRESSION_NONE,
+ stream,
+ false
+ );
+ zipW.close();
+
+ return brokenFile;
+}
+
+// Creates an add-on with a broken signature by removing an existing file
+function createBrokenAddonRemove(file) {
+ let brokenFile = gTmpD.clone();
+ brokenFile.append("broken.xpi");
+ file.copyTo(brokenFile.parent, brokenFile.leafName);
+
+ var stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+ stream.setData("FOOBAR", -1);
+ var zipW = Cc["@mozilla.org/zipwriter;1"].createInstance(Ci.nsIZipWriter);
+ zipW.open(brokenFile, FileUtils.MODE_RDWR | FileUtils.MODE_APPEND);
+ zipW.removeEntry("test.txt", false);
+ zipW.close();
+
+ return brokenFile;
+}
+
+function serveUpdate(filename) {
+ const RESPONSE = {
+ addons: {
+ [ID]: {
+ updates: [
+ {
+ version: "2.0",
+ update_link: `http://example.com/${filename}`,
+ applications: {
+ gecko: {
+ strict_min_version: "4",
+ advisory_max_version: "6",
+ },
+ },
+ },
+ ],
+ },
+ },
+ };
+ AddonTestUtils.registerJSON(testserver, "/update.json", RESPONSE);
+}
+
+async function test_install_broken(
+ file,
+ expectedError,
+ expectNullAddon = true
+) {
+ let install = await AddonManager.getInstallForFile(file);
+ await Assert.rejects(
+ install.install(),
+ /Install failed/,
+ "Install of an improperly signed extension should throw"
+ );
+
+ Assert.equal(install.state, AddonManager.STATE_DOWNLOAD_FAILED);
+ Assert.equal(install.error, expectedError);
+
+ if (expectNullAddon) {
+ Assert.equal(install.addon, null);
+ }
+}
+
+async function test_install_working(file, expectedSignedState) {
+ let install = await AddonManager.getInstallForFile(file);
+ await install.install();
+
+ Assert.equal(install.state, AddonManager.STATE_INSTALLED);
+ Assert.notEqual(install.addon, null);
+ Assert.equal(install.addon.signedState, expectedSignedState);
+
+ await install.addon.uninstall();
+}
+
+async function test_update_broken(file1, file2, expectedError) {
+ // First install the older version
+ await Promise.all([
+ promiseInstallFile(file1),
+ promiseWebExtensionStartup(ID),
+ ]);
+
+ testserver.registerFile("/" + file2.leafName, file2);
+ serveUpdate(file2.leafName);
+
+ let addon = await promiseAddonByID(ID);
+ let update = await promiseFindAddonUpdates(addon);
+ let install = update.updateAvailable;
+ await Assert.rejects(
+ install.install(),
+ /Install failed/,
+ "Update to an improperly signed extension should throw"
+ );
+
+ Assert.equal(install.state, AddonManager.STATE_DOWNLOAD_FAILED);
+ Assert.equal(install.error, expectedError);
+ Assert.equal(install.addon, null);
+
+ testserver.registerFile("/" + file2.leafName, null);
+ testserver.registerPathHandler("/update.json", null);
+
+ await addon.uninstall();
+}
+
+async function test_update_working(file1, file2, expectedSignedState) {
+ // First install the older version
+ await promiseInstallFile(file1);
+
+ testserver.registerFile("/" + file2.leafName, file2);
+ serveUpdate(file2.leafName);
+
+ let addon = await promiseAddonByID(ID);
+ let update = await promiseFindAddonUpdates(addon);
+ let install = update.updateAvailable;
+ await Promise.all([install.install(), promiseWebExtensionStartup(ID)]);
+
+ Assert.equal(install.state, AddonManager.STATE_INSTALLED);
+ Assert.notEqual(install.addon, null);
+ Assert.equal(install.addon.signedState, expectedSignedState);
+
+ testserver.registerFile("/" + file2.leafName, null);
+ testserver.registerPathHandler("/update.json", null);
+
+ await install.addon.uninstall();
+}
+
+add_task(async function setup() {
+ createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "4", "4");
+ await promiseStartupManager();
+});
+
+// Try to install a broken add-on
+add_task(async function test_install_invalid_modified() {
+ let file = createBrokenAddonModify(do_get_file(DATA + ADDONS.signed1));
+ await test_install_broken(file, AddonManager.ERROR_CORRUPT_FILE);
+ file.remove(true);
+});
+
+add_task(async function test_install_invalid_added() {
+ let file = createBrokenAddonAdd(do_get_file(DATA + ADDONS.signed1));
+ await test_install_broken(file, AddonManager.ERROR_CORRUPT_FILE);
+ file.remove(true);
+});
+
+add_task(async function test_install_invalid_removed() {
+ let file = createBrokenAddonRemove(do_get_file(DATA + ADDONS.signed1));
+ await test_install_broken(file, AddonManager.ERROR_CORRUPT_FILE);
+ file.remove(true);
+});
+
+// Try to install an unsigned add-on
+add_task(async function test_install_invalid_unsigned() {
+ let file = do_get_file(DATA + ADDONS.unsigned);
+ await test_install_broken(file, AddonManager.ERROR_SIGNEDSTATE_REQUIRED);
+});
+
+// Try to install a signed add-on
+add_task(async function test_install_valid() {
+ let file = do_get_file(DATA + ADDONS.signed1);
+ await test_install_working(file, AddonManager.SIGNEDSTATE_SIGNED);
+});
+
+add_task(
+ {
+ pref_set: [["xpinstall.signatures.dev-root", true]],
+ // `xpinstall.signatures.dev-root` is not taken into account on release
+ // builds because `MOZ_REQUIRE_SIGNING` is set to `true`.
+ skip_if: () => AppConstants.MOZ_REQUIRE_SIGNING,
+ },
+ async function test_install_valid_file_with_different_root_cert() {
+ const TEST_CASES = [
+ {
+ title: "XPI without ID in manifest",
+ xpi: "data/webext-implicit-id.xpi",
+ expectedMessage:
+ /Cannot find id for addon .+ Preference xpinstall.signatures.dev-root is set/,
+ },
+ {
+ title: "XPI with ID in manifest",
+ xpi: DATA + ADDONS.signed1,
+ expectedMessage: /Add-on test@somewhere.com is not correctly signed/,
+ },
+ ];
+
+ for (const { title, xpi, expectedMessage } of TEST_CASES) {
+ info(`test_install_valid_file_with_different_root_cert: ${title}`);
+
+ const file = do_get_file(xpi);
+
+ const awaitConsole = new Promise(resolve => {
+ Services.console.registerListener(function listener(message) {
+ if (expectedMessage.test(message.message)) {
+ Services.console.unregisterListener(listener);
+ resolve();
+ }
+ });
+ });
+
+ await test_install_broken(
+ file,
+ AddonManager.ERROR_CORRUPT_FILE,
+ // We don't expect the `addon` property on the `install` object to be
+ // `null` because that seems to happen later (when the signature is
+ // checked).
+ false
+ );
+
+ await awaitConsole;
+ }
+ }
+);
+
+// Try to install an add-on signed with SHA-256
+add_task(async function test_install_valid_sha256() {
+ // Bug 1509093
+ // let file = do_get_file(DATA + ADDONS.sha256Signed);
+ // await test_install_working(file, AddonManager.SIGNEDSTATE_SIGNED);
+});
+
+// Try to install an add-on with the "Mozilla Extensions" OU
+add_task(async function test_install_valid_privileged() {
+ let file = do_get_file(DATA + ADDONS.privileged);
+ await test_install_working(file, AddonManager.SIGNEDSTATE_PRIVILEGED);
+});
+
+// Try to update to a broken add-on
+add_task(async function test_update_invalid_modified() {
+ let file1 = do_get_file(DATA + ADDONS.signed1);
+ let file2 = createBrokenAddonModify(do_get_file(DATA + ADDONS.signed2));
+ await test_update_broken(file1, file2, AddonManager.ERROR_CORRUPT_FILE);
+ file2.remove(true);
+});
+
+add_task(async function test_update_invalid_added() {
+ let file1 = do_get_file(DATA + ADDONS.signed1);
+ let file2 = createBrokenAddonAdd(do_get_file(DATA + ADDONS.signed2));
+ await test_update_broken(file1, file2, AddonManager.ERROR_CORRUPT_FILE);
+ file2.remove(true);
+});
+
+add_task(async function test_update_invalid_removed() {
+ let file1 = do_get_file(DATA + ADDONS.signed1);
+ let file2 = createBrokenAddonRemove(do_get_file(DATA + ADDONS.signed2));
+ await test_update_broken(file1, file2, AddonManager.ERROR_CORRUPT_FILE);
+ file2.remove(true);
+});
+
+// Try to update to an unsigned add-on
+add_task(async function test_update_invalid_unsigned() {
+ let file1 = do_get_file(DATA + ADDONS.signed1);
+ let file2 = do_get_file(DATA + ADDONS.unsigned);
+ await test_update_broken(
+ file1,
+ file2,
+ AddonManager.ERROR_SIGNEDSTATE_REQUIRED
+ );
+});
+
+// Try to update to a signed add-on
+add_task(async function test_update_valid() {
+ let file1 = do_get_file(DATA + ADDONS.signed1);
+ let file2 = do_get_file(DATA + ADDONS.signed2);
+ await test_update_working(file1, file2, AddonManager.SIGNEDSTATE_SIGNED);
+});
+
+add_task(() => promiseShutdownManager());