Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

// 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",
// 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",
);
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_setup(async function setup() {
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "4", "4");
await promiseStartupManager();
});
// Try to install a broken add-on
add_task(useAMOStageCert(), 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(useAMOStageCert(), 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(useAMOStageCert(), 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(useAMOStageCert(), async function test_install_valid() {
let file = do_get_file(DATA + ADDONS.signed1);
await test_install_working(file, AddonManager.SIGNEDSTATE_SIGNED);
});
add_task(
useAMOStageCert(),
async function test_install_implicit_id_with_different_root_cert() {
info(
`test install error on fail to verify signature on XPI without ID in manifest`
);
const xpi = do_get_file("data/webext-implicit-id.xpi");
const expectedMessage =
/Cannot find id for addon .+ Preference xpinstall.signatures.dev-root is set/;
const { messages } = await AddonTestUtils.promiseConsoleOutput(async () => {
await test_install_broken(
xpi,
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
);
});
AddonTestUtils.checkMessages(messages, {
expected: [{ message: expectedMessage }],
});
}
);
add_task(
async function test_install_stage_signed_invalid_with_prod_root_cert() {
info(
`test install error on fail to verify signature on XPI with ID in manifest`
);
const xpi = do_get_file(DATA + ADDONS.signed1);
const expectedMessage = /Add-on test@somewhere.com is not correctly signed/;
const { messages } = await AddonTestUtils.promiseConsoleOutput(async () => {
await test_install_broken(
xpi,
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
);
});
AddonTestUtils.checkMessages(messages, {
expected: [{ message: expectedMessage }],
});
}
);
// Try to install an add-on signed with SHA-256
add_task(async function test_install_valid_sha256() {
// 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);
try {
// Prevent install to fail due to privileged.xpi version using
// a version format that hits a manifest warning.
// TODO(Bug 1824240): remove this once privileged.xpi can be resigned with a
// version format that does not hit a manifest warning.
ExtensionTestUtils.failOnSchemaWarnings(false);
await test_install_working(file, AddonManager.SIGNEDSTATE_PRIVILEGED);
} finally {
ExtensionTestUtils.failOnSchemaWarnings(true);
}
});
// Try to update to a broken add-on
add_task(useAMOStageCert(), 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(useAMOStageCert(), 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(useAMOStageCert(), 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(useAMOStageCert(), 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(useAMOStageCert(), 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());