Identity/AttachedServices/KeyServerProtocol
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
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.
This same protocol is used, with slightly different 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 (getSignToken1 and getSignToken2) each perform multiple jobs.
getSignToken1
As soon as the user finishes typing in the email address, the client should send it in the "getSignToken1" 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 getSignToken2 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 getSignToken1().
(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 getSignToken1() response.
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 getSignToken1() 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.
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 .
The basic idea is that we're using the main-KDF output "srpPW" as a password for the SRP calculation, and leaving the other values ("identity" and "salt") blank, since they're already folded into the password-stretching process.
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. 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 getSignToken1.
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 (getSignToken2).
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 signToken and encrypts kA+wrap(kB)+signToken as described below, returning the encrypted/MACed bundle in the response to getSignToken2.
getSignToken2
The client-side SRP calculation results in two values that are sent to the server in the "getSignToken2()" 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 signToken for this device, and encrypts kA/wrap(kB)/signToken with the session key. The server returns a success message with the encrypted bundle.
Decrypting the getSignToken2 Response
The SRP session key ("srpK") is used to derive two other keys: respHMACkey and respXORkey.
The respXORkey is used to encrypt the concatenated kA/wrap(kB)/signToken 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.
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)/signToken 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.
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.
Resetting the Account
The current stub just submits (newPassword, wrap(kB), resetToken). This will be replaced soon.







