summaryrefslogtreecommitdiffstats
path: root/services/common/blocklist-clients.js
diff options
context:
space:
mode:
Diffstat (limited to 'services/common/blocklist-clients.js')
-rw-r--r--services/common/blocklist-clients.js310
1 files changed, 0 insertions, 310 deletions
diff --git a/services/common/blocklist-clients.js b/services/common/blocklist-clients.js
deleted file mode 100644
index fc51aaca4..000000000
--- a/services/common/blocklist-clients.js
+++ /dev/null
@@ -1,310 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-this.EXPORTED_SYMBOLS = ["AddonBlocklistClient",
- "GfxBlocklistClient",
- "OneCRLBlocklistClient",
- "PluginBlocklistClient",
- "FILENAME_ADDONS_JSON",
- "FILENAME_GFX_JSON",
- "FILENAME_PLUGINS_JSON"];
-
-const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
-
-Cu.import("resource://gre/modules/Services.jsm");
-const { Task } = Cu.import("resource://gre/modules/Task.jsm");
-const { OS } = Cu.import("resource://gre/modules/osfile.jsm");
-Cu.importGlobalProperties(["fetch"]);
-
-const { loadKinto } = Cu.import("resource://services-common/kinto-offline-client.js");
-const { KintoHttpClient } = Cu.import("resource://services-common/kinto-http-client.js");
-const { CanonicalJSON } = Components.utils.import("resource://gre/modules/CanonicalJSON.jsm");
-
-const PREF_SETTINGS_SERVER = "services.settings.server";
-const PREF_BLOCKLIST_BUCKET = "services.blocklist.bucket";
-const PREF_BLOCKLIST_ONECRL_COLLECTION = "services.blocklist.onecrl.collection";
-const PREF_BLOCKLIST_ONECRL_CHECKED_SECONDS = "services.blocklist.onecrl.checked";
-const PREF_BLOCKLIST_ADDONS_COLLECTION = "services.blocklist.addons.collection";
-const PREF_BLOCKLIST_ADDONS_CHECKED_SECONDS = "services.blocklist.addons.checked";
-const PREF_BLOCKLIST_PLUGINS_COLLECTION = "services.blocklist.plugins.collection";
-const PREF_BLOCKLIST_PLUGINS_CHECKED_SECONDS = "services.blocklist.plugins.checked";
-const PREF_BLOCKLIST_GFX_COLLECTION = "services.blocklist.gfx.collection";
-const PREF_BLOCKLIST_GFX_CHECKED_SECONDS = "services.blocklist.gfx.checked";
-const PREF_BLOCKLIST_ENFORCE_SIGNING = "services.blocklist.signing.enforced";
-
-const INVALID_SIGNATURE = "Invalid content/signature";
-
-this.FILENAME_ADDONS_JSON = "blocklist-addons.json";
-this.FILENAME_GFX_JSON = "blocklist-gfx.json";
-this.FILENAME_PLUGINS_JSON = "blocklist-plugins.json";
-
-function mergeChanges(localRecords, changes) {
- // Kinto.js adds attributes to local records that aren't present on server.
- // (e.g. _status)
- const stripPrivateProps = (obj) => {
- return Object.keys(obj).reduce((current, key) => {
- if (!key.startsWith("_")) {
- current[key] = obj[key];
- }
- return current;
- }, {});
- };
-
- const records = {};
- // Local records by id.
- localRecords.forEach((record) => records[record.id] = stripPrivateProps(record));
- // All existing records are replaced by the version from the server.
- changes.forEach((record) => records[record.id] = record);
-
- return Object.values(records)
- // Filter out deleted records.
- .filter((record) => record.deleted != true)
- // Sort list by record id.
- .sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
-}
-
-
-function fetchCollectionMetadata(collection) {
- const client = new KintoHttpClient(collection.api.remote);
- return client.bucket(collection.bucket).collection(collection.name).getData()
- .then(result => {
- return result.signature;
- });
-}
-
-function fetchRemoteCollection(collection) {
- const client = new KintoHttpClient(collection.api.remote);
- return client.bucket(collection.bucket)
- .collection(collection.name)
- .listRecords({sort: "id"});
-}
-
-/**
- * Helper to instantiate a Kinto client based on preferences for remote server
- * URL and bucket name. It uses the `FirefoxAdapter` which relies on SQLite to
- * persist the local DB.
- */
-function kintoClient() {
- let base = Services.prefs.getCharPref(PREF_SETTINGS_SERVER);
- let bucket = Services.prefs.getCharPref(PREF_BLOCKLIST_BUCKET);
-
- let Kinto = loadKinto();
-
- let FirefoxAdapter = Kinto.adapters.FirefoxAdapter;
-
- let config = {
- remote: base,
- bucket: bucket,
- adapter: FirefoxAdapter,
- };
-
- return new Kinto(config);
-}
-
-
-class BlocklistClient {
-
- constructor(collectionName, lastCheckTimePref, processCallback, signerName) {
- this.collectionName = collectionName;
- this.lastCheckTimePref = lastCheckTimePref;
- this.processCallback = processCallback;
- this.signerName = signerName;
- }
-
- validateCollectionSignature(payload, collection, ignoreLocal) {
- return Task.spawn((function* () {
- // this is a content-signature field from an autograph response.
- const {x5u, signature} = yield fetchCollectionMetadata(collection);
- const certChain = yield fetch(x5u).then((res) => res.text());
-
- const verifier = Cc["@mozilla.org/security/contentsignatureverifier;1"]
- .createInstance(Ci.nsIContentSignatureVerifier);
-
- let toSerialize;
- if (ignoreLocal) {
- toSerialize = {
- last_modified: `${payload.last_modified}`,
- data: payload.data
- };
- } else {
- const localRecords = (yield collection.list()).data;
- const records = mergeChanges(localRecords, payload.changes);
- toSerialize = {
- last_modified: `${payload.lastModified}`,
- data: records
- };
- }
-
- const serialized = CanonicalJSON.stringify(toSerialize);
-
- if (verifier.verifyContentSignature(serialized, "p384ecdsa=" + signature,
- certChain,
- this.signerName)) {
- // In case the hash is valid, apply the changes locally.
- return payload;
- }
- throw new Error(INVALID_SIGNATURE);
- }).bind(this));
- }
-
- /**
- * Synchronize from Kinto server, if necessary.
- *
- * @param {int} lastModified the lastModified date (on the server) for
- the remote collection.
- * @param {Date} serverTime the current date return by the server.
- * @return {Promise} which rejects on sync or process failure.
- */
- maybeSync(lastModified, serverTime) {
- let db = kintoClient();
- let opts = {};
- let enforceCollectionSigning =
- Services.prefs.getBoolPref(PREF_BLOCKLIST_ENFORCE_SIGNING);
-
- // if there is a signerName and collection signing is enforced, add a
- // hook for incoming changes that validates the signature
- if (this.signerName && enforceCollectionSigning) {
- opts.hooks = {
- "incoming-changes": [this.validateCollectionSignature.bind(this)]
- }
- }
-
- let collection = db.collection(this.collectionName, opts);
-
- return Task.spawn((function* syncCollection() {
- try {
- yield collection.db.open();
-
- let collectionLastModified = yield collection.db.getLastModified();
- // If the data is up to date, there's no need to sync. We still need
- // to record the fact that a check happened.
- if (lastModified <= collectionLastModified) {
- this.updateLastCheck(serverTime);
- return;
- }
- // Fetch changes from server.
- try {
- let syncResult = yield collection.sync();
- if (!syncResult.ok) {
- throw new Error("Sync failed");
- }
- } catch (e) {
- if (e.message == INVALID_SIGNATURE) {
- // if sync fails with a signature error, it's likely that our
- // local data has been modified in some way.
- // We will attempt to fix this by retrieving the whole
- // remote collection.
- let payload = yield fetchRemoteCollection(collection);
- yield this.validateCollectionSignature(payload, collection, true);
- // if the signature is good (we haven't thrown), and the remote
- // last_modified is newer than the local last_modified, replace the
- // local data
- const localLastModified = yield collection.db.getLastModified();
- if (payload.last_modified >= localLastModified) {
- yield collection.clear();
- yield collection.loadDump(payload.data);
- }
- } else {
- throw e;
- }
- }
- // Read local collection of records.
- let list = yield collection.list();
-
- yield this.processCallback(list.data);
-
- // Track last update.
- this.updateLastCheck(serverTime);
- } finally {
- collection.db.close();
- }
- }).bind(this));
- }
-
- /**
- * Save last time server was checked in users prefs.
- *
- * @param {Date} serverTime the current date return by server.
- */
- updateLastCheck(serverTime) {
- let checkedServerTimeInSeconds = Math.round(serverTime / 1000);
- Services.prefs.setIntPref(this.lastCheckTimePref, checkedServerTimeInSeconds);
- }
-}
-
-/**
- * Revoke the appropriate certificates based on the records from the blocklist.
- *
- * @param {Object} records current records in the local db.
- */
-function* updateCertBlocklist(records) {
- let certList = Cc["@mozilla.org/security/certblocklist;1"]
- .getService(Ci.nsICertBlocklist);
- for (let item of records) {
- try {
- if (item.issuerName && item.serialNumber) {
- certList.revokeCertByIssuerAndSerial(item.issuerName,
- item.serialNumber);
- } else if (item.subject && item.pubKeyHash) {
- certList.revokeCertBySubjectAndPubKey(item.subject,
- item.pubKeyHash);
- }
- } catch (e) {
- // prevent errors relating to individual blocklist entries from
- // causing sync to fail. At some point in the future, we may want to
- // accumulate telemetry on these failures.
- Cu.reportError(e);
- }
- }
- certList.saveEntries();
-}
-
-/**
- * Write list of records into JSON file, and notify nsBlocklistService.
- *
- * @param {String} filename path relative to profile dir.
- * @param {Object} records current records in the local db.
- */
-function* updateJSONBlocklist(filename, records) {
- // Write JSON dump for synchronous load at startup.
- const path = OS.Path.join(OS.Constants.Path.profileDir, filename);
- const serialized = JSON.stringify({data: records}, null, 2);
- try {
- yield OS.File.writeAtomic(path, serialized, {tmpPath: path + ".tmp"});
-
- // Notify change to `nsBlocklistService`
- const eventData = {filename: filename};
- Services.cpmm.sendAsyncMessage("Blocklist:reload-from-disk", eventData);
- } catch(e) {
- Cu.reportError(e);
- }
-}
-
-
-this.OneCRLBlocklistClient = new BlocklistClient(
- Services.prefs.getCharPref(PREF_BLOCKLIST_ONECRL_COLLECTION),
- PREF_BLOCKLIST_ONECRL_CHECKED_SECONDS,
- updateCertBlocklist,
- "onecrl.content-signature.mozilla.org"
-);
-
-this.AddonBlocklistClient = new BlocklistClient(
- Services.prefs.getCharPref(PREF_BLOCKLIST_ADDONS_COLLECTION),
- PREF_BLOCKLIST_ADDONS_CHECKED_SECONDS,
- updateJSONBlocklist.bind(undefined, FILENAME_ADDONS_JSON)
-);
-
-this.GfxBlocklistClient = new BlocklistClient(
- Services.prefs.getCharPref(PREF_BLOCKLIST_GFX_COLLECTION),
- PREF_BLOCKLIST_GFX_CHECKED_SECONDS,
- updateJSONBlocklist.bind(undefined, FILENAME_GFX_JSON)
-);
-
-this.PluginBlocklistClient = new BlocklistClient(
- Services.prefs.getCharPref(PREF_BLOCKLIST_PLUGINS_COLLECTION),
- PREF_BLOCKLIST_PLUGINS_CHECKED_SECONDS,
- updateJSONBlocklist.bind(undefined, FILENAME_PLUGINS_JSON)
-);