BrowserID Key Wrapping

From MozillaWiki
Jump to: navigation, search

When a user logs in to a web site directly, that web site has the user's password available to it, and it can use this password as a source of entropy for a cryptographic key. Once a web site uses BrowserID, that goes away. With BrowserID key wrapping, we're bringing this feature back. We're providing a way for web sites to get access to a cryptographic key based on the user's login.

API and Overall Behavior

A web site wants to perform client-side encryption of user data. We propose an API where a site can take a cryptographic key, wrap it with BrowserID in a way that can be later unwrapped. Wrapping should be keyed to a single email address, and should be as stable as possible: if the user changes password, unwrapping should continue to work. We accept that, if the user loses their BrowserID password altogether, the keys wrapped prior to the password reset will be lost.

BrowserID does not store any data on behalf of the web site. Instead, it provides a wrapping/unwrapping API. The web site is expected to generate a key for the user, wrap it via BrowserID, and store the wrapped key on its own servers.

It goes like this:

   // obtain an ID assertion from BrowserID in the normal way
   
   // generate a key
   var key = generateKey();
 
   // wrap the key
   navigator.id.secret.wrap(assertion, key,
     function(wrappedKey) {
       // store the wrappedKey on the server
     },
     function(error) {});

The key must be base64-encoded. The wrappedKey that is passed back is also base64-encoded. The plaintext is expected to be fairly short: mostly this is for wrapping other keys.

Content API

With the new BrowserID API that keeps track of whether user is logged in, we don't need to feed in the assertion anymore. But we do feed in the identity, in case there's a disconnect.

 navigator.id.secret.wrap(identity, plainKey, successCB, failureCB);
 navigator.id.secret.unwrap(identity, wrappedKey, successCB, failureCB);

We use the XHR approach, with one callback for success and one for failure.

The audience of the assertion must match the origin of the content, and a wrapped key can only be unwrapped by the same origin that wrapped it.

Since we don't want web sites to have to deal with their own key-generation process when we already have to solve that, we also introduce:

 navigator.id.secret.generateAndWrap(identity, successCB, failureCB);

If successful, successCB is called as follows:

 successCB(keys, wrappedKeys);

Internal API

Before this is exposed as a content API, BrowserID exposes secret wrapping as an internal API:

 IDService.wrapSecret(origin, plainKey, cb)
 IDService.unwrapSecret(origin, wrappedKey, cb);

In this internal API, the origin has to be explicitly specified.

The single callback is done in a node style: first parameter is error, second parameter is result.

Architecture

Crypto Preliminaries

We don't ever do raw encryption. We perform encryption-then-MAC. We do this by encrypting the plaintext, then HMACing the ciphertext. The decryption process first checks the HMAC, and decrypts only if it is valid. In the following description, every time we talk about a single key, we really mean two keys: one for encryption, and one for HMACing. We use AES in CBC mode for encryption, and HMAC-SHA256 for MACs.

Wrapping a key means encrypting (and HMACing) a data structure that contains the key and some optional tags. We denote this

 WRAP(wrapper_key, wrapped_key, {key1: val1, key2: val2})

Communication with BrowserID

In the diagrams below, communication is shown between the client-side of the web service and the BrowserID implementation in the client. If BrowserID is delivered by BrowserID.org, this communication is implemented over postMessage() between the service's origin and the BrowserID origin. If BrowserID is built into the user agent, this communication is simply the native DOM API defined above, with BrowserID implemented in the browser chrome.

User Key

BrowserID generates a new key for each email address it verifies. We call this the user key UK. BrowserID wraps UK with a password-key (PWK) tagged keytype=user. PWK is derived from the user's password using PBDKF2. When the user changes their password, UK is unwrapped and rewrapped appropriately with a new PWK'. If the user loses their password completely, in the current specification, UK is unrecoverable and may as well be deleted.

Browserid-userkeys.png

What this Means for BrowserID

BrowserID is now storing a (wrapped) user-key per email address. This functionality should move to the identity provider for that email address. BrowserID, as the secondary identity provider, will be the fallback, of course. This means we'll need to define an interface for this key-wrapping interface into an identity provider. We leave that specification to a later date.

Wrapping

Once a user key is established within BrowserID for a given email address, a site can generate a key SK, then ask BrowserID to wrap it. BrowserID will do so using UK, and will tag this wrapped key with the origin that requested the wrapping. This tagging is meant to ensure that unwrapping is done only by the domain that requested the wrapping in the first place.

Browserid-keywrapping.png

For unwrapping, the reverse operation occurs. BrowserID checks that the unwrapper is the same origin as the wrapper:

Browserid-keyunwrapping.png

What this Means for the Service

BrowserID is providing key wrapping and unwrapping, but as far as the service is concerned, BrowserID does not provide key storage. The service is expected to wrap any cryptographic material it needs in the client, using the BrowserID wrapping API, and then store the wrapped data (usually just a single key) on its own servers. For example, in the case of Firefox Sync, the user's sync key gets wrapped by BrowserID, and this wrapped key is then stored on the Sync servers themselves.

Security Considerations

The most significant issue implied by this proposal is that the security of the encryption now depends ultimately on the security of the user's passphrase.

It's worth noting that the data stored on the service (e.g. Sync) is not vulnerable to low-entropy passwords, because it is encrypted with a user-key. However, the wrapped user key stored on the BrowserID service may be vulnerable if the user's password is not sufficiently secure. Finding a good, usable way to obtain a secure password will be very useful.

Further Work

Currently, if the user loses their password and must reset it, the user key UK might as well be thrown away, and data encrypted against that key is thus lost for good. In future work, we may evaluate a key escrow mechanism by which the key can be recovered, somehow. This is considered out of scope for now.

If we do introduce key escrow, we would likely augment the API to explicitly allow escrow for certain keys, and not others:

 // defaults to false
 navigator.id.secret.wrap(assertion, plainKey, successCB, failureCB, {allowEscrow: true});
 navigator.id.secret.wrap(assertion, superSecretPlainKey, successCB, failureCB, {allowEscrow: false});