summaryrefslogtreecommitdiffstats
path: root/toolkit/mozapps/extensions/test/xpcshell/test_signed_verify.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/mozapps/extensions/test/xpcshell/test_signed_verify.js')
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/test_signed_verify.js438
1 files changed, 434 insertions, 4 deletions
diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_signed_verify.js b/toolkit/mozapps/extensions/test/xpcshell/test_signed_verify.js
index c17cb941cb..e801485c73 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/test_signed_verify.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_signed_verify.js
@@ -25,9 +25,439 @@ function verifySignatures() {
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "4", "4");
-add_task(async function test_no_change() {
+add_setup(async () => {
await promiseStartupManager();
+});
+
+add_task(function test_hasStrongSignature_helper() {
+ const { hasStrongSignature } = ChromeUtils.importESModule(
+ "resource://gre/modules/addons/crypto-utils.sys.mjs"
+ );
+ const { PKCS7_WITH_SHA1, PKCS7_WITH_SHA256, COSE_WITH_SHA256 } =
+ Ci.nsIAppSignatureInfo;
+ const testCases = [
+ [false, "SHA1 only", [PKCS7_WITH_SHA1]],
+ [true, "SHA256 only", [PKCS7_WITH_SHA256]],
+ [true, "COSE only", [COSE_WITH_SHA256]],
+ [true, "SHA1 and SHA256", [PKCS7_WITH_SHA1, PKCS7_WITH_SHA256]],
+ [true, "SHA1 and COSE", [PKCS7_WITH_SHA1, COSE_WITH_SHA256]],
+ [true, "SHA256 and COSE", [PKCS7_WITH_SHA256, COSE_WITH_SHA256]],
+ ];
+ for (const [expect, msg, signedTypes] of testCases) {
+ Assert.equal(hasStrongSignature({ signedTypes }), expect, msg);
+ }
+});
+
+add_task(async function test_addon_signedTypes() {
+ // This test is allowing weak signatures to run assertions on the AddonWrapper.signedTypes
+ // property also for extensions only including SHA1 signatures.
+ const resetWeakSignaturePref =
+ AddonTestUtils.setWeakSignatureInstallAllowed(true);
+
+ const { PKCS7_WITH_SHA1, COSE_WITH_SHA256 } = Ci.nsIAppSignatureInfo;
+
+ const { addon: addonSignedCOSE } = await promiseInstallFile(
+ do_get_file("amosigned-mv3-cose.xpi")
+ );
+ const { addon: addonSignedSHA1 } = await promiseInstallFile(
+ do_get_file("amosigned-sha1only.xpi")
+ );
+
+ Assert.deepEqual(
+ addonSignedCOSE.signedTypes.sort(),
+ [COSE_WITH_SHA256, PKCS7_WITH_SHA1].sort(),
+ `Expect ${addonSignedCOSE.id} to be signed with both COSE and SHA1`
+ );
+
+ Assert.deepEqual(
+ addonSignedSHA1.signedTypes,
+ [PKCS7_WITH_SHA1],
+ `Expect ${addonSignedSHA1.id} to be signed with SHA1 only`
+ );
+
+ await addonSignedSHA1.uninstall();
+ await addonSignedCOSE.uninstall();
+
+ resetWeakSignaturePref();
+});
+
+add_task(
+ async function test_install_error_on_new_install_with_weak_signature() {
+ // Ensure restrictions on weak signatures are enabled (this should be removed when
+ // the new behavior is riding the train).
+ const resetWeakSignaturePref =
+ AddonTestUtils.setWeakSignatureInstallAllowed(false);
+
+ const { messages } = await AddonTestUtils.promiseConsoleOutput(async () => {
+ let install = await AddonManager.getInstallForFile(
+ do_get_file("amosigned-sha1only.xpi")
+ );
+
+ await Assert.equal(
+ install.state,
+ AddonManager.STATE_DOWNLOAD_FAILED,
+ "Expect install state to be STATE_DOWNLOAD_FAILED"
+ );
+
+ await Assert.rejects(
+ install.install(),
+ /Install failed: onDownloadFailed/,
+ "Expected install to fail"
+ );
+ });
+
+ resetWeakSignaturePref();
+
+ // Checking the message expected to be logged in the Browser Console.
+ AddonTestUtils.checkMessages(messages, {
+ expected: [
+ {
+ message:
+ /Invalid XPI: install rejected due to the package not including a strong cryptographic signature/,
+ },
+ ],
+ });
+ }
+);
+
+/**
+ * Test helper used to simulate an update from a given pre-installed add-on xpi to a new xpi file for the same
+ * add-on and assert the expected result and logged messages.
+ *
+ * @param {object} params
+ * @param {string} params.currentAddonXPI
+ * The path to the add-on xpi to be pre-installed and then updated to `newAddonXPI`.
+ * @param {string} params.newAddonXPI
+ * The path to the add-on xpi to be installed as an update over `currentAddonXPI`.
+ * @param {string} params.newAddonVersion
+ * The add-on version expected for `newAddonXPI`.
+ * @param {boolean} params.expectedInstallOK
+ * Set to true for an update scenario that is expected to be successful.
+ * @param {Array<string|RegExp>} params.expectedMessages
+ * Array of strings or RegExp for console messages expected to be logged.
+ * @param {Array<string|RegExp>} params.forbiddenMessages
+ * Array of strings or RegExp for console messages expected to NOT be logged.
+ */
+async function testWeakSignatureXPIUpdate({
+ currentAddonXPI,
+ newAddonXPI,
+ newAddonVersion,
+ expectedInstallOK,
+ expectedMessages,
+ forbiddenMessages,
+}) {
+ // Temporarily allow weak signature to install the xpi as a new install.
+ let resetWeakSignaturePref =
+ AddonTestUtils.setWeakSignatureInstallAllowed(true);
+
+ const { addon: addonFirstInstall } = await promiseInstallFile(
+ currentAddonXPI
+ );
+ const addonId = addonFirstInstall.id;
+ const initialAddonVersion = addonFirstInstall.version;
+
+ resetWeakSignaturePref();
+
+ // Make sure the install over is executed while weak signature is not allowed
+ // for new installs to confirm that installing over is allowed.
+ resetWeakSignaturePref = AddonTestUtils.setWeakSignatureInstallAllowed(false);
+
+ info("Install over the existing installed addon");
+ let addonInstalledOver;
+ const { messages } = await AddonTestUtils.promiseConsoleOutput(async () => {
+ const fileURL = Services.io.newFileURI(newAddonXPI).spec;
+ let install = await AddonManager.getInstallForURL(fileURL, {
+ existingAddon: addonFirstInstall,
+ version: newAddonVersion,
+ });
+
+ addonInstalledOver = await install.install().catch(err => {
+ if (expectedInstallOK) {
+ ok(false, `Unexpected error hit on installing update XPI: ${err}`);
+ } else {
+ ok(true, `Install failed as expected: ${err}`);
+ }
+ });
+ });
+
+ resetWeakSignaturePref();
+
+ if (expectedInstallOK) {
+ Assert.equal(
+ addonInstalledOver.id,
+ addonFirstInstall.id,
+ "Expect addon id to be the same"
+ );
+ Assert.equal(
+ addonInstalledOver.version,
+ newAddonVersion,
+ "Got expected addon version after update xpi install completed"
+ );
+ await addonInstalledOver.uninstall();
+ } else {
+ Assert.equal(
+ addonInstalledOver?.version,
+ undefined,
+ "Expect update addon xpi not installed successfully"
+ );
+ Assert.equal(
+ (await AddonManager.getAddonByID(addonId)).version,
+ initialAddonVersion,
+ "Expect the addon version to match the initial XPI version"
+ );
+ await addonFirstInstall.uninstall();
+ }
+
+ Assert.equal(
+ await AddonManager.getAddonByID(addonId),
+ undefined,
+ "Expect the test addon to be fully uninstalled"
+ );
+
+ // Checking the message logged in the Browser Console.
+ AddonTestUtils.checkMessages(messages, {
+ expected: expectedMessages,
+ forbidden: forbiddenMessages,
+ });
+}
+
+add_task(async function test_weak_install_over_weak_existing() {
+ const addonId = "amosigned-xpi@tests.mozilla.org";
+ await testWeakSignatureXPIUpdate({
+ currentAddonXPI: do_get_file("amosigned-sha1only.xpi"),
+ newAddonXPI: do_get_file("amosigned-sha1only.xpi"),
+ newAddonVersion: "2.1",
+ expectedInstallOK: true,
+ expectedMessages: [
+ {
+ message: new RegExp(
+ `Allow weak signature install over existing "${addonId}" XPI`
+ ),
+ },
+ ],
+ });
+});
+
+add_task(async function test_update_weak_to_strong_signature() {
+ const addonId = "amosigned-xpi@tests.mozilla.org";
+ await testWeakSignatureXPIUpdate({
+ currentAddonXPI: do_get_file("amosigned-sha1only.xpi"),
+ newAddonXPI: do_get_file("amosigned.xpi"),
+ newAddonVersion: "2.2",
+ expectedInstallOK: true,
+ forbiddenMessages: [
+ {
+ message: new RegExp(
+ `Allow weak signature install over existing "${addonId}" XPI`
+ ),
+ },
+ ],
+ });
+});
+
+add_task(async function test_update_strong_to_weak_signature() {
+ const addonId = "amosigned-xpi@tests.mozilla.org";
+ await testWeakSignatureXPIUpdate({
+ currentAddonXPI: do_get_file("amosigned.xpi"),
+ newAddonXPI: do_get_file("amosigned-sha1only.xpi"),
+ newAddonVersion: "2.1",
+ expectedInstallOK: false,
+ expectedMessages: [
+ {
+ message: new RegExp(
+ "Invalid XPI: install rejected due to the package not including a strong cryptographic signature"
+ ),
+ },
+ ],
+ forbiddenMessages: [
+ {
+ message: new RegExp(
+ `Allow weak signature install over existing "${addonId}" XPI`
+ ),
+ },
+ ],
+ });
+});
+
+add_task(async function test_signedTypes_stored_in_addonDB() {
+ const { addon: addonAfterInstalled } = await promiseInstallFile(
+ do_get_file("amosigned-mv3-cose.xpi")
+ );
+ const addonId = addonAfterInstalled.id;
+
+ const { PKCS7_WITH_SHA1, COSE_WITH_SHA256 } = Ci.nsIAppSignatureInfo;
+ const expectedSignedTypes = [COSE_WITH_SHA256, PKCS7_WITH_SHA1].sort();
+
+ Assert.deepEqual(
+ addonAfterInstalled.signedTypes.sort(),
+ expectedSignedTypes,
+ `Got expected ${addonId} signedTyped after install`
+ );
+
+ await promiseRestartManager();
+
+ const addonAfterAOMRestart = await AddonManager.getAddonByID(addonId);
+
+ Assert.deepEqual(
+ addonAfterAOMRestart.signedTypes.sort(),
+ expectedSignedTypes,
+ `Got expected ${addonId} signedTyped after AOM restart`
+ );
+
+ const removeSignedStateFromAddonDB = async () => {
+ const addon_db_file = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ addon_db_file.append("extensions.json");
+ const addon_db_data = await IOUtils.readJSON(addon_db_file.path);
+
+ const addon_db_data_tampered = {
+ ...addon_db_data,
+ addons: addon_db_data.addons.map(addonData => {
+ // Tamper the data of the test extension to mock the
+ // scenario.
+ delete addonData.signedTypes;
+ return addonData;
+ }),
+ };
+ await IOUtils.writeJSON(addon_db_file.path, addon_db_data_tampered);
+ };
+
+ // Shutdown the AddonManager and tamper the AddonDB to confirm XPIProvider.checkForChanges
+ // calls originated internally when opening a profile created from a previous Firefox version
+ // is going to populate the new signedTypes property.
+ info(
+ "Check that XPIProvider.checkForChanges(true) will recompute missing signedTypes properties"
+ );
+ await promiseShutdownManager();
+ await removeSignedStateFromAddonDB();
+ await promiseStartupManager();
+
+ // Expect the signedTypes property to be undefined because of the
+ // AddonDB data being tampered earlier in this test.
+ const addonAfterAppUpgrade = await AddonManager.getAddonByID(addonId);
+ Assert.deepEqual(
+ addonAfterAppUpgrade.signedTypes,
+ undefined,
+ `Got empty ${addonId} signedTyped set to undefied after AddonDB data tampered`
+ );
+
+ // Mock call to XPIDatabase.checkForChanges expected to be hit when the application
+ // is updated.
+ AddonTestUtils.getXPIExports().XPIProvider.checkForChanges(
+ /* aAppChanged */ true
+ );
+
+ Assert.deepEqual(
+ addonAfterAppUpgrade.signedTypes?.sort(),
+ expectedSignedTypes.sort(),
+ `Got expected ${addonId} signedTyped after XPIProvider.checkForChanges recomputed it`
+ );
+
+ // Shutdown the AddonManager and tamper the AddonDB to confirm XPIDatabase.updateCompatibility
+ // would populate signedTypes if missing.
+ info(
+ "Check that XPIDatabase.updateCompatibility will recompute missing signedTypes properties"
+ );
+ await promiseShutdownManager();
+ await removeSignedStateFromAddonDB();
+ await promiseStartupManager();
+
+ // Expect the signedTypes property to be undefined because of the
+ // AddonDB data being tampered earlier in this test.
+ const addonAfterUpdateCompatibility = await AddonManager.getAddonByID(
+ addonId
+ );
+ Assert.deepEqual(
+ addonAfterUpdateCompatibility.signedTypes,
+ undefined,
+ `Got empty ${addonId} signedTyped set to undefied after AddonDB data tampered`
+ );
+
+ // Mock call to XPIDatabase.updateCompatibility expected to be originated from
+ // XPIDatabaseReconcile.processFileChanges and confirm that signedTypes has been
+ // recomputed as expected.
+ AddonTestUtils.getXPIExports().XPIDatabaseReconcile.processFileChanges(
+ {},
+ true
+ );
+ Assert.deepEqual(
+ addonAfterUpdateCompatibility.signedTypes?.sort(),
+ expectedSignedTypes.sort(),
+ `Got expected ${addonId} signedTyped after XPIDatabase.updateCompatibility recomputed it`
+ );
+
+ // Restart the AddonManager and confirm that XPIDatabase.verifySignatures would not recompute
+ // the content of the signedTypes array if all values are still the same.
+ info(
+ "Check that XPIDatabase.updateCompatibility will recompute missing signedTypes properties"
+ );
+ await promiseRestartManager();
+
+ let listener = {
+ onPropertyChanged(_addon, properties) {
+ Assert.deepEqual(
+ properties,
+ [],
+ `No properties should have been changed for ${_addon.id}`
+ );
+ Assert.ok(
+ false,
+ `onPropertyChanged should have not been called for ${_addon.id}`
+ );
+ },
+ };
+
+ AddonManager.addAddonListener(listener);
+ await verifySignatures();
+ AddonManager.removeAddonListener(listener);
+
+ // Shutdown the AddonManager and tamper the AddonDB to set signedTypes to undefined
+ // then confirm that XPIDatabase.verifySignatures does not hit an exception due to
+ // signedTypes assumed to always be set to an array.
+ info(
+ "Check that XPIDatabase.verifySignatures does not fail when signedTypes is undefined"
+ );
+ await promiseShutdownManager();
+ await removeSignedStateFromAddonDB();
+ await promiseStartupManager();
+
+ // Expect the signedTypes property to be undefined because of the
+ // AddonDB data being tampered earlier in this test.
+ const addonUndefinedSignedTypes = await AddonManager.getAddonByID(addonId);
+ Assert.deepEqual(
+ addonUndefinedSignedTypes.signedTypes,
+ undefined,
+ `Got empty ${addonId} signedTyped set to undefied after AddonDB data tampered`
+ );
+ await verifySignatures();
+ Assert.deepEqual(
+ addonUndefinedSignedTypes.signedTypes?.sort(),
+ expectedSignedTypes.sort(),
+ `Got expected ${addonId} signedTyped after XPIDatabase.verifySignatures recomputed it`
+ );
+
+ await addonUndefinedSignedTypes.uninstall();
+});
+
+add_task(
+ {
+ pref_set: [["xpinstall.signatures.required", false]],
+ // Skip this test on builds where disabling xpi signature checks is not supported.
+ skip_if: () => AppConstants.MOZ_REQUIRE_SIGNING,
+ },
+ async function test_weak_signature_not_restricted_on_disabled_signature_checks() {
+ // Ensure the restriction on installing xpi using only weak signatures is enabled.
+ let resetWeakSignaturePref =
+ AddonTestUtils.setWeakSignatureInstallAllowed(false);
+ const { addon } = await promiseInstallFile(
+ do_get_file("amosigned-sha1only.xpi")
+ );
+ Assert.notEqual(addon, null, "Expect addon to be installed");
+ resetWeakSignaturePref();
+ await addon.uninstall();
+ }
+);
+add_task(useAMOStageCert(), async function test_no_change() {
// Install the first add-on
await promiseInstallFile(do_get_file(`${DATA}/signed1.xpi`));
@@ -43,7 +473,7 @@ add_task(async function test_no_change() {
await manuallyInstall(do_get_file(`${DATA}/signed2.xpi`), profileDir, ID);
let listener = {
- onPropetyChanged(_addon, properties) {
+ onPropertyChanged(_addon) {
Assert.ok(false, `Got unexpected onPropertyChanged for ${_addon.id}`);
},
};
@@ -63,7 +493,7 @@ add_task(async function test_no_change() {
AddonManager.removeAddonListener(listener);
});
-add_task(async function test_diable() {
+add_task(useAMOStageCert(), async function test_disable() {
// Install the first add-on
await promiseInstallFile(do_get_file(`${DATA}/signed1.xpi`));
@@ -96,7 +526,7 @@ add_task(async function test_diable() {
Assert.deepEqual(
changedProperties,
- ["signedState", "appDisabled"],
+ ["signedState", "signedTypes", "appDisabled"],
"Got onPropertyChanged events for signedState and appDisabled"
);