JEP 118 - JetpackID
- Champion: Brian Warner - firstname.lastname@example.org
- Status: Under Review
- Bug Ticket:
Define a unique unforgeable "Jetpack ID" for each add-on, explain how it is generated and used.
Atul and I (warner) came up with the following plan. First I'll describe the state we want to get to, then I'll describe the sequence of steps through which we plan to get there.
Add-ons are defined by a sign+verify keypair. Each add-on gets its own keypair. Upgrades use the same keypair but higher version numbers: e.g. if "foo-v2.xpi" is meant to be an upgrade to "foo-v1.xpi", then both will be signed by the same key, but "foo-v2.xpi" will contain a version number that is higher than the one inside "foo-v1.xpi".
When a developer first starts working on an add-on, they run "cfx init". This:
- creates a keypair
- computes the "jetpack id" JID by hashing the public key into a shorter string
- stores the (private) signing key in a new file in ~/.jetpack/signing-keys/JID.key
- creates a new directory for the add-on code
- populates several skeleton/example files in the new directory. The skeleton "package.json" file is populated with a copy of the (public) verifying key, and the JID.
Later, when the developer is ready to build the add-on, they run "cfx build". This bundles the add-on code and resources into an XPI container, then signs this container with the right key.
AMO will use the JID as a primary key for add-ons (the JID will replace the existing easily-forged free-form add-on-ID). AMO can continue to maintain a table of authorized uploaders for each JID, rejecting uploads by users that are not on the list. But now, for each upload, AMO will unpack the container XPI and verify that it is signed by the JID-specified key. AMO will reject uploads that are not signed in this way: this indicates a corrupt container, or one which was signed by the wrong key.
The browser's add-on manager/loader will also check the signature. The AMO check is merely a courtesy to alert developers about signature problems early: the browser's check is the important one, which protects end users.
The JID will be available to the add-on code via the JEP105 introspection mechanism, with the following code:
let my_id = require("self").id;
This ID will be a printable string.
The first step, hopefully implementable by the 0.2 release, is to make a JID available to the add-on code (or the APIs which it uses), specifically to allow the JEP104 "Simple Storage" code to have a key that associates persistent data with a specific add-on. For this milestone, the JID will be generated randomly at "cfx build" time. No cryptographic code will be written or even defined. These JIDs will be forgeable and ephemeral, but at least browser-side code which needs JIDs can be written. Developers will be warned that any add-ons created in this release will be unable to provide persistent storage to future upgrades.
The second (baby) step is to implement "cfx init", and have it create a random JID. This will make JIDs persistent, and allow storage to traverse some upgrades, but will not be forwards-compatible with the final scheme.
The third step will be to decide upon a cryptographic signing scheme, and have "cfx init" generate real keys (and a real JID). This will make JIDs persistent and forwards-compatible (all subsequent upgrades should get the same JID, so persistent storage will be available, and tools like AMO can start usefully paying attention to the JID). However they'll still be forgeable.
The fourth step will be to implement signing in "cfx build", and verification in the browser's add-on manager/loader (and AMO's upload-processor). Once this step is done, JIDs will be unforgeable, and the process will be complete.
Several factors will influence our choice of cryptographic signing algorithm:
- availability of developer-side signing code. We'd like to retain the pure-python nature of the jetpack SDK, but this would require a pure-python implementation of some PK signing algorithm. It's ok if this is somewhat slow: signing the XPI can take a few seconds without slowing down a developer workflow too badly. If necessary, we could include binary eggs with a crypto library in the jetpack SDK, but we'd like to avoid it. Similarly, we could require developers to have /usr/bin/openssl or similar tool installed, and invoke it for signatures, but that would also be a usability loss.
- ability to specify signature/key serialization format concisely. The RSA/DSA-based signature schemes involve several parameters of variable length, leaving lots of wiggle room in the serialization format. We need to be able to clearly define exactly how a keypair is generated and serialized (and the pubkey hashed into the JID), so that alternate implementations can be written and interoperate correctly. Some algorithms (e.g. ECDSA, with smaller keysizes) lead to fewer format uncertainties, which may make it easier to specify in a way that future developers can match.
The use of a private long-term signing key raises additional questions:
- Can developers reliably maintain this key, safe and secure, in the long term? If they forget it, the JID is effectively frozen and un-updatable.. how much of a problem would this be? Could we build some sort of usable recovery mechanism?
- When using remote services like FlightDeck, how can the developer safely sign the add-on? Does the private signing key need to be given to the FlightDeck server?
Dependencies & Requirements
Capabilities Required (if applicable)
API Methods (if applicable)
- A list of add-ons and applications that would utilize this JEP
- Common actions within add-ons or applications that would be reliant on such functionality?