CloudServices/Sync/ExtensionStorage Design Doc

From MozillaWiki
< CloudServices‎ | Sync
Revision as of 15:56, 26 September 2016 by Glasserc (talk | contribs) (explain a bunch of edge cases)
Jump to navigation Jump to search

Extension Storage Sync is available through the chrome.storage.sync WebExtension API. It syncs as part of Firefox Sync, but using a different storage backend (Kinto).

As much as possible, Extension Storage Sync is meant to seem like "part of Sync" from the user's perspective. This means that Extension Storage Sync takes a similar approach to its cryptography as the rest of Sync.

Kinto structure

Kinto is an object store with three levels -- buckets, which contain collections, which contain records. A given user just uses the "default" bucket, which is mapped to a different bucket for each user. Each extension gets its own collection. Each key/value pair that an extension stores becomes its own record.

Crypto

When a user does a sync, we want the user's data to be stored securely, so we encrypt it. This encryption happens using the Kinto "remote transformer" feature. This means that encryption happens on the client side before sending the data, or just after receiving the data.

Each collection (thus, extension) gets its own key. These keys are stored in a separate "keyring", which is itself stored as a record in a special "crypto" collection. This record is encrypted using a key that is derived from a user's kB. This two-tier crypto system was inherited from Firefox Sync and it helps us to minimize data that we reupload when a user's kB changes.

When we sync, we map the local collection name to an "obfuscated" remote collection name. This is done so that metadata doesn't leak information about what extensions a user has installed.

Password changes

Because the "keyring" is encrypted using kB, and there is a relationship between kB and the user's password, there are a couple of wrinkles that we need to be aware of when a user's password changes.

A user's password changes due to a "change password" event and due to a "reset password" event. These behave differently with regard to kB.

During a "change password" event, kB doesn't change. kB is a random value that is "wrapped" with the user's password, but since a user who is performing a "change password" has access to the old password, they have access to the unwrapped kB. After the password is changed, FxA rewraps kB using the new password and uploads it. kB doesn't change, so our crypto is fine, and we don't have to do anything.

During a "reset password" event, kB changes because the user no longer has access to the old kB. However, the desktop client will still have access to any data that it saved previously. Among other things, this means the keyring. Since the version of the keyring on the server is encrypted with the old kB, we will no longer be able to access it. We should therefore upload the same record encrypted with the new kB every time kB changes.

We detect kB changing by storing the current kB (as a hash) in the keyring record itself. We "update" this keyring record with the current kB hash on every call to sync(). kinto.js tries to track the status of the keyring in a field called _status -- it should be "synced" when it is the same as the version that we expect on the server, and it should be "updated" if we've changed it and haven't pushed it to the server. So after possibly updating the kB hash (or just replacing it with what it already is), we check if the keyring is "updated" and if so, try to upload it to the server. Because encryption happens "just-in-time", this causes the keyring to be reuploaded but encrypted by the new kB.

Edge Cases

  • Can keys in the keyring ever change? No, at the time of this writing, keys are generated once and stay forever. When a user resets their password, the keyring is reuploaded, but the keys in the keyring aren't changed.
  • What happens if I uninstall and reinstall an add-on? Keys are preserved in the keyring forever, so the old data will still be available to that extension, encrypted in the same way on the server.
  • What happens if a user simultaneously installs an add-on (and uses it) in two Firefoxes? Until one Firefox syncs the data for that add-on, no data is encrypted, so there can be no conflict in the keyring. Each Firefox will try to update its keyring to have a new key for this collection. One will "win"; the other will get a conflict, and pull down the new keyring (because syncing is done using "server_wins"). No Firefox will try to upload data for any add-on for which it doesn't have a key in a synced keyring, and two keyring syncs can't be interleaved, so it's impossible for data to be encrypted on the server with two keys.
  • What happens if a user resets their password on one device in the middle of an upload? We only consider a user's password state at the beginning of a sync. If we get as far as uploading extension data, then the keys for those extensions exist on the server, encrypted with the old kB. This is fine because next sync, we will discover that the keyring was encrypted with the old kB, update the kB on that keyring, and reupload it.
  • What happens if a user resets their password on one device when another is uploading? Since the keys for each extension don't change, syncing of this data will continue to work. The only concern is whether syncing the keyrings will work. If one device replaces the keyring with the newly encrypted one, the other device will fail to sync that keyring because it can't successfully decode it. Alternately, if one device updated the keyring with a new key while the other device gets its password reset, the second device will fail to reupload its keyring because it can't decrypt the new version on the server (which was encrypted with the old kB).

Right now, the code is conservative, so it won't try to work without a successfully synced keyring, because this could lead to keys getting lost or data being uploaded with a key that doesn't exist on the server. Ideally, the other devices will also have their passwords reset to the new kB. If a device last updated the keyring with the old kB, it will be the only one that can successfully sync the keyring, and it will do so using the new kB. Each other device that gets the new kB will be able to use the new keyring, once it is uploaded.

  • What's the worst thing that could happen? A user could reset their password on one device and then throw that device in a river and forget the password. If other devices don't get the new kB, they will refuse to sync because they are afraid of erasing an unreadable keyring on the server which has keys that were used to encrypt some extension's data. *I'm not sure what the right fix here is.* One obvious fix is to start ignoring/discarding any record that you can't read for more than some span of time. Since it's impossible for two devices to be syncing with different kBs (one would be logged out), eventually all devices would converge on a set of data that was uploaded with a kB that everyone who was logged in could read. However, this set of data would discard data that extensions already stored, and could be surprising to extension authors.