--- 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.
//