350 lines
11 KiB
JavaScript
350 lines
11 KiB
JavaScript
// 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.setByteStringData("FOOBAR");
|
|
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.setByteStringData("FOOBAR");
|
|
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.setByteStringData("FOOBAR");
|
|
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() {
|
|
// 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);
|
|
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());
|