Bug 1253740 - Try to build and function even on Android, r?kmag draft
authorEthan Glasser-Camp <eglassercamp@mozilla.com>
Mon, 07 Nov 2016 12:13:08 -0500
changeset 437985 5bed712e9124ec857b7c4486cd035826b579a897
parent 437984 3b7c7886dfcdd76057001e3f6505031e7965a7dd
child 439379 ea1a8b7b32f1467878a8795c319901643ac10147
push id35578
push usereglassercamp@mozilla.com
push dateSat, 12 Nov 2016 03:33:15 +0000
reviewerskmag
bugs1253740
milestone52.0a1
Bug 1253740 - Try to build and function even on Android, r?kmag MozReview-Commit-ID: 5NGXzNhHGUN
toolkit/components/extensions/ExtensionStorageSync.jsm
--- a/toolkit/components/extensions/ExtensionStorageSync.jsm
+++ b/toolkit/components/extensions/ExtensionStorageSync.jsm
@@ -177,135 +177,137 @@ const cryptoCollectionIdSchema = {
     throw new Error("cannot generate IDs for system collection");
   },
 
   validate(id) {
     return true;
   },
 };
 
-/**
- * Wrapper around the crypto collection providing some handy utilities.
- */
-const cryptoCollection = this.cryptoCollection = {
-  getCollection: Task.async(function* () {
-    const {kinto} = yield storageSyncInit;
-    return kinto.collection(STORAGE_SYNC_CRYPTO_COLLECTION_NAME, {
-      idSchema: cryptoCollectionIdSchema,
-      remoteTransformers: [new KeyRingEncryptionRemoteTransformer()],
-    });
-  }),
-
+let cryptoCollection, CollectionKeyEncryptionRemoteTransformer;
+if (AppConstants.platform != "android") {
   /**
-   * Retrieve the keyring record from the crypto collection.
-   *
-   * You can use this if you want to check metadata on the keyring
-   * record rather than use the keyring itself.
-   *
-   * @returns {Promise<Object>}
+   * Wrapper around the crypto collection providing some handy utilities.
    */
-  getKeyRingRecord: Task.async(function* () {
-    const collection = yield this.getCollection();
-    const cryptoKeyRecord = yield collection.getAny(STORAGE_SYNC_CRYPTO_KEYRING_RECORD_ID);
+  cryptoCollection = this.cryptoCollection = {
+    getCollection: Task.async(function* () {
+      const {kinto} = yield storageSyncInit;
+      return kinto.collection(STORAGE_SYNC_CRYPTO_COLLECTION_NAME, {
+        idSchema: cryptoCollectionIdSchema,
+        remoteTransformers: [new KeyRingEncryptionRemoteTransformer()],
+      });
+    }),
+
+    /**
+     * Retrieve the keyring record from the crypto collection.
+     *
+     * You can use this if you want to check metadata on the keyring
+     * record rather than use the keyring itself.
+     *
+     * @returns {Promise<Object>}
+     */
+    getKeyRingRecord: Task.async(function* () {
+      const collection = yield this.getCollection();
+      const cryptoKeyRecord = yield collection.getAny(STORAGE_SYNC_CRYPTO_KEYRING_RECORD_ID);
+
+      let data = cryptoKeyRecord.data;
+      if (!data) {
+        // This is a new keyring. Invent an ID for this record. If this
+        // changes, it means a client replaced the keyring, so we need to
+        // reupload everything.
+        const uuidgen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
+        const uuid = uuidgen.generateUUID();
+        data = {uuid};
+      }
+      return data;
+    }),
 
-    let data = cryptoKeyRecord.data;
-    if (!data) {
-      // This is a new keyring. Invent an ID for this record. If this
-      // changes, it means a client replaced the keyring, so we need to
-      // reupload everything.
-      const uuidgen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
-      const uuid = uuidgen.generateUUID();
-      data = {uuid};
-    }
-    return data;
-  }),
+    /**
+     * Retrieve the actual keyring from the crypto collection.
+     *
+     * @returns {Promise<CollectionKeyManager>}
+     */
+    getKeyRing: Task.async(function* () {
+      const cryptoKeyRecord = yield this.getKeyRingRecord();
+      const collectionKeys = new CollectionKeyManager();
+      if (cryptoKeyRecord.keys) {
+        collectionKeys.setContents(cryptoKeyRecord.keys, cryptoKeyRecord.last_modified);
+      } else {
+        // We never actually use the default key, so it's OK if we
+        // generate one multiple times.
+        collectionKeys.generateDefaultKey();
+      }
+      // Pass through uuid field so that we can save it if we need to.
+      collectionKeys.uuid = cryptoKeyRecord.uuid;
+      return collectionKeys;
+    }),
+
+    updateKBHash: Task.async(function* (kbHash) {
+      const coll = yield this.getCollection();
+      yield coll.update({id: STORAGE_SYNC_CRYPTO_KEYRING_RECORD_ID,
+                         kbHash: kbHash},
+                        {patch: true});
+    }),
+
+    upsert: Task.async(function* (record) {
+      const collection = yield this.getCollection();
+      yield collection.upsert(record);
+    }),
+
+    sync: Task.async(function* () {
+      const collection = yield this.getCollection();
+      return yield ExtensionStorageSync._syncCollection(collection, {
+        strategy: "server_wins",
+      });
+    }),
+
+    /**
+     * Reset sync status for ALL collections by directly
+     * accessing the FirefoxAdapter.
+     */
+    resetSyncStatus: Task.async(function* () {
+      const coll = yield this.getCollection();
+      yield coll.db.resetSyncStatus();
+    }),
+
+    // Used only for testing.
+    _clear: Task.async(function* () {
+      const collection = yield this.getCollection();
+      yield collection.clear();
+    }),
+  };
 
   /**
-   * Retrieve the actual keyring from the crypto collection.
+   * An EncryptionRemoteTransformer that uses the special "keys" record
+   * to find a key for a given extension.
    *
-   * @returns {Promise<CollectionKeyManager>}
+   * @param {string} extensionId The extension ID for which to find a key.
    */
-  getKeyRing: Task.async(function* () {
-    const cryptoKeyRecord = yield this.getKeyRingRecord();
-    const collectionKeys = new CollectionKeyManager();
-    if (cryptoKeyRecord.keys) {
-      collectionKeys.setContents(cryptoKeyRecord.keys, cryptoKeyRecord.last_modified);
-    } else {
-      // We never actually use the default key, so it's OK if we
-      // generate one multiple times.
-      collectionKeys.generateDefaultKey();
+  CollectionKeyEncryptionRemoteTransformer = class extends EncryptionRemoteTransformer {
+    constructor(extensionId) {
+      super();
+      this.extensionId = extensionId;
     }
-    // Pass through uuid field so that we can save it if we need to.
-    collectionKeys.uuid = cryptoKeyRecord.uuid;
-    return collectionKeys;
-  }),
-
-  updateKBHash: Task.async(function* (kbHash) {
-    const coll = yield this.getCollection();
-    yield coll.update({id: STORAGE_SYNC_CRYPTO_KEYRING_RECORD_ID,
-                       kbHash: kbHash},
-                      {patch: true});
-  }),
-
-  upsert: Task.async(function* (record) {
-    const collection = yield this.getCollection();
-    yield collection.upsert(record);
-  }),
-
-  sync: Task.async(function* () {
-    const collection = yield this.getCollection();
-    return yield ExtensionStorageSync._syncCollection(collection, {
-      strategy: "server_wins",
-    });
-  }),
 
-  /**
-   * Reset sync status for ALL collections by directly
-   * accessing the FirefoxAdapter.
-   */
-  resetSyncStatus: Task.async(function* () {
-    const coll = yield this.getCollection();
-    yield coll.db.resetSyncStatus();
-  }),
-
-  // Used only for testing.
-  _clear: Task.async(function* () {
-    const collection = yield this.getCollection();
-    yield collection.clear();
-  }),
-};
-
-/**
- * An EncryptionRemoteTransformer that uses the special "keys" record
- * to find a key for a given extension.
- *
- * @param {string} extensionId The extension ID for which to find a key.
- */
-class CollectionKeyEncryptionRemoteTransformer extends EncryptionRemoteTransformer {
-  constructor(extensionId) {
-    super();
-    this.extensionId = extensionId;
-  }
-
-  getKeys() {
-    const self = this;
-    return Task.spawn(function* () {
-      // FIXME: cache the crypto record for the duration of a sync cycle?
-      const collectionKeys = yield cryptoCollection.getKeyRing();
-      if (!collectionKeys.hasKeysFor([self.extensionId])) {
-        // This should never happen. Keys should be created (and
-        // synced) at the beginning of the sync cycle.
-        throw new Error(`tried to encrypt records for ${this.extensionId}, but key is not present`);
-      }
-      return collectionKeys.keyForCollection(self.extensionId);
-    });
-  }
+    getKeys() {
+      const self = this;
+      return Task.spawn(function* () {
+        // FIXME: cache the crypto record for the duration of a sync cycle?
+        const collectionKeys = yield cryptoCollection.getKeyRing();
+        if (!collectionKeys.hasKeysFor([self.extensionId])) {
+          // This should never happen. Keys should be created (and
+          // synced) at the beginning of the sync cycle.
+          throw new Error(`tried to encrypt records for ${this.extensionId}, but key is not present`);
+        }
+        return collectionKeys.keyForCollection(self.extensionId);
+      });
+    }
+  };
+  global.CollectionKeyEncryptionRemoteTransformer = CollectionKeyEncryptionRemoteTransformer;
 }
-global.CollectionKeyEncryptionRemoteTransformer = CollectionKeyEncryptionRemoteTransformer;
-
 /**
  * Clean up now that one context is no longer using this extension's collection.
  *
  * @param {Extension} extension
  *                    The extension whose context just ended.
  * @param {Context} context
  *                  The context that just ended.
  */
@@ -331,19 +333,23 @@ function cleanUpForContext(extension, co
  *                  The context for this extension. The Collection
  *                  will shut down automatically when all contexts
  *                  close.
  * @returns {Promise<Collection>}
  */
 const openCollection = Task.async(function* (extension, context) {
   let collectionId = extension.id;
   const {kinto} = yield storageSyncInit;
+  const remoteTransformers = [];
+  if (CollectionKeyEncryptionRemoteTransformer) {
+    remoteTransformers.push(new CollectionKeyEncryptionRemoteTransformer(extension.id));
+  }
   const coll = kinto.collection(collectionId, {
     idSchema: storageSyncIdSchema,
-    remoteTransformers: [new CollectionKeyEncryptionRemoteTransformer(extension.id)],
+    remoteTransformers,
   });
   return coll;
 });
 
 /**
  * Hash an extension ID for a given user so that an attacker can't
  * identify the extensions a user has installed.
  *
@@ -360,18 +366,36 @@ function extensionIdToCollectionId(user,
   let hasher = Cc["@mozilla.org/security/hash;1"]
                  .createInstance(Ci.nsICryptoHash);
   hasher.init(hasher.SHA256);
   hasher.update(data, data.length);
 
   return CommonUtils.bytesAsHex(hasher.finish(false));
 }
 
+/**
+ * Verify that we were built on not-Android. Call this as a sanity
+ * check before using cryptoCollection.
+ */
+function ensureCryptoCollection() {
+  if (!cryptoCollection) {
+    throw new Error("Call to ensureKeysFor, but no sync code; are you on Android?");
+  }
+}
+
+// FIXME: This is kind of ugly. Probably we should have
+// ExtensionStorageSync not be a singleton, but a constructed object,
+// and this should be a constructor argument.
+let _fxaService = null;
+if (AppConstants.platform != "android") {
+  _fxaService = fxAccounts;
+}
+
 this.ExtensionStorageSync = {
-  _fxaService: fxAccounts,
+  _fxaService,
   listeners: new WeakMap(),
 
   syncAll: Task.async(function* () {
     const extensions = extensionContexts.keys();
     const extIds = Array.from(extensions, extension => extension.id);
     log.debug(`Syncing extension settings for ${JSON.stringify(extIds)}\n`);
     if (extIds.length == 0) {
       // No extensions to sync. Get out.
@@ -507,16 +531,18 @@ this.ExtensionStorageSync = {
    * as well as that on the server, have keys for all the extensions
    * in extIds.
    *
    * @param {Array<string>} extIds
    *                        The IDs of the extensions which need keys.
    * @returns {Promise<CollectionKeyManager>}
    */
   ensureKeysFor: Task.async(function* (extIds) {
+    ensureCryptoCollection();
+
     const collectionKeys = yield cryptoCollection.getKeyRing();
     if (collectionKeys.hasKeysFor(extIds)) {
       return collectionKeys;
     }
 
     const kbHash = yield this.getKBHash();
     const newKeys = yield collectionKeys.ensureKeysFor(extIds);
     const newRecord = {
@@ -560,16 +586,18 @@ this.ExtensionStorageSync = {
     hasher.init(hasher.SHA256);
     return CommonUtils.bytesAsHex(CryptoUtils.digestBytes(signedInUser.uid + kBbytes, hasher));
   }),
 
   /**
    * Update the kB in the crypto record.
    */
   updateKeyRingKB: Task.async(function* () {
+    ensureCryptoCollection();
+
     const signedInUser = yield this._fxaService.getSignedInUser();
     if (!signedInUser) {
       // Although this function is meant to be called on login,
       // it's not unreasonable to check any time, even if we aren't
       // logged in.
       //
       // If we aren't logged in, we don't have any information about
       // the user's kB, so we can't be sure that the user changed
@@ -584,30 +612,34 @@ this.ExtensionStorageSync = {
   /**
    * Make sure the keyring is up to date and synced.
    *
    * This is called on syncs to make sure that we don't sync anything
    * to any collection unless the key for that collection is on the
    * server.
    */
   checkSyncKeyRing: Task.async(function* () {
+    ensureCryptoCollection();
+
     yield this.updateKeyRingKB();
 
     const cryptoKeyRecord = yield cryptoCollection.getKeyRingRecord();
     if (cryptoKeyRecord && cryptoKeyRecord._status !== "synced") {
       // We haven't successfully synced the keyring since the last
       // change. This could be because kB changed and we touched the
       // keyring, or it could be because we failed to sync after
       // adding a key. Either way, take this opportunity to sync the
       // keyring.
       yield this._syncKeyRing(cryptoKeyRecord);
     }
   }),
 
   _syncKeyRing: Task.async(function* (cryptoKeyRecord) {
+    ensureCryptoCollection();
+
     try {
       // Try to sync using server_wins.
       //
       // We use server_wins here because whatever is on the server is
       // at least consistent with itself -- the crypto in the keyring
       // matches the crypto on the collection records. This is because
       // we generate and upload keys just before syncing data.
       //