Labs/Jetpack/ID

From MozillaWiki
< Labs‎ | Jetpack
Jump to: navigation, search

Jetpack "Add-On IDs"

Each add-on (or top-level jetpack program) is defined by a cryptographic keypair. The private key will be used to sign each add-on, and the public key will be used to check those signatures. The public key will be shipped with the add-on, while the private key remains safe on the developer's computer (in ~/.jetpack/keys/).

A secure hash of the public key is used as the "Jetpack ID" or "JID", which is then used to derive the add-on ID (this goes into the install.rdf file) and several other per-add-on identifiers.

A typical JID looks like this: jid0-I1AF9BUoqgpQEorhKSRVp0CjA7U .

Upgrading from pre-0.4 extensions

Before the 0.4 release of the Jetpack SDK, the "id" field of the package.json file (if present) was copied directly into the add-on ID. If this field was missing, a default value would be supplied.

Starting with 0.4, the "id" field is mandatory, and must be a JID in the format shown above. In addition, the corresponding private key must be present in ~/.jetpack/keys . When you run "cfx xpi", you will be prompted to fix any problems with the "id" value. If it is missing, a new keypair will be generated for you and the package.json file will be edited to include the JID.

So if you have some pre-0.4 add-on source code which defines an "id" field, you will need to delete that field from your package.json before "cfx xpi" can complete successfully.

Rationale

One aspect of Jetpack is security. One aspect of security is making (and keeping) various promises. We'd like to be able to promise the user that an add-on which doesn't need network access won't get it. And we'd like to be able to promise the author of one add-on that their code won't be messed up by a second add-on, or by code loaded as part of a web page.

The most obvious thing that needs protection is the add-on's runtime state (the code and variables it uses). We will do this by making sure that all add-ons are isolated from each other, and from web page code. Add-ons cannot be allowed to read or write the private runtime of other add-ons. (this does not rule out interactions between add-ons: one add-on might publish a method that can be called by others, which will change its internal state somehow. It merely says that each add-on gets to control its own data, and that its data cannot be modified without its cooperation. If I want the sandwich that you're eating, I can't just take it, I have to ask you nicely. And you get to say no.).

A slightly more subtle piece of state that needs protecting is the add-on's persistent storage, as accessed through the JEP104 "Simple Storage" API. Add-ons will use this to record preferences, cached lookup results, anything that needs to be retained from one instance of the browser to the next. Add-ons should be able to trust this persistent state just as much as they trust their own internal variables: other add-ons should not be able to read or write it.

When an add-on is upgraded, the new version should have access to the same state that the old version did: otherwise an "upgrade" would behave more like "uninstall and reinstall", with the add-on code losing all of its memory in between. This would be a drag, especially if the user spent a lot of time configuring the add-on's preferences (which they'd have to re-do), or if the add-on spent a lot of time caching data (which would be have to be regenerated).

But this brings up an interesting question: how do we tell the difference between an upgrade and a regular installation? Specifically, given an installed add-on A, and another add-on B that the user has decided to install, why should the Jetpack platform grant B the right to access data that was stored by A?

Basically, we need a proper way to express that B is a new version of A, one that is safe against spoofing attacks (a different add-on "C", which is *not* an upgrade of A, should not be able to pretend to be an upgrade and steal access to A/B's persistent state).

Pre-Jetpack Add-On IDs

The traditional (pre-Jetpack) situation uses an "Add-On ID" to answer this question. This ID is generated by the add-on developer, before they upload the first version to AMO (addons.mozilla.org). Developers are advised to produce a unique ID, either by generating a random UUID (like "{340c2bbc-ce74-4362-90b5-7c26312808ef}"), or by using an email-like string (like "firebug@software.joehewitt.com").

This ID is used as the primary key in a number of tables, both in the browser's add-on manager and in supporting sites like AMO. If the browser sees an attempt to install an add-on which has an ID that matches an existing add-on, it treats that as an upgrade. Since pre-Jetpack add-ons don't have Simple Storage, and all add-ons are equally infinitely powerful, there's no notion of "should get access", so the difference between install and upgrade is less significant.

AMO keeps track of these IDs and tries to make sure they remain unique. There's nothing to stop a developer from creating an XPI that contains the ID of somebody else's add-on, but AMO can refuse to distribute an XPI that looks funny (and most users currently get their XPIs from AMO).

In particular, AMO has a table which associates a username+password (the "AMO credentials") with each add-on ID. AMO knows which bundle of code is the latest for each ID, and only allows updates from developers who demonstrate knowledge of the right credentials.

The browser also uses this ID to ask about upgrades. It asks each configured upgrade server (AMO by default, but there could be others) about a list of IDs, and the upgrade server returns an XPI bundle for each one. The browser believes whatever the upgrade server tells them.

Weaknesses of Pre-Jetpack IDs

The uniqueness of this pre-Jetpack ID is managed mostly by AMO. If an attacker compromised the AMO table, or the AMO login credentials of an existing developer, that attacker could replace an add-on with their own version. When a browser is configured to use additional upgrade servers beyond AMO, it becomes vulnerable to compromises in those sites as well. It is also vulnerable to the communication links to those servers: a failure in the CA infrastructure (such as a CA issuing a cert for addons.mozilla.org to someone outside of Mozilla) would hurt browsers just as badly as a compromise of AMO itself.

In addition, the lack of end-to-end security in these IDs means that AMO cannot use mirrors to share the burden of hosting add-on downloads: either the mirror sites would need access to AMO's private certificate (giving them too much authority), or the browser would have to accept unauthenticated HTTP access to the upgrade servers (making browsers vulnerable to a huge set of network-side attacks).

My hope is that new security mechanisms developed for Jetpack will be end-to-end, such that the security properties do not depend upon central services like AMO (i.e. AMO can improve security but cannot detract from it). The promise that we make to each developer ("your add-on's private state cannot be corrupted by other add-ons") should not need to be subject to conditions like ".. unless AMO or the CA infrastructure are compromised, or the user installs XPIs from beyond AMO".


Secure Jetpack IDs

(note: the 0.4 release does not implement signing or verification, merely key/ID generation)

In the new Jetpack world, each add-on is defined by a cryptographic keypair. (note: this is not really limited to Firefox add-ons: other "jetpack programs" will get secure IDs too). The developer signs the add-on with the private signing key. The public verifying key is shipped with the add-on, and is used to derive the secure "Jetpack ID". We use short ECDSA-256 keys, which (when base32-encoded) will look like this:

private-key 
private-jid0-7ezkmonxvtlkup43dhouwuib7izqypgwbgf3uzevujyto6npnxnq
public-key 
public-jid0-lo2q2zzstbo3gmwq3zgm4kaamiz7ebrm2iagp2vqmveae5traramxrdgzr6uadyt7r5ud2idpvj6vtcyhr3cnbfjuzncljlz43itoci
jid 
jid0-I1AF9BUoqgpQEorhKSRVp0CjA7U

The specifics:

  • The private key is an ECDSA secret exponent on the nist256p curve, expressed as a big-endian bytestring with no header or padding, then base32-encoded, lowercased, with trailing "=" trimmed. The result is given a "private-jid0-" prefix.
  • The public key is the public point, expressed as the concatenation of the X and Y coordinates (padded to be of equal and constant length) as big-endian bytestrings, with no additional header, then base32/lowercased/trimmed as above. The result is given a "public-jid0-" prefix.
  • The JID is computed by SHA256 hashing "jetpack-id-v0:" concatenated with the public key string (before base32 encoding), using the first 160 bits (20 bytes) of the hash output, base64 encoding that, trimming trailing "=", and replacing "+" with "A" and "/" with "B" (to make it URL- and path- safe). The result is given a "jid0-" prefix.
  • The add-on ID is computed by appending "@jetpack" to the JID, making it look enough like an email address work.

The XPI (or other distributable add-on bundle format) will be signed by the private key, and will contain a copy of the public key in an easy-to-extract location. So, given an XPI, it will be trivial to get the public key that it claims to use (and its JID), then check the signature against that key. XPIs which are not signed by their declared key are corrupt and will be ignored.

The question of whether "B" is an upgrade of "A" is then simply a matter of comparing the JIDs. The author of A (who signed it with a specific key) is entitled to give B (which is also signed by that same key) access to all saved state that was generated by A. This property is end-to-end, and independent of AMO, SSL, and the set of upgrade servers that the browser is using.

Each add-on gets a different keypair. The developer holds on to the private signing key themselves, although they may share it with FlightDeck, or store it on AMO for safekeeping if they wish (this adds AMO to the reliance set, but may be an appropriate tradeoff). If a team of developers collaborate to produce a given add-on, then all members of the team might hold a copy of the signing key (at least those members who need to generate the XPIs).

Note that this keypair and signature says nothing about the quality of the code, nor does it imply any review has taken place. The user (and AMO reviewers) will use entirely separate mechanisms to decide whether a given add-on is "safe" to run (i.e. whether the apparent benefits of running it outweigh the apparent risks). It merely asserts that a specific private key has been used to sign the code, making a (strong) claim of common origin between add-ons A and B. Code is not accepted just because it is signed: code gets access to a specific piece of persistent state because it is signed by the same key that signed the code which created that state in the first place.

Also note that the keypair says nothing about the name of the person who created the code. We don't care if the add-on was written by Alice, Bob, the Pope, or Dr. Evil: all we care about is whether the upgrade XPI was signed by the same key as the original XPI. In this sense, the JID keys are "anonymous".

The User Experience

When the developer creates a runnable bundle by using "cfx xpi", the cfx tool will check their package.json for completeness. If it is missing important items (like a JID), it will edit the file in-place to add the necessary fields (after making a backup, of course). This is the point at which a keypair is generated. Other fields may require user involvement (providing project name, author email, and other things which cannot be automatically generated), so an open question is whether this check should ask interactive questions of the user, or simply complain about incomplete fields (instructing the developer to fill in the missing pieces and then run the command again).

The package.json's "id" field will be set equal to the JID, which is a hash of the public verifying key. The private signing key will be written into ~/.jetpack/keys/$JID, in which the first line will be the privkey, and the remaining lines will contain the JID, pubkey, and the add-on's name. Developers can make a backup of all of their add-on's private keys by simply copying the files in this directory (cp -r ~/.jetpack /USBDRIVE/jetpack is a good backup strategy).

Eventually, cfx run/xpi will actually sign the generated XPI bundle with the private signing key. The bundle will also contain a copy of the verifying key (either in package.json, or as a separate item), so that anyone with the bundle can compute the signed-by keyid.

Starting From An Existing Add-On

Many (perhaps most) add-ons will begin life as a copy of somebody else's existing code. Given a tarball of add-on code (including the package.json file), what should the second developer see as they try to get it running for themselves?

The cfx run/xpi tool, doing its pre-flight checks, will notice that the pubkey embedded in the "id" field is not present in the developer's ~/.jetpack/keys directory, meaning that either the original developer has moved their source tree to a new machine, or that a subsequent developer has copied the source code. It will then explain to the developer that they either need to remove the "id:" property (so cfx run/xpi can generate a new one), or, if they're the original developer, need to copy the appropriate private key from some other machine.

Removing the "id:" property severs the connection between the old add-on and the new one.