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

_______________________________________________
Dev-fxacct mailing list
[email protected]
https://mail.mozilla.org/listinfo/dev-fxacct

Reply via email to