summaryrefslogtreecommitdiffstats
path: root/services/sync/modules/engines/extension-storage.js
diff options
context:
space:
mode:
Diffstat (limited to 'services/sync/modules/engines/extension-storage.js')
-rw-r--r--services/sync/modules/engines/extension-storage.js277
1 files changed, 0 insertions, 277 deletions
diff --git a/services/sync/modules/engines/extension-storage.js b/services/sync/modules/engines/extension-storage.js
deleted file mode 100644
index f8f15b128..000000000
--- a/services/sync/modules/engines/extension-storage.js
+++ /dev/null
@@ -1,277 +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 = ['ExtensionStorageEngine', 'EncryptionRemoteTransformer',
- 'KeyRingEncryptionRemoteTransformer'];
-
-const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
-
-Cu.import("resource://services-crypto/utils.js");
-Cu.import("resource://services-sync/constants.js");
-Cu.import("resource://services-sync/engines.js");
-Cu.import("resource://services-sync/keys.js");
-Cu.import("resource://services-sync/util.js");
-Cu.import("resource://services-common/async.js");
-XPCOMUtils.defineLazyModuleGetter(this, "ExtensionStorageSync",
- "resource://gre/modules/ExtensionStorageSync.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
- "resource://gre/modules/FxAccounts.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Task",
- "resource://gre/modules/Task.jsm");
-
-/**
- * The Engine that manages syncing for the web extension "storage"
- * API, and in particular ext.storage.sync.
- *
- * ext.storage.sync is implemented using Kinto, so it has mechanisms
- * for syncing that we do not need to integrate in the Firefox Sync
- * framework, so this is something of a stub.
- */
-this.ExtensionStorageEngine = function ExtensionStorageEngine(service) {
- SyncEngine.call(this, "Extension-Storage", service);
-};
-ExtensionStorageEngine.prototype = {
- __proto__: SyncEngine.prototype,
- _trackerObj: ExtensionStorageTracker,
- // we don't need these since we implement our own sync logic
- _storeObj: undefined,
- _recordObj: undefined,
-
- syncPriority: 10,
- allowSkippedRecord: false,
-
- _sync: function () {
- return Async.promiseSpinningly(ExtensionStorageSync.syncAll());
- },
-
- get enabled() {
- // By default, we sync extension storage if we sync addons. This
- // lets us simplify the UX since users probably don't consider
- // "extension preferences" a separate category of syncing.
- // However, we also respect engine.extension-storage.force, which
- // can be set to true or false, if a power user wants to customize
- // the behavior despite the lack of UI.
- const forced = Svc.Prefs.get("engine." + this.prefName + ".force", undefined);
- if (forced !== undefined) {
- return forced;
- }
- return Svc.Prefs.get("engine.addons", false);
- },
-};
-
-function ExtensionStorageTracker(name, engine) {
- Tracker.call(this, name, engine);
-}
-ExtensionStorageTracker.prototype = {
- __proto__: Tracker.prototype,
-
- startTracking: function () {
- Svc.Obs.add("ext.storage.sync-changed", this);
- },
-
- stopTracking: function () {
- Svc.Obs.remove("ext.storage.sync-changed", this);
- },
-
- observe: function (subject, topic, data) {
- Tracker.prototype.observe.call(this, subject, topic, data);
-
- if (this.ignoreAll) {
- return;
- }
-
- if (topic !== "ext.storage.sync-changed") {
- return;
- }
-
- // Single adds, removes and changes are not so important on their
- // own, so let's just increment score a bit.
- this.score += SCORE_INCREMENT_MEDIUM;
- },
-
- // Override a bunch of methods which don't do anything for us.
- // This is a performance hack.
- saveChangedIDs: function() {
- },
- loadChangedIDs: function() {
- },
- ignoreID: function() {
- },
- unignoreID: function() {
- },
- addChangedID: function() {
- },
- removeChangedID: function() {
- },
- clearChangedIDs: function() {
- },
-};
-
-/**
- * Utility function to enforce an order of fields when computing an HMAC.
- */
-function ciphertextHMAC(keyBundle, id, IV, ciphertext) {
- const hasher = keyBundle.sha256HMACHasher;
- return Utils.bytesAsHex(Utils.digestUTF8(id + IV + ciphertext, hasher));
-}
-
-/**
- * A "remote transformer" that the Kinto library will use to
- * encrypt/decrypt records when syncing.
- *
- * This is an "abstract base class". Subclass this and override
- * getKeys() to use it.
- */
-class EncryptionRemoteTransformer {
- encode(record) {
- const self = this;
- return Task.spawn(function* () {
- const keyBundle = yield self.getKeys();
- if (record.ciphertext) {
- throw new Error("Attempt to reencrypt??");
- }
- let id = record.id;
- if (!record.id) {
- throw new Error("Record ID is missing or invalid");
- }
-
- let IV = Svc.Crypto.generateRandomIV();
- let ciphertext = Svc.Crypto.encrypt(JSON.stringify(record),
- keyBundle.encryptionKeyB64, IV);
- let hmac = ciphertextHMAC(keyBundle, id, IV, ciphertext);
- const encryptedResult = {ciphertext, IV, hmac, id};
- if (record.hasOwnProperty("last_modified")) {
- encryptedResult.last_modified = record.last_modified;
- }
- return encryptedResult;
- });
- }
-
- decode(record) {
- const self = this;
- return Task.spawn(function* () {
- if (!record.ciphertext) {
- // This can happen for tombstones if a record is deleted.
- if (record.deleted) {
- return record;
- }
- throw new Error("No ciphertext: nothing to decrypt?");
- }
- const keyBundle = yield self.getKeys();
- // Authenticate the encrypted blob with the expected HMAC
- let computedHMAC = ciphertextHMAC(keyBundle, record.id, record.IV, record.ciphertext);
-
- if (computedHMAC != record.hmac) {
- Utils.throwHMACMismatch(record.hmac, computedHMAC);
- }
-
- // Handle invalid data here. Elsewhere we assume that cleartext is an object.
- let cleartext = Svc.Crypto.decrypt(record.ciphertext,
- keyBundle.encryptionKeyB64, record.IV);
- let jsonResult = JSON.parse(cleartext);
- if (!jsonResult || typeof jsonResult !== "object") {
- throw new Error("Decryption failed: result is <" + jsonResult + ">, not an object.");
- }
-
- // Verify that the encrypted id matches the requested record's id.
- // This should always be true, because we compute the HMAC over
- // the original record's ID, and that was verified already (above).
- if (jsonResult.id != record.id) {
- throw new Error("Record id mismatch: " + jsonResult.id + " != " + record.id);
- }
-
- if (record.hasOwnProperty("last_modified")) {
- jsonResult.last_modified = record.last_modified;
- }
-
- return jsonResult;
- });
- }
-
- /**
- * Retrieve keys to use during encryption.
- *
- * Returns a Promise<KeyBundle>.
- */
- getKeys() {
- throw new Error("override getKeys in a subclass");
- }
-}
-// You can inject this
-EncryptionRemoteTransformer.prototype._fxaService = fxAccounts;
-
-/**
- * An EncryptionRemoteTransformer that provides a keybundle derived
- * from the user's kB, suitable for encrypting a keyring.
- */
-class KeyRingEncryptionRemoteTransformer extends EncryptionRemoteTransformer {
- getKeys() {
- const self = this;
- return Task.spawn(function* () {
- const user = yield self._fxaService.getSignedInUser();
- // FIXME: we should permit this if the user is self-hosting
- // their storage
- if (!user) {
- throw new Error("user isn't signed in to FxA; can't sync");
- }
-
- if (!user.kB) {
- throw new Error("user doesn't have kB");
- }
-
- let kB = Utils.hexToBytes(user.kB);
-
- let keyMaterial = CryptoUtils.hkdf(kB, undefined,
- "identity.mozilla.com/picl/v1/chrome.storage.sync", 2*32);
- let bundle = new BulkKeyBundle();
- // [encryptionKey, hmacKey]
- bundle.keyPair = [keyMaterial.slice(0, 32), keyMaterial.slice(32, 64)];
- return bundle;
- });
- }
- // Pass through the kbHash field from the unencrypted record. If
- // encryption fails, we can use this to try to detect whether we are
- // being compromised or if the record here was encoded with a
- // different kB.
- encode(record) {
- const encodePromise = super.encode(record);
- return Task.spawn(function* () {
- const encoded = yield encodePromise;
- encoded.kbHash = record.kbHash;
- return encoded;
- });
- }
-
- decode(record) {
- const decodePromise = super.decode(record);
- return Task.spawn(function* () {
- try {
- return yield decodePromise;
- } catch (e) {
- if (Utils.isHMACMismatch(e)) {
- const currentKBHash = yield ExtensionStorageSync.getKBHash();
- if (record.kbHash != currentKBHash) {
- // Some other client encoded this with a kB that we don't
- // have access to.
- KeyRingEncryptionRemoteTransformer.throwOutdatedKB(currentKBHash, record.kbHash);
- }
- }
- throw e;
- }
- });
- }
-
- // Generator and discriminator for KB-is-outdated exceptions.
- static throwOutdatedKB(shouldBe, is) {
- throw new Error(`kB hash on record is outdated: should be ${shouldBe}, is ${is}`);
- }
-
- static isOutdatedKB(exc) {
- const kbMessage = "kB hash on record is outdated: ";
- return exc && exc.message && exc.message.indexOf &&
- (exc.message.indexOf(kbMessage) == 0);
- }
-}