Bug 1244357: Use a shim around the certificate DB to allow the add-ons manager to think that add-ons are signed when they aren't. r=rhelmer

Because the add-ons manager hasn't startup up yet we can replace the certificate
database in xpcshell tests with one that claims add-ons are signed by valid
certificates even when they aren't. This allows us to run tests even in builds
where signing cannot be disabled during for the normal application.

This adds an override for all tests except those that are explicitely testing
signing.
This commit is contained in:
Dave Townsend 2016-01-29 16:41:18 -08:00
parent e96bcf3add
commit a752390265
17 changed files with 239 additions and 39 deletions

View File

@ -92,6 +92,7 @@ def build_dict(config, env=os.environ):
d['tests_enabled'] = substs.get('ENABLE_TESTS') == "1"
d['bin_suffix'] = substs.get('BIN_SUFFIX', '')
d['addon_signing'] = substs.get('MOZ_ADDON_SIGNING') == '1'
d['require_signing'] = substs.get('MOZ_REQUIRE_SIGNING') == '1'
d['official'] = bool(substs.get('MOZILLA_OFFICIAL'))
def guess_platform():

View File

@ -1015,7 +1015,7 @@ add_task(function* test_addonsAndPlugins() {
hasBinaryComponents: false,
installDay: ADDON_INSTALL_DATE,
updateDay: ADDON_INSTALL_DATE,
signedState: mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_MISSING : AddonManager.SIGNEDSTATE_NOT_REQUIRED,
signedState: mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_SIGNED : AddonManager.SIGNEDSTATE_NOT_REQUIRED,
};
const EXPECTED_PLUGIN_DATA = {

View File

@ -1641,11 +1641,18 @@ function verifyZipSignedState(aFile, aAddon) {
root = Ci.nsIX509CertDB.AddonsStageRoot;
return new Promise(resolve => {
gCertDB.openSignedAppFileAsync(root, aFile, (aRv, aZipReader, aCert) => {
if (aZipReader)
aZipReader.close();
resolve(getSignedStatus(aRv, aCert, aAddon.id));
});
let callback = {
openSignedAppFileFinished: function(aRv, aZipReader, aCert) {
if (aZipReader)
aZipReader.close();
resolve(getSignedStatus(aRv, aCert, aAddon.id));
}
};
// This allows the certificate DB to get the raw JS callback object so the
// test code can pass through objects that XPConnect would reject.
callback.wrappedJSObject = callback;
gCertDB.openSignedAppFileAsync(root, aFile, callback);
});
}
@ -1668,9 +1675,16 @@ function verifyDirSignedState(aDir, aAddon) {
root = Ci.nsIX509CertDB.AddonsStageRoot;
return new Promise(resolve => {
gCertDB.verifySignedDirectoryAsync(root, aDir, (aRv, aCert) => {
resolve(getSignedStatus(aRv, aCert, aAddon.id));
});
let callback = {
verifySignedDirectoryFinished: function(aRv, aCert) {
resolve(getSignedStatus(aRv, aCert, aAddon.id));
}
};
// This allows the certificate DB to get the raw JS callback object so the
// test code can pass through objects that XPConnect would reject.
callback.wrappedJSObject = callback;
gCertDB.verifySignedDirectoryAsync(root, aDir, callback);
});
}

View File

@ -7,6 +7,8 @@ var AM_Ci = Components.interfaces;
const XULAPPINFO_CONTRACTID = "@mozilla.org/xre/app-info;1";
const XULAPPINFO_CID = Components.ID("{c763b610-9d49-455a-bbd2-ede71682a1ac}");
const CERTDB_CONTRACTID = "@mozilla.org/security/x509certdb;1";
const CERTDB_CID = Components.ID("{fb0bbc5c-452e-4783-b32c-80124693d871}");
const PREF_EM_CHECK_UPDATE_SECURITY = "extensions.checkUpdateSecurity";
const PREF_EM_STRICT_COMPATIBILITY = "extensions.strictCompatibility";
@ -296,6 +298,179 @@ function createAppInfo(id, name, version, platformVersion) {
XULAPPINFO_CONTRACTID, XULAppInfoFactory);
}
function getManifestURIForBundle(file) {
if (file.isDirectory()) {
file.append("install.rdf");
if (file.exists()) {
return NetUtil.newURI(file);
}
file.leafName = "manifest.json";
if (file.exists()) {
return NetUtil.newURI(file);
}
throw new Error("No manifest file present");
}
let zip = AM_Cc["@mozilla.org/libjar/zip-reader;1"].
createInstance(AM_Ci.nsIZipReader);
zip.open(file);
try {
let uri = NetUtil.newURI(file);
if (zip.hasEntry("install.rdf")) {
return NetUtil.newURI("jar:" + uri.spec + "!/" + "install.rdf");
}
if (zip.hasEntry("manifest.json")) {
return NetUtil.newURI("jar:" + uri.spec + "!/" + "manifest.json");
}
throw new Error("No manifest file present");
}
finally {
zip.close();
}
}
let getIDForManifest = Task.async(function*(manifestURI) {
// Load it
let inputStream = yield new Promise((resolve, reject) => {
NetUtil.asyncFetch({
uri: manifestURI,
loadUsingSystemPrincipal: true,
}, (inputStream, status) => {
if (status != Components.results.NS_OK)
reject(status);
resolve(inputStream);
});
});
// Get the data as a string
let data = NetUtil.readInputStreamToString(inputStream, inputStream.available());
if (manifestURI.spec.endsWith(".rdf")) {
let rdfParser = AM_Cc["@mozilla.org/rdf/xml-parser;1"].
createInstance(AM_Ci.nsIRDFXMLParser)
let ds = AM_Cc["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"].
createInstance(AM_Ci.nsIRDFDataSource);
rdfParser.parseString(ds, manifestURI, data);
let rdfService = AM_Cc["@mozilla.org/rdf/rdf-service;1"].
getService(AM_Ci.nsIRDFService);
let rdfID = ds.GetTarget(rdfService.GetResource("urn:mozilla:install-manifest"),
rdfService.GetResource("http://www.mozilla.org/2004/em-rdf#id"),
true);
return rdfID.QueryInterface(AM_Ci.nsIRDFLiteral).Value;
}
else {
let manifest = JSON.parse(data);
return manifest.applications.gecko.id;
}
});
let gUseRealCertChecks = false;
function overrideCertDB(handler) {
// Unregister the real database. This only works because the add-ons manager
// hasn't started up and grabbed the certificate database yet.
let registrar = Components.manager.QueryInterface(AM_Ci.nsIComponentRegistrar);
let factory = registrar.getClassObject(CERTDB_CID, AM_Ci.nsIFactory);
registrar.unregisterFactory(CERTDB_CID, factory);
// Get the real DB
let realCertDB = factory.createInstance(null, AM_Ci.nsIX509CertDB);
let verifyCert = Task.async(function*(caller, file, result, cert, callback) {
// If this isn't a callback we can get directly to through JS then just
// pass on the results
if (!callback.wrappedJSObject) {
caller(callback, result, cert);
return;
}
// Bypassing XPConnect allows us to create a fake x509 certificate from
// JS
callback = callback.wrappedJSObject;
if (gUseRealCertChecks || result != Components.results.NS_ERROR_SIGNED_JAR_NOT_SIGNED) {
// If the real DB found a useful result of some kind then pass it on.
caller(callback, result, cert);
return;
}
try {
let manifestURI = getManifestURIForBundle(file);
let id = yield getIDForManifest(manifestURI);
// Make sure to close the open zip file or it will be locked.
if (file.isFile()) {
Services.obs.notifyObservers(file, "flush-cache-entry", "cert-override");
}
let fakeCert = {
commonName: id
}
caller(callback, Components.results.NS_OK, fakeCert);
}
catch (e) {
// If there is any error then just pass along the original results
caller(callback, result, cert);
}
});
let fakeCertDB = {
openSignedAppFileAsync(root, file, callback) {
// First try calling the real cert DB
realCertDB.openSignedAppFileAsync(root, file, (result, zipReader, cert) => {
function call(callback, result, cert) {
callback.openSignedAppFileFinished(result, zipReader, cert);
}
verifyCert(call, file.clone(), result, cert, callback);
});
},
verifySignedDirectoryAsync(root, dir, callback) {
// First try calling the real cert DB
realCertDB.verifySignedDirectoryAsync(root, dir, (result, cert) => {
function call(callback, result, cert) {
callback.verifySignedDirectoryFinished(result, cert);
}
verifyCert(call, dir.clone(), result, cert, callback);
});
},
QueryInterface: XPCOMUtils.generateQI([AM_Ci.nsIX509CertDB])
};
for (let property of Object.keys(realCertDB)) {
if (property in fakeCertDB) {
continue;
}
if (typeof realCertDB[property] == "function") {
fakeCertDB[property] = realCertDB[property].bind(realCertDB);
}
}
let certDBFactory = {
createInstance: function(outer, iid) {
if (outer != null) {
throw Cr.NS_ERROR_NO_AGGREGATION;
}
return fakeCertDB.QueryInterface(iid);
}
};
registrar.registerFactory(CERTDB_CID, "CertDB",
CERTDB_CONTRACTID, certDBFactory);
}
overrideCertDB();
/**
* Tests that an add-on does appear in the crash report annotations, if
* crash reporting is enabled. The test will fail if the add-on is not in the
@ -1736,8 +1911,8 @@ Services.prefs.setCharPref("extensions.hotfix.id", "");
Services.prefs.setCharPref(PREF_EM_MIN_COMPAT_APP_VERSION, "0");
Services.prefs.setCharPref(PREF_EM_MIN_COMPAT_PLATFORM_VERSION, "0");
// Disable signature checks for most tests
Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_REQUIRED, false);
// Ensure signature checks are enabled by default
Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_REQUIRED, true);
// Register a temporary directory for the tests.
const gTmpD = gProfD.clone();

View File

@ -11,6 +11,9 @@ var CacheFlushObserver = {
observe: function(aSubject, aTopic, aData) {
if (aTopic != "flush-cache-entry")
return;
// Ignore flushes triggered by the fake cert DB
if (aData == "cert-override")
return;
do_check_true(gExpectedFile != null);
do_check_true(aSubject instanceof AM_Ci.nsIFile);

View File

@ -285,6 +285,7 @@ add_task(function*() {
gAppInfo.browserTabsRemoteAutostart = true;
Services.prefs.setBoolPref("extensions.e10sBlocksEnabling", true);
Services.prefs.setCharPref("extensions.hotfix.id", ID);
Services.prefs.setBoolPref("extensions.hotfix.cert.checkAttributes", false);
yield check_normal();
});

View File

@ -49,7 +49,7 @@ add_task(function*() {
do_check_false(addon.appDisabled);
do_check_true(addon.isActive);
do_check_eq(addon.type, "extension");
do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_MISSING : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_SIGNED : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
do_check_true(proxyFile.exists());

View File

@ -1,5 +1,5 @@
// Enable signature checks for these tests
Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_REQUIRED, true);
gUseRealCertChecks = true;
// Disable update security
Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);

View File

@ -1,5 +1,5 @@
// Enable signature checks for these tests
Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_REQUIRED, true);
gUseRealCertChecks = true;
// Disable update security
Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);

View File

@ -6,6 +6,8 @@ Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);
// The test add-ons were signed by the dev root
Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_DEV_ROOT, true);
gUseRealCertChecks = true;
const DATA = "data/signing_checks/";
const ID_63 = "123456789012345678901234567890123456789012345@tests.mozilla.org"
@ -35,17 +37,18 @@ add_task(function* test_working() {
}
});
// Installs the cases that should be broken
// Checks the cases that should be broken
add_task(function* test_broken() {
yield promiseInstallAllFiles([do_get_file(DATA + "long_63_hash.xpi"),
do_get_file(DATA + "long_64_hash.xpi")]);
function promiseInstallForFile(file) {
return new Promise(resolve => AddonManager.getInstallForFile(file, resolve));
}
let addons = yield promiseAddonsByIDs([ID_63, ID_64]);
let promises = [promiseInstallForFile(do_get_file(DATA + "long_63_hash.xpi")),
promiseInstallForFile(do_get_file(DATA + "long_64_hash.xpi"))];
let installs = yield Promise.all(promises);
for (let addon of addons) {
do_check_neq(addon, null);
do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_BROKEN);
addon.uninstall();
for (let install of installs) {
do_check_eq(install.state, AddonManager.STATE_DOWNLOAD_FAILED);
do_check_eq(install.error, AddonManager.ERROR_CORRUPT_FILE);
}
});

View File

@ -1,5 +1,5 @@
// Enable signature checks for these tests
Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_REQUIRED, true);
gUseRealCertChecks = true;
// Disable update security
Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);
// Allow attempting to show the compatibility UI which should not happen

View File

@ -1,5 +1,5 @@
// Enable signature checks for these tests
Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_REQUIRED, true);
gUseRealCertChecks = true;
// Disable update security
Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);

View File

@ -1,5 +1,6 @@
// Disable update security
Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);
gUseRealCertChecks = true;
const DATA = "data/signing_checks/";
const ID = "test@tests.mozilla.org";
@ -54,6 +55,7 @@ function run_test() {
// Updating the pref without changing the app version won't disable add-ons
// immediately but will after a signing check
add_task(function*() {
Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_REQUIRED, false);
startupManager();
// Install the signed add-on

View File

@ -1,5 +1,5 @@
// Enable signature checks for these tests
Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_REQUIRED, true);
gUseRealCertChecks = true;
// Disable update security
Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);

View File

@ -57,7 +57,7 @@ add_task(function*() {
do_check_false(addon.appDisabled);
do_check_true(addon.isActive);
do_check_eq(addon.type, "extension");
do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_MISSING : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_SIGNED : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
yield promiseRestartManager();
@ -87,7 +87,7 @@ add_task(function*() {
do_check_false(addon.appDisabled);
do_check_true(addon.isActive);
do_check_eq(addon.type, "extension");
do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_MISSING : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_SIGNED : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
// test that an unpacked add-on works too
let tempdir = gTmpD.clone();
@ -125,7 +125,7 @@ add_task(function*() {
do_check_false(addon.appDisabled);
do_check_true(addon.isActive);
do_check_eq(addon.type, "extension");
do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_MISSING : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_SIGNED : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
restartManager();
@ -142,7 +142,7 @@ add_task(function*() {
do_check_false(addon.appDisabled);
do_check_true(addon.isActive);
do_check_eq(addon.type, "extension");
do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_MISSING : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_SIGNED : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
unpacked_addon.remove(true);
addon.uninstall();
@ -226,7 +226,7 @@ add_task(function*() {
do_check_false(addon.appDisabled);
do_check_true(addon.isActive);
do_check_eq(addon.type, "extension");
do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_MISSING : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_SIGNED : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
addon.uninstall();
@ -243,7 +243,7 @@ add_task(function*() {
do_check_false(addon.appDisabled);
do_check_true(addon.isActive);
do_check_eq(addon.type, "extension");
do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_MISSING : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_SIGNED : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
unpacked_addon.remove(true);
addon.uninstall();
@ -314,7 +314,7 @@ add_task(function*() {
do_check_false(tempAddon.appDisabled);
do_check_true(tempAddon.isActive);
do_check_eq(tempAddon.type, "extension");
do_check_eq(tempAddon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_MISSING : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
do_check_eq(tempAddon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_SIGNED : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
tempAddon.uninstall();
unpacked_addon.remove(true);
@ -333,7 +333,7 @@ add_task(function*() {
do_check_false(addon.appDisabled);
do_check_true(addon.isActive);
do_check_eq(addon.type, "extension");
do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_MISSING : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_SIGNED : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
addon.uninstall();
@ -368,7 +368,7 @@ add_task(function*(){
do_check_false(addon.appDisabled);
do_check_true(addon.isActive);
do_check_eq(addon.type, "extension");
do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_MISSING : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_SIGNED : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
let tempdir = gTmpD.clone();
writeInstallRDFToDir({
@ -423,7 +423,7 @@ add_task(function*() {
do_check_false(addon.appDisabled);
do_check_true(addon.isActive);
do_check_eq(addon.type, "extension");
do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_MISSING : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_SIGNED : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
try {
yield AddonManager.installTemporaryAddon(do_get_addon("test_bootstrap1_1"));

View File

@ -55,7 +55,7 @@ add_task(function*() {
do_check_false(addon.appDisabled);
do_check_true(addon.isActive);
do_check_eq(addon.type, "extension");
do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_MISSING : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_SIGNED : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
let uri = do_get_addon_root_uri(profileDir, ID);
@ -82,7 +82,7 @@ add_task(function*() {
do_check_false(addon.appDisabled);
do_check_true(addon.isActive);
do_check_eq(addon.type, "extension");
do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_MISSING : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_SIGNED : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
let file = getFileForAddon(profileDir, ID);
do_check_true(file.exists());
@ -135,7 +135,7 @@ add_task(function*() {
do_check_false(addon.appDisabled);
do_check_true(addon.isActive);
do_check_eq(addon.type, "extension");
do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_MISSING : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_SIGNED : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
let file = getFileForAddon(profileDir, ID);
do_check_true(file.exists());

View File

@ -239,6 +239,7 @@ fail-if = buildapp == "mulet" || os == "android"
[test_safemode.js]
[test_signed_updatepref.js]
run-if = addon_signing
skip-if = require_signing
[test_signed_verify.js]
run-if = addon_signing
[test_signed_inject.js]