BrowserID Key Wrapping: Difference between revisions

 
(24 intermediate revisions by 2 users not shown)
Line 3: Line 3:
== API and Overall Behavior ==
== API and Overall Behavior ==


A web site wants to have access to a securely stored cryptographic key bound to each of its users. That key should survive across multiple BrowserID logins, and be as stable as possible: if it is lost, the user's data that the site chooses to secure with this key will also be lost.
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 <em>not</em> store any data on behalf of the web site. Instead, it provides a wrapping/unwrapping API. The web site is expected to generate the user's key, wrap it via BrowserID, and store the wrapped key on its own servers.
BrowserID does <em>not</em> 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:
It goes like this:


  // web site logs the user in via BrowserID
    // obtain an ID assertion from BrowserID in the normal way
  navigator.id.get(gotAssertion);
   
 
    // generate a key
  function gotAssertion(assertion) {
     var key = generateKey();
     var key = generateKey();
    
    
Line 21: Line 20:
       },
       },
       function(error) {});
       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.
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, <tt>successCB</tt> is called as follows:
  successCB(keys, wrappedKeys);


=== Internal API ===
=== Internal API ===
Line 29: Line 46:
Before this is exposed as a content API, BrowserID exposes secret wrapping as an internal API:
Before this is exposed as a content API, BrowserID exposes secret wrapping as an internal API:


   navigator.id.internal.secret.wrap(origin, assertion, plainKey, successCB, failureCB)
   IDService.wrapSecret(origin, plainKey, cb)
   navigator.id.internal.secret.unwrap(origin, assertion, wrappedKey, successCB, failureCB);
   IDService.unwrapSecret(origin, wrappedKey, cb);


In this internal API, the <tt>origin</tt> has to be explicitly specified.
In this internal API, the <tt>origin</tt> has to be explicitly specified.
The single callback is done in a node style: first parameter is error, second parameter is result.


== Architecture ==
== Architecture ==


BrowserID generates a new key for each email address it verifies. We call this the user key.
=== 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 ===


BrowserID wraps this user key with a password-key derived from the user's password. When the user changes their password, the user-key is unwrapped and rewrapped appropriately. If the user loses their password completely, in the current specification, the user-key is unrecoverable and may as well be deleted.
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.


[diagram of wrapped keys]
=== 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 <tt>keytype=user</tt>. 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.
 
[[Image: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.
 
[[Image:browserid-keywrapping.png]]
 
For unwrapping, the reverse operation occurs. BrowserID checks that the unwrapper is the same origin as the wrapper:
 
[[Image: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 ==
== 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 ==
== 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});
Confirmed users
134

edits