Identity/AttachedServices/KeyServerProtocol

From MozillaWiki
< Identity‎ | AttachedServices
Revision as of 22:01, 27 June 2013 by Warner (talk | contribs) (→‎Email+Password -> SignToken: merge "sign" and "reset" token-fetching methods)
Jump to navigation Jump to search

PiCL Key Server / IdP Protocol

NOTE: This specification is under active development (27-Jun-2013). Several pieces are not yet complete. If you write any code based on this design, keep a close eye on this page and/or contact me (warner) on the #picl IRC channel to learn about changes. Eventually this will be nailed down and should serve as a stable spec for the PICL keyserver/IdP protocol.

Email+Password -> SignToken/ResetToken

The current stub just submits plaintext email+password and receives back (signToken, kA, wrap(kB)). It uses no key-stretching, nor SRP.

The full replacement uses key-stretching to transform the email+password into a "masterKey", then feeds this into an SRP protocol to get a session key. It uses this session key to decrypt a bundle of encrypted data from the keyserver, resulting in three values: kA, wrap(kB), and the signToken (or resetToken).

IdP Auth Big Picture

This same protocol is used, with slightly different methods and constants, to obtain the "resetToken".

The protocol is optimized to minimize round-trips and to enable parallelism. As a result, the two messages it sends (getToken1 and getToken2) each perform multiple jobs.

getToken1

As soon as the user finishes typing in the email address, the client should send it in the "getToken1" message to the keyserver. The response will include a set of parameters that are needed for key-stretching (described below), and the common parameters used by both sides of the SRP protocol to follow. These are simply looked up in a database entry for the client, along with an account-id. It must also include an allocated session-id that is used to associate this request with the subsequent getToken2 request. Finally, the response also includes the server's contribution to the SRP protocol ("srpB"), which is calculated on the server based upon a random value that it remembers in the session.

Proof-Of-Work

To protect the server's session table memory and CPU usage for the initial SRP calculation, the server might require clients to perform busy-work before calling getToken1().

(TBD)

Client-Side Key Stretching

The current stub does no stretching. It just performs a single HKDF operation, combining the user's email address, their password, and a "stretchSalt" retrieved from the server's getToken1() response.

Stretching KDF

A later version of the protocol will replace this with the PBKDF2+scrypt+PBKDF2 protocol described in Identity/CryptoIdeas/01-PBKDF-scrypt. This stretching is expected to take a second or two. The client can optimistically start this process (using default parameters) before receiving the getToken1() response, and then check that it used the right parameters afterwards (repeating the operation if not). (We'll want to build the stretching function with periodic checkpoints so that we don't have to lose all progress if the parameters turn out to be wrong). The "stretchSalt" is added *after* the stretching, to enable this parallelism (at a tiny cost in security).

After "masterKey" is derived, a second HKDF call is used to derive "unwrapKey" and "srpPW" which will be used later.

masterKey KDF

Client-Side SRP Calculation

(TBD) This.. is kind of crazy so far. The picture is incomplete and needs more detail.

The server should use Jed's SRP module from https://github.com/jedp/node-srp .

client-side SRP

The basic idea is that we're using the main-KDF output "srpPW" as a password for the SRP calculation. We use the email address for "identity", and a server-provided string for "salt". (We could safely leave them blank, since equivalent values are already folded into the password-stretching process, but it's less confusing to follow the SRP spec and fill them in with something sensible).

The SRP "g" (generator) and "N" (prime modulus) should use the 2048-bit value from RFC 5054 Appendix A, which is also used in SRP. Clients should not accept arbitrary g/N values (to protect against small primes, non-primes, and non-generators). In the future we might allow alternate parameter sets, in which case the server's first response should indicate which parameter set to use.

The server creates its "B" value according to the SRP protocol and includes it in the response to getToken1.

The client does its entire SRP calculation in a single step, after receiving the server's "B" value. It creates its "A" value, computes the shared secret S, and the proof-of-knowledge M1. It sends both "A" and "M1" in the same message (getToken2).

The server receives "A", computes the shared secret "S", computes M1, checks that the client's M1 is correct, then derives the shared session key K. It then allocates a token (of the requested type) and encrypts kA+wrap(kB)+token as described below, returning the encrypted/MACed bundle in the response to getToken2.

getToken2

This method has two flavors, one for obtaining "signing tokens", the other for getting "reset tokens". TBD: either we'll have two different method names / API endpoints (getToken2Sign and getToken2Reset), or we'll pass an argument to a single "getToken2" method that indicates either "sign" or "reset". (using different endpoints would make it easier to monitor server load).

The client-side SRP calculation results in two values that are sent to the server in the "getToken2()" message: "srpA" and "srpM1". "A" is the client's contribution to the SRP protocol. "M1" is an output of this protocol, and proves (to the server) that this client knew the right password.

The server feeds "A" into its own SRP calculation and derives (hopefully) the same "S" value as the client did. It can then compute its own copy of M1 and see if it matches. If not, the client (or a man-in-the-middle) did not get the right password, and the server will return an error and increment it's "somebody is trying to guess passwords" counter (which will be used to trigger defenses against online guessing attacks). If it does match, then both sides can derive the same "K" session key.

The server then allocates a token for this device, and encrypts kA/wrap(kB)/token with the session key. The server returns a success message with the encrypted bundle.

Future variants (e.g. to fetch a third kind of token) might put additional values in the response to getToken2.

Decrypting the getToken2 Response

The SRP session key ("srpK") is used to derive two other keys: respHMACkey and respXORkey.

Decrypting the signToken 1

The respXORkey is used to encrypt the concatenated kA/wrap(kB)/token string, by simply XORing the two. This ciphertext is then protected by a MAC, using HMAC-SHA256, keyed by respHMACkey. The MAC is appended to the ciphertext, and the whole bundle is returned to the client.

Decrypting the signToken 2

The client recomputes the MAC, compares it (throwing an error if it doesn't match), extracts the ciphertext, XORs it with the derived respXORkey, then splits it into the separate kA/wrap(kB)/token values.

Since the kA/wrap(kB)/signToken response is so similar to the kA/wrap(kB)/resetToken response, the same code can be used to check+decrypt both. However remember that the respXORkey/respHMACkey is derived differently for each (using different "context" values).

Signing Certificates

The current stub just submits (cert, signToken), and gets back a signed certificate. This will be replaced soon.

(TBD, future protocol)

The Sign Token is used to derive a "tokenID" and four additional keys:

  • request XOR key
  • request HMAC key
  • response HMAC key
  • response XOR key


The requestHMACkey is used in a HAWK (https://github.com/hueniverse/hawk/) request to provide integrity over the "signCertificate" request. It is used as credentials.key, while tokenID is used as credentials.id . HAWK includes the URL and the HTTP method ("POST") in the HMAC-protected data, and will optionally include the HTTP request body (payload) if requested.

Signing the Request

HAWK provides one thing: integrity/authentication for the request contents (URL, method, and optionally the body). It does not provide confidentiality of the request, or integrity of the response, or confidentiality of the response. We must provide these three other properties ourselves.

We might not need request confidentiality for signCertificate(). We do need it for resetAccount(). And we do need response confidentiality and integrity for both. To achieve these, the HAWK response is defined to be HMAC'ed (using responseHMACkey) and encrypted (XORed with the responseXORkey). XOR is safe and appropriate because the key is single-use and the data we're protecting is short and fixed-length.

Decrypting the Response

Resetting the Account

The current stub just submits (newPassword, wrap(kB), resetToken). This will be replaced soon.