encryption keys and the oauth flow

Ryan Kelly rfkelly at mozilla.com
Wed Dec 17 02:44:11 PST 2014


Hi All,


A limitation of the current FxA OAuth flow is that OAuth reliers cannot 
get access to the user's encryption keys.

This means that we could not have build new-sync atop our OAuth 
infrastructure.  I've also heard from at least two potential new 
services that would like to do encryption of user data.

Let's try to figure out a way to enable this.

Below is a concrete proposal based on discussions I've had with a few 
folks.  It's intended as a starting point for discussion.  I don't think 
we can realistically ship anything like this before Q2, but it would be 
good to reach a rough consensus on approach so that people can starting 
hacking around with the ideas.

It's pretty long, so I recommend stopping here if you're not interested 
OAuth or encryption keys...



A brief recap of the current situation:
=======================================

Your Firefox Account comes with a pair of encryption keys, "kA" and 
"kB".  The first is known to the server and designed for encrypting data 
that must be recoverable even after a password reset.  The second is 
derived from the user's password and hence secret even from Mozilla, but 
will not survive a password reset event.

Sync currently encrypts your stored data using a derivative of kB.

To obtain these keys, you must know the user's account password and you 
must perform some special extra steps at login time.  There is no way to 
fetch either key without having the account password.

Since OAuth reliers never get to see the user's password, there is 
currently no way for them to take advantage of these keys.

Starting premises:

   * OAuth reliers should be able to obtain encryption keys.

   * OAuth reliers must not be able to access the raw values of kA or kB,
     but may obtain derivatives of them with user consent.

   * FxA servers must never learn kB or anything derived from it.

   * OAuth relier servers should be able to avoid learning anything
     derived from kB (although we cannot prevent them from deliberately
     sending the keys from client to server).

   * And of course, OAuth reliers must never have access to the user's
     account password, or anything that can be used to guess it.


Any amendments or additions to this list?



A proposal for scoped encryption keys:
======================================

There will be two different types of derived encryption key in our 
service ecosystem.


1) Relier-specific keys

Each OAuth relier should be able to obtain a pair of derived keys 
kAr/kBr that are private to that relier.

   kAr = HKDF(kA, "identity.mozilla.com/picl/v1/keys/relier/<client-id>")
   kBr = HKDF(kB, "identity.mozilla.com/picl/v1/keys/relier/<client-id>")

The use-case here is an external service that wants to do encryption of 
its own private data, such as an FxA/daybed.io integration.


2) Service-specific keys

Each OAuth service provider should be able to have a derived key that is 
specific to that service, and is provided to any relier that is granted 
access to the service.

As a concrete example, suppose that the readinglist service wants to 
encrypt some fields in the data stored on its server.  As a matter of 
policy, we decide that  we can tolerate loss of such data in the event 
of a password reset, so we define the key for scope "readinglist" as:

   kS = HKDF(kB, "identity.mozilla.com/picl/v1/keys/service/readinglist")

Any relier that is granted access to scope "readinglist" should also be 
able to get this derived key, in order to properly access the data 
stored in the service.


These two types completely define the encryption keys available to a 
relier.  During a key-enabled oauth flow they should somehow receive:

   * the relier-specific kAr and kBr
   * any scope-specific keys defined for the scopes they are granted


Any use-cases not covered by one of these two types of derived key?



A key-fetching OAuth dance:
===========================

The real trick, of course, is safely deriving these keys and delivering 
them to the relier.  I posit that this must be done as part of the OAuth 
dance.  The content-server is the only thing that can prompt the user 
for their password in order to derive the keys, and the content-server 
only gets involved during the OAuth dance.

We have a couple of options here, but I'll sketch out the one I like 
best (large chunks of which are thanks to Alexis):


1) When initiating the OAuth dance, the relier generates a 
public/private keypair, stores the private key in its application state, 
and includes the public key in the OAuth authorization request:

   GET /v1/authorization?client_id=AAA&scope=readinglist&keyfetch=PUBKEY


2) We're redirected to the content-server OAuth page, which does its 
usual solicitation of user permission, prompts for the user's password 
if necessary to obtain kA and kB, and derives the appropriate scoped 
encryption keys.


3) The content-server encrypts the derived keys using the supplied 
pubkey and includes them when provisioning the OAuth token:

   POST /v1/authorization
   ==>
   {
     "client_id": "AAA",
     "scope": "readinglist",
     "keys": {
       "relier": [encrypt(kAr, pubkey), encrypt(kBr, pubkey)],
       "readinglist": encrypt(kS, pubkey)
     }
   }
   <==
   {
     "code": "XXX"
   }


4) We're redirected back to the relier's registered redirect_uri, where 
they retrieve the granted token and encrypted encryption keys:


   POST /v1/token
   ==>
   {
     "code": "XXX"
   }
   <==
   {
     "access_token": "blahblah",
     "token_type": "bearer",
     "scope": "readinglist",
     "keys" {
       "relier": [encrypt(kAr, pubkey), encrypt(kBr, pubkey)],
       "readinglist": encrypt(kS, pubkey)
     }
   }

The relier can use its private key to decrypt the encryption keys and 
use them as it sees fit.


5) Profit.


Points to note:

   * The FxA servers never see any of the keys, because they transit
     the server in encrypted form.

   * The relier can generate and manage its keyfetch keypair in
     client-side code, to avoid learning the keys server-side.

   * This flow does not depend on persistent state on the client, all
     state can be tunneled through URL params in the OAuth flow.

   * There's a whole lot of potential for crypto to be done wrong
     here!  It's pretty scary stuff and smells kinda fragile.


I'd love to avoid public-key crypto here...perhaps there's a way for the 
content-server to generate a symmetric encryption secret and communicate 
it directly back to the relier without it transiting our servers?

Chris also suggested that the encryption keys may not need to transit 
the server at all, but could instead be communicated from content-server 
to relier via a client-side postMessage API.  I don't know much about 
postMessage but it sounds worth exploring.

Details aside, will this sort of enhanced OAuth dance give us what we want?

Does this sound appropriately terrifying to everyone?



  Cheers,

     Ryan




More information about the Dev-fxacct mailing list