https://wiki.mozilla.org/api.php?action=feedcontributions&user=Warner&feedformat=atomMozillaWiki - User contributions [en]2024-03-28T22:40:53ZUser contributionsMediaWiki 1.27.4https://wiki.mozilla.org/index.php?title=Identity/AttachedServices/KeyServerProtocol&diff=991148Identity/AttachedServices/KeyServerProtocol2014-06-22T18:16:18Z<p>Warner: /* Creating The Account */ srpV is sent to /account/create too</p>
<hr />
<div>= PiCL Key Server / IdP Protocol =<br />
<br />
NOTE: This specification is slowly converging on stability (31-Jul-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.<br />
<br />
The server is being developed in https://github.com/mozilla/picl-idp . This repo currently includes a demonstration client (node.js CLI).<br />
<br />
Note that all messages are delivered over an HTTPS connection. The client browser may also implement cert-pinning to improve on the certificate validation process. The protections described below are in addition to those provided by TLS.<br />
<br />
Remaining TODO items:<br />
<br />
* decide on client-side key-stretching parameters: http://keywrapping.appspot.com can help<br />
* finalize SRP questions (definition of M1, generation of a/b)<br />
* finalize proof-of-work/DoS-prevention details<br />
* decide how to rate-limit account-creation calls<br />
* confirm this is actually implementable inside Firefox (especially w.r.t. NSS and Android/Java crypto)<br />
<br />
= Creating The Account =<br />
<br />
The first act performed by a user is to create the account. They enter email+password into their browser, which then does the following steps:<br />
<br />
* decide upon stretching parameters (perhaps consulting the keyserver for recommendations, but imposing a minimum strength requirement). For now we used a fixed {firstPBKDF:20000, scrypt: {N:65536, r:8, p:1}, secondPBKDF:20000}.<br />
* randomly choose a 32-byte mainSalt (this should be unique, but is not secret)<br />
* choose the SRP group parameters (fixed: use the 2048-bit group described below)<br />
* perform key-stretching (as described below), derive stretchedPW and srpPW<br />
* randomly choose a 32-byte srpSalt (unique, but not secret)<br />
* create srpVerifier from srpPW and srpSalt (as described below)<br />
* deliver (email, stretchParams, mainSalt, srpParams, srpSalt, srpVerifier) to the keyserver's "POST /account/create" API<br />
<br />
The server, when creating a new account, creates both kA and wrap(kB) as randomly-generated 256-bit (32-byte) strings. It stores these, along with all the remaining values, indexed by email, in the account table where they can be retrieved by getToken later.<br />
<br />
== Email Verification ==<br />
<br />
To prevent fixation attacks, we require new accounts to verify their configured recovery email address before letting them learn the generated keys or obtain a signed certificate. Nevertheless, we wish clients to forget the user's password while they wait for email verification to complete. To achieve this, clients can obtain a sessionToken before verification, but most APIs that require it will raise errors until verification is finished.<br />
<br />
The server will send email with a URL that contains a long random "verification code" in the "fragment" hash. This URL points to a static page with some javascript that submits the code to the "POST /account/recovery_methods/verify_code" API. The URL can be clicked by any browser (it is not bound to anything), and when the API is hit, the account is marked as verified.<br />
<br />
After the client submits /account/create, it performs the "/session/auth" login sequence below to obtain a sessionToken. It then polls the "GET /account/recovery_methods" (which requires a sessionToken but not account verification) until the user clicks the email link and the API reports verification is complete. Then the client uses "GET /account/keys" and "POST /certificate/sign", described below, to obtain kA, kB, and a signed certificate to talk to the storage server.<br />
<br />
= Login: Obtaining the authToken =<br />
<br />
To connect a browser to an existing account, we use the following login protocol to transform an email+password pair into a single-use authToken. We will use this in the next section to create a session, from which we can obtain encryption keys and signed certificates.<br />
<br />
This protocol starts by using key-stretching to transform the email+password into a "stretchedPW", then feeds this into an SRP protocol to get the authToken.<br />
<br />
[[File:PICL-IdPAuth-auth-start.png|IdP Auth Protocol]]<br />
<br />
This authToken can be used (once) to do one of the following:<br />
<br />
* /session/create: obtain a sessionToken (and keyFetchToken), which enables storage server access<br />
* /password/change: obtain an accountResetToken, to safely reset the account password<br />
* /account/delete: to delete the entire account<br />
<br />
The protocol is designed to enable parallelism between key-stretching and the initial network messages, to reduce the time it takes to connect a browser to the account. In total, the browser requires five messages in four roundtrips (1: /auth/start, 2: /auth/finish, 3: /session/create, 4: /account/keys and /certificate/sign in parallel) before it is ready to talk to the storage server.<br />
<br />
== /auth/start ==<br />
<br />
As soon as the user finishes typing in the email address, the client should send it in the "/auth/start" 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 loginSRPToken that is used to associate this request with the subsequent /auth/finish 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.<br />
<br />
To mitigate DoS abuse, /auth/start may also require a proof-of-work string, described below.<br />
<br />
== Client-Side Key Stretching ==<br />
<br />
"Key Stretching" is the practice of running a password through a computationally-expensive one-way function before using it for encryption or authentication. The goal is to make brute-force dictionary attacks more expensive, by raising the cost of testing each guess.<br />
<br />
To protect the user's class-B data against compromises of our keyserver, we perform this key stretching on the client, rather than on the server. We use the memory-hard "scrypt" function (pronounced "ess-crypt") for this purpose, as motivated by the attacker-cost studies in [[Identity/CryptoIdeas/01-PBKDF-scrypt]]. Slower (low-RAM) devices can still participate by outsourcing the scrypt step to an "scrypt helper". To provide at least minimal protection against a compromise of this helper, we sandwich the scrypt step between two PBKDF2 steps. The complete protocol looks like this:<br />
<br />
[[File:PICL-IdPAuth-stretch-KDF.png|Stretching KDF]]<br />
<br />
Our initial parameter choices for this stretching are to use 20000 iterations of PBKDF2 on each side (40k in all), and to run scrypt with N/r/p set to 64k/8/1. This requires roughly 60MB of RAM for about 1.3 seconds on a 1.8GHz Intel Atom linux box (it will run faster, but use the same memory, on more modern CPUs). We are still studying performance on various platforms to decide what parameters to use in V1: lowering them speeds up the login process, but reduces security (by reducing the cost of an dictionary attack).<br />
<br />
Since the stretching is expected to take a second or two, the client can optimistically start this process (using default parameters) before receiving the auth/start 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 "mainSalt" is added *after* the stretching, to enable this parallelism (at a tiny cost in security).<br />
<br />
After "stretchedPW" is derived, a second HKDF call is used to derive "srpPW" and "unwrapBKey" which will be used later.<br />
<br />
[[File:PICL-IdPAuth-main-KDF.png|masterKey KDF]]<br />
<br />
== SRP Protocol Details ==<br />
<br />
The PiCL client uses the SRP protocol (http://srp.stanford.edu/) to prove that it knows the (stretched) account password without revealing the actual password (or information enabling a brute-force attack) to the server or any eavesdroppers. <br />
<br />
SRP is somewhat underspecified. We use SRP-6a, with SHA256 as the hash, and the 2048-bit modulus defined in RFC 5054 Appendix A. We consistently zero-pad all string values to 256 bytes (2048 bits), and use H(A+B+S) as the key-confirmation message "M1". These details, plus the SRP design papers and RFCs 2945 and 5054, should be enough to build a compatible implementation. The diagrams below and the test vectors at the end of this page can be used to verify compatibility.<br />
<br />
The server should use Jed's SRP module from https://github.com/jedp/node-srp . The client might use SJCL (http://crypto.stanford.edu/sjcl/) or native code (NSS).<br />
<br />
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).<br />
<br />
Note that SRP-6a uses a "k" value which basically encodes the group being used ("N" and "g"). Since all PICL accounts use the same 2048-bit group, they will all use the same "k" value (not to be confused with the per-session shared-secret "K" key that emerges from the protocol). This group's "k" integer is (as a base-10 number):<br />
2590038599070950300691544216303772122846747035652616593381637186118123578112<br />
<br />
=== SRP Verifier Calculation ===<br />
<br />
When the client first creates the account, it must combine the account email address, the stretched password (srpPW), and a randomly-generated srpSalt, to compute the srpVerifier. The server will use this verifier later, to check whether or not the client really knows the password.<br />
<br />
If the server is compromised and an attacker learns the srpVerifier for a given account, they cannot use this to directly log in (the verifier is not "password-equivalent"), but it does allow them to perform an offline brute-force attack against the user's password. In this respect, it is similar to a traditional hashed password. We make these attacks somewhat more expensive by performing the client-side stretching described above, instead of using the raw user password in the SRP calculation.<br />
<br />
[[File:PICL-IdPAuth-SRP-Verifier.png|client-side SRP Verifier calculation]]<br />
<br />
Test vectors are provided at the end of this page. The protocol requires that several values ("a", "b", "srpSalt") are chosen randomly, but for illustrative purposes, the test vectors use carefully-crafted non-random values. For the user's sake, please ensure implementations use proper random values instead of simply copying the test vectors.<br />
<br />
The client sends srpSalt and srpVerifier to the server when it creates the account. It will also re-compute the 'x' value (as an integer) during sign-in. The server will convert the srpVerifier string back into an integer ('v') for use during its own sign-in calculations.<br />
<br />
=== SRP Server-side Sign-In Flow ===<br />
<br />
When the user connects a new device to their account, they use the /auth/start API to start the SRP protocol. This sends the account email address to the server. The server looks up the stored srpVerifier for this account, creates a random 'b' integer, performs some math to compute the "B" number, then converts B into a string known as "srpB". "srpB" is returned to the client, along with srpSalt and the key-stretching parameters. "b" and "srpB" are retained for the subsequent /auth/finish call. '''Note that it is critical that the "b" integer remain secret on the server.''' The server also allocates a random loginSRPToken to connect the two calls.<br />
<br />
[[File:PICL-IdPAuth-SRP-Server.png|server-side SRP]]<br />
<br />
Later, /auth/start will be called with the client's srpA string and its M1 key-confirmation string. srpA is combined with srpB to calculate the "u" integer. srpA is also turned into an integer and used to compute the shared-secret "S" integer. S is then used to compute the shared key "srpK", which is the output of the SRP process.<br />
<br />
=== SRP Client Calculation ===<br />
<br />
While the client is waiting for the response to /auth/start, it begins its key-stretching calculations. Everything else must wait until the response to /auth/start arrives, which includes the key-stretching parameters (which are retroactively confirmed), srpSalt, and the server's generated srpB value.<br />
<br />
[[File:PICL-IdPAuth-SRP-Client.png|client-side SRP]]<br />
<br />
Once the client knows srpSalt, it computes the same "x" integer as it did in the middle of the srpVerifier calculation. It also converts srpB into an integer named "B". Then it creates a random "a" integer, uses it to compute the string "srpA", then combines srpA with the server's srpB to compute the "u" integer. It then combines the static "k", the password-derived "x", the combined "u", and the server's "B", together with some magic math, to derive the "S" integer. If everything went well, the client will compute the same "S" value as the server did. If not (the password was wrong, or the client is talking to a fake server that doesn't really know srpVerifier), then the two "S" values will not match.<br />
<br />
('''Again, it is critical that the client keep its "a" and "x" integers secret, both during and after the protocol run.''')<br />
<br />
To safely tell if the "S" values match, both client and server combine srpA, srpB, and their (independently) generated "S" strings to form a string named "M1". The client sends M1 (along with srpA) in the /session/auth/finish message. The server compares the client's copy of M1 against its own. If they match, the client knew the password and the server can safely respond with the encrypted account data. If they do not match, the client (or a man-in-the-middle attacker) did not know the password, and the client should increment a counter that can trigger defenses against online guessing attacks. The server must then return an error to the client, and '''not''' use or reveal srpK (or the correct M1) in any way.<br />
<br />
Both client and server also hash "S" into "srpK". This is the shared session key, from which specific message encryption and MAC keys are derived (as described below).<br />
<br />
=== SRP Notes ===<br />
<br />
The SRP "g" (generator) and "N" (prime modulus) should use the 2048-bit value from RFC 5054 Appendix A. 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.<br />
<br />
There are several places in SRP where integers (usually in the range 1..N-1) are converted into bytestrings, either for transmission over a wire, or to be passed into a hash function. The SRP spec is somewhat ambiguous about padding here: if the integer happens to be less than 2^2040, the simplest toString() approach will yield a '''255''' byte string, not a 256 byte string. PiCL consistently uses padding, so compatible implementations must prepend one or more NUL bytes to these short strings before transmission or hashing. The test vectors below were brute-forced to ensure that "srpVerifier", "srpA", "srpB", and "S" all wind up with leading zeros, to exercise the padding code in compatible implementations. If you are having problems getting your code to match these results, add some assertions to test that the stringified integers being put into hashes are exactly 256 bytes long.<br />
<br />
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 (/auth/finish).<br />
<br />
The server receives "A" in /auth/finish, 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 keyFetchToken+sessionToken as described below, returning the encrypted/MACed bundle in the response to /auth/finish.<br />
<br />
On the server, it is critical to reject an "A" value that is 0, or some other multiple of N. If the server does not check this, anybody can trivially sign in to any account without knowing the password. Likewise, it is critical for the client to reject a "B" value where B%N==0. If the client does not check this, the server (or an attacker pretending to be the server) will get a value that can be used in an offline brute-force search for the user's password.<br />
<br />
Outstanding crypto questions:<br />
<br />
* How exactly should the "a" and "b" integers be generated? The issue is of how much bias SRP can tolerate. Ideally these integers are uniformly distributed from 1 to N-1 (inclusive). The only way to obtain a purely uniform distribution from a source of random bytes is try-try-again: pick a (integral-number-of-bytes) number, compare it to the desired range, try again if it falls outside the range. If you get unlucky, this can take a lot of guesses, depending upon how close the range is to a power of two. ECDSA implementations tend to pick a number twice as long as the modulus and then modulo it down (rand(2^4096) % N), which yields a tiny fraction of a bit of bias. The cheaper approach is to do the same with a number of equal length (rand(2^2048) % N), which imposes more bias. Some SRP implementations appear to be satisfied by rand(2^256). We need more review here.<br />
* The original SRP papers defined M1=H(A+B+S) as we use here, but other implementation (in particular RFC2945) uses a curious construct that includes the username, the salt, and an odd XOR combination of N and g. We need to decide what to use for M1.<br />
<br />
== /auth/finish ==<br />
<br />
The client-side SRP calculation results in two values that are sent to the server in the /auth/finish 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.<br />
<br />
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.<br />
<br />
The server then allocates a single-use 32-byte random token named "authToken". It encrypts the token with the session key, and returns a success message with the encrypted bundle.<br />
<br />
All tokens have an associated tokenID, described below. The server needs to maintain a table that maps the tokenID to the token itself, so it can derive other values from the token later. The tokens are also associated with a specific account, so later API requests do not specify an email address or account ID.<br />
<br />
== Decrypting the /auth/finish Response ==<br />
<br />
The SRP session key ("srpK") is used to derive two other keys: respHMACkey and respXORkey.<br />
<br />
[[File:PICL-IdPAuth-encrypt-authToken.png|Decrypting the authToken]]<br />
<br />
The respXORkey is used to encrypt the authToken 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.<br />
<br />
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 returns the authToken value.<br />
<br />
= After Login: Using the authToken =<br />
<br />
After the authToken is acquired, the client can create a session and fetch the encryption keys. The high-level flow looks like this:<br />
<br />
[[File:PICL-IdPAuth-session-start.png|Using the authToken]]<br />
<br />
= Creating a Session =<br />
<br />
For login, the single-use authToken is spent on a call to /session/create .<br />
This allocates two new (random 32-byte) tokens: a long-lived "sessionToken", and a single-use "keyFetchToken". The /session/create call returns an encrypted bundle containing the two tokens.<br />
<br />
[[File:PICL-IdPAuth-encrypt-sessionToken.png|Decrypting the sessionToken and keyFetchToken]]<br />
<br />
For calls which accept an authToken, the client uses authToken to derive three values:<br />
<br />
* tokenID<br />
* reqHMACkey<br />
* requestKey<br />
<br />
<br />
The client uses tokenID and reqHMACkey for a HAWK (https://github.com/hueniverse/hawk/) request to the "POST /session/create" API, using tokenID as "credentials.id" and reqHMACkey as "credentials.key". The server uses tokenID to look up the corresponding token, then derives reqHMACkey to validate the request.<br />
<br />
Each authToken-using call then derives additional API-specific values from requestKey. /session/create uses two derived values:<br />
<br />
* respHMACkey<br />
* respXORkey<br />
<br />
<br />
When the server receives a valid /session/create request, it allocates sessionToken and keyFetchToken, concatenates them, encrypts the pair by XORing it with the derived respXORkey, and attaches a MAC generated with respHMACkey. The encrypted MACed bundle is returned to the client.<br />
<br />
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 keyFetchToken and sessionToken values.<br />
<br />
Each authToken is single-use: once a successful request has been made with it, the authToken and its corresponding ID is removed from the server's memory, and subsequent attempts to use it will return a "no such token" error. The token is consumed even if the request fails (e.g. the MAC did not match).<br />
<br />
The authToken can be used (once) by multiple APIs. The server only needs to maintain one table mapping tokenID to authToken, because all these APIs share the same method of deriving a tokenID and reqHMACkey from the authToken.<br />
<br />
When a HAWK request appears, it should look up the tokenID in this table, retrieve the authToken, recompute reqHMACkey and requestKey, validate the request, delete the table entry, then hand the HTTP request and requestKey to the specific API handler. That handler should derive the API-specific values (respHMACkey, respXORkey, etc) to process the request or construct the response.<br />
<br />
The server can support multiple sessions per account (typically one per client device, plus perhaps others for account-management portals). There can also be multiple outstanding keyFetchTokens. The sessionToken lasts forever (until revoked by a password change or explicit revocation command), and can be used an unlimited number of times. The keyFetchToken expires after 60 seconds, and is single-use.<br />
<br />
= Obtaining keys kA and kB =<br />
<br />
Clients which have exchanged an authToken for either a sessionToken or an accountResetToken will also receive a keyFetchToken. This single-use token allows the client to retrieve kA and wrap(kB), which enables the client to encrypt and decrypt browser data (bookmarks, open-tabs, etc) correctly. As above, the keyFetchToken is used to derive tokenID, reqHMACkey, respHMACkey, and respXORkey, which are used in a HAWK request to the "GET /account/keys" API.<br />
<br />
The server pulls kA and wrap(kB) from the account table, concatenates them, encrypts the pair by XORing it with the derived respXORkey, and attaches a MAC generated with respHMACkey.<br />
<br />
[[File:PICL-IdPAuth-keys-server.png|keyFetchToken: server encrypts keys]]<br />
<br />
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 and wrap(kB) values.<br />
<br />
[[File:PICL-IdPAuth-keys-client.png|keyFetchToken: client decrypts keys]]<br />
<br />
Finally, the server-provided wrap(kB) value is simply XORed with the password-derived unwrapBKey (both are 32-byte strings) to obtain kB. There is no MAC on wrap(kB).<br />
<br />
[[File:PICL-IdPAuth-key-unwrap.png|unwrapping kB]]<br />
<br />
"kA" and "kB" enable the browser to encrypt/decrypt synchronized data records. They will be used to derive separate encryption and HMAC keys for each data collection (bookmarks, form-fill data, saved-password, open-tabs, etc). This will allow the user to share some data, but not everything, with a third party. The client may intentionally forget kA and kB (only retaining the derived keys) to reduce the power available to someone who steals their device.<br />
<br />
Note that /account/keys will not succeed until the account's email address has been verified. Also note that each keyFetchToken is single-use and short-lived. The token is consumed even if the request fails (e.g. the MAC does not match).<br />
<br />
= Signing Certificates =<br />
<br />
The sessionToken is used to derive two values:<br />
<br />
* tokenID<br />
* request HMAC key<br />
<br />
<br />
[[File:PICL-IdPAuth-use-session.png|Using the sessionToken, signing certificates]]<br />
<br />
The requestHMACkey is used in a HAWK request to provide integrity over many APIs, including /certificate/sign. requestHMACkey 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.<br />
<br />
For /certificate/sign, it is critical to enable payload verification by setting options.payload=true (on both client and server). Otherwise a man-in-the-middle could submit their own public key, get it signed, and then delete the user's data on the storage servers.<br />
<br />
The following keyserver APIs require a HAWK-protected request that uses the sessionToken. In addition, some require that the account be in the "verified" state:<br />
<br />
* GET /account/devices<br />
* POST /session/destroy<br />
* GET /recovery_email/status<br />
* POST /recovery_email/resend_code<br />
* POST /certificate/sign (requires "verified" account)<br />
<br />
= Resetting The Account =<br />
<br />
The account may be reset in two circumstances: when the user changes their password, and when the user forgets their password. In both cases, the client first obtains an "accountResetToken". This token is then used to change the SRP Verifier and either reset or replace the wrap(kB) value.<br />
<br />
== Changing the Password ==<br />
<br />
When the user wishes to change their password (i.e. they still know the old password), they first use the /auth/start+/auth/finish SRP protocol safely obtain an "authToken". They they use the "/password/change/start" API to exchange the authToken for an accountResetToken and a keyFetchToken.<br />
<br />
[[File:PICL-IdPAuth-encrypt-passwordChange.png|Server encrypts passwordChange response]]<br />
<br />
The accountResetToken will be used below to set the new password safely. The keyFetchToken should be used first, to obtain kB, so the subsequent account reset can replace wrap(kB) with a new value. This allows the password-changing client to retain their class-B data.<br />
<br />
Requiring an authToken proves that the user has provided the correct account password recently. When the account is reset, all active sessions and tokens will be cancelled (disconnecting all devices from the account). The client should immediately establish a new session as described above.<br />
<br />
This API is only used when the user knows their old password: if they have forgotten the password, use the "/password/forgot" APIs below.<br />
<br />
== Handling a Forgotten Password ==<br />
<br />
When the user has forgotten their password, they can use one of their "recovery methods" to obtain an accountResetToken. For now, this means we send a random code to the email address associated with their account. The user must copy this code from the email into their client, whereupon the client will get an accountResetToken that can be delivered to the API below.<br />
<br />
Note that, since the forgotten-password client never learns kB, any class-B data will be lost. This is necessary to protect class-B data from attackers who can read the users's email but do not know the account password (including those who compromise the IdP and the keyserver itself). When using /account/reset below, they will set wrap(kB) to a string of all zeros, which means the server should generate a new random wrap(kB) (just as it does during account creation).<br />
<br />
The /password/forgot/send_code API is used to ask the server to send a recovery code. This takes a recovery method, which for now is just an email address. This API is unauthenticated (after all, the user who has forgotten their password knows nothing but their email address). The server marks the corresponding account as "pending recovery", allocates a random forgotPasswordToken for the account, creates a recovery code, and sends the code (with instructions) via email. The API returns forgotPasswordToken to the client.<br />
<br />
The user must copy the recovery code into the same browser where they started the process. The client then submits the code to /password/forgot/verify_code along with the forgotPasswordToken they received. If they match, the server allocates a accountResetToken and returns it to the client. If they do not match, the server increments a counter (which is used to decide if an online guessing attack is happening).<br />
<br />
forgotPasswordToken can be used three times before it is exhausted. If the user guesses incorrectly this often, the client must call send_code again to get a new token and code. Each account has at most one token+code active at a time.<br />
<br />
The recovery code is initially a random 8-digit decimal number. If an attacker tries to sign in as someone else, hits the "forgot my password" button, then submits a guess to /password/forgot/verify_code, they will have a 1-in-100-million chance of success. If the server detects too many wrong guesses, it should increase the length of new codes. Another defensive technique is to require that users click an email link before being given the code: the server is told when the link is clicked, so the code will not be enabled until the email has been read. It remains to be seen whether this will be sufficient.<br />
<br />
The exact thresholds are TBD, but a nominal goal is to keep the chances of any attack succeeding to below 1-in-a-million per year. To achieve this, we can tolerate 100 verify_code failures in a single year before we must increase the length of the code.<br />
<br />
== Using accountResetToken ==<br />
<br />
An accountResetToken, obtained through either of the methods above, is used to invoke the /account/reset API. If the request is accepted, the server replaces the account password with a new one, updates the wrap(kB) value, cancels all active sessions and tokens (disconnecting all devices from the account), and sends a "your password has been changed" email to the user.<br />
<br />
If the client knew the old password, it can supply a new wrap(kB) value that will yield the same kB key as before, so no data will be lost. If the client did *not* know the old password, then it will supply a wrap(kB) of all-zeros, which tells the server to generate a new random wrap(kB) (just like during account creation), which means the class-B data will be lost.<br />
<br />
The client puts their new password through the same stretching procedure as described in the new-account section above, resulting in a new srpVerifier and unwrapBKey. If they knew the old kB, they XOR it with the new unwrapBKey to obtain a new wrap(kB).<br />
<br />
/account/reset needs request confidentiality, since the arguments include the newly wrapped kB value and the new SRP verifier, both of which enable a brute-force attack against the password. HAWK provides request integrity. The response is a single "ok" or "fail", conveyed by the HTTP headers, so we do not require response confidentiality, and can live without response integrity.<br />
<br />
So the single-use resetToken is used to derive three values:<br />
<br />
* tokenID<br />
* request HMAC key<br />
* request XOR key<br />
<br />
<br />
[[File:PICL-IdPAuth-encrypt-resetAccount.png|Client encrypts resetAccount request]]<br />
<br />
The request data will contain wrap(kB) and the new SRP verifier, concatenated together. Since we always pad the SRP verifier to the full (256-byte) group length, both pieces are fixed-length. We generate enough reqXORkey bytes to cover both values.<br />
<br />
The request data is XORed with requestXORkey, then delivered in the body of a HAWK request that uses tokenID as credentials.id and requestHMACkey as credentials.key . Note: it is critical to include the request body in the HAWK integrity check (options.payload=true, on both client and server), otherwise a man-in-the-middle could substitute their own SRP verifier, giving them control over the account (access to the user's class-A data, and a brute-force attack on their password).<br />
<br />
The client submits other values in the same request:<br />
<br />
* stretchParams<br />
* mainKDFSalt<br />
* srpSalt<br />
<br />
<br />
These values do not require confidentiality, so are not included in the encrypted bundle. They are still protected by the HAWK integrity check. Note that the server should assert that both salts are different than the previously stored values.<br />
<br />
After using /account/reset, clients should immediately perform the login protocol from above. If the old password was forgotten, this is necessary to fetch kA. In either case, a new sessionToken is required, since old sessions and tokens are revoked by /account/reset. Clients should retain the new srpPassword value during this process to avoid needing to run the lengthy key-stretching routine a second time.<br />
<br />
= Deleting The Account =<br />
<br />
When the user wishes to completely delete their account, the browser needs to perform two actions:<br />
<br />
* contact the storage servers and delete all records and collections<br />
* contact the keyserver and delete the account information<br />
<br />
The user should be prompted for their password as confirmation (i.e. a browser in the normal attached-and-synchronizing state should not be able to erase the account information: it must acquire a new authToken first).<br />
<br />
The device then obtains an authToken as described above, then spends it on a HAWK-protected request to the /account/destroy endpoint. This request contains no body and returns only a success code.<br />
<br />
[[File:PICL-IdPAuth-deleteAccount.png|Deleting the Account]]<br />
<br />
= Crypto Notes =<br />
<br />
Strong entropy is needed in the following places:<br />
<br />
* (client) creation of private "a" value inside SRP<br />
* (server) initial creation of kA and wrap(kB)<br />
* (server) creation of private "B" value inside SRP<br />
* (server) creation of signToken and resetToken<br />
<br />
<br />
On the server, code should get entropy from /dev/urandom via a function that uses it, like "crypto.randomBytes()" in node.js or "os.urandom()" in python. On the client, code should combine local entropy with some fetched from the keyserver via getEntropy(), to guard against failures in the local entropy pool. Something like HKDF(IKM=localEntropy+remoteEntropy, salt="", info=KW("mergeEntropy")).<br />
<br />
An HKDF-based stream cipher is used to protect the response for getToken2(), and the request for resetAccount(). HKDF is used to create a number of random bytes equal to the length of the message, then these are XORed with the plaintext to produce the ciphertext. An HMAC is then computed from the ciphertext, to protect the integrity of the message.<br />
<br />
HKDF, like any KDF, is defined to produce output that is indistinguishable from random data ("The HKDF Scheme", http://eprint.iacr.org/2010/264.pdf , by Hugo Krawczyk, section 3). XORing a plaintext with a random keystream to produce ciphertext is a simple and secure approach to data encryption, epitomized by AES-CTR or a stream cipher (http://cr.yp.to/snuffle/design.pdf). HKDF is not the fastest way to generate such a keystream, but it is safe, easy to specify, and easy to implement (just HMAC and XOR).<br />
<br />
Each keystream must be unique. SRP is defined to produce a random session key for each run (as long as at least one of the sides provides a random ephemeral key). We define resetToken to be a single-use randomly-generated value. Hence our two HKDF-XOR keystreams will be unique.<br />
<br />
A slightly more-traditional alternative would be to use AES-CTR (with the same HMAC-SHA256 used here), with a randomly-generated IV. This is equally secure, but requires implementors to obtain an AES library (with CTR mode, which does not seem to be universal). An even more traditional technique would be AES-CBC, which introduces the need for padding and a way to specify the length of the plaintext. The additional specification complexity, plus the library load, leads me to prefer HKDF+XOR.<br />
<br />
kB is equal to the XOR of wrapKey (which is a deterministic function of the user's email address, password, mainSalt, and the stretching parameters) and the server's randomly-generated wrap(kB) value, making kB a random value too. Using XOR as a wrapping function allows us to avoid sending kB or wrap(kB) in the initial createAccount arguments, which are not protected by SRP, and thus would enable an eavesdropper to mount a dictionary attack on the password (using wrap(kB) as their oracle).<br />
<br />
Likewise, allowing the server to generate kA avoids exposure during createAccount. The only point of vulnerability is a forgotten-password resetAccount() message, if an eavesdropper can learn the resetToken. In this case, the attacker might either use the resetToken themselves (then use signToken to learn kA and wrap(kB)), or passively decrypt the resetAccount() arguments to retrieve just wrap(kB). Given wrap(kB) and some encrypted browser data, the attacker can guess passwords (and derive kB, etc) until the data decrypts properly.<br />
<br />
To make this technique safe, any time kB or the password is changed, the mainSalt should be changed too. Otherwise knowledge of both wrap(old-kB) and old-kB would reveal wrapKey, making it easy to deduce the new kB. Changing mainSalt causes wrapKey to change too, preventing this.<br />
<br />
mainSalt is incorporated at the end of the stretching process to allow it to proceed in parallel with the getToken1() call that retrieves the salt. The inputs to the lengthy stretch come entirely from the user (email and password) or are optimistically (stretching parameters). This speedup seems more important than the minor security benefit of including the salt at the beginning of the stretch.<br />
<br />
There is no MAC on wrap(kB). If the keyserver chooses to deliver a bogus wrap(kB) or kA, the client will discover the problem a moment later when it talks to a storage server and attempts to retrieve data from an unrecognized collection-ID (since we intend to derive collection-IDs from the key used to encrypt their data, which will be derived from kA or kB as appropriate). It might be useful to add a checksum to kA and wrap(kB) to detect accidental corruption (e.g. store and deliver kA+SHA256(kA)), but this doesn't protect against intentional changes. We omit this checksum for now, assuming that disks will be reliable enough to let us never experience such failures.<br />
<br />
We use scrypt without a per-user salt. This is safe because the "password" input to scrypt is already diversified by the user's email address. The intention is to allow the scrypt-helper to run on anonymous data, so that an attacker who compromises this helper cannot easily learn the email addresses of the partially-stretched K1 values that it is receiving, confounding their attack.<br />
<br />
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. <br />
For /certificate/sign, we do not need request confidentiality or response confidentiality, since the client's pubkey and the resulting certificate will both be exposed over a similar SSL connection to the storage server later. And it is sufficient to rely on the response integrity provided by SSL, since the client can verify the returned certificate for itself. For the other keyserver APIs protected by HAWK, these properties are either unnecessary, or are provided by additional mechanisms.<br />
<br />
= Proof-Of-Work =<br />
<br />
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(). The server can control how much work is required.<br />
<br />
The getToken1() call looks for a "X-PiCL-PoW:" HTTP header. Most of the time, clients don't supply this header. But if the server responds to the getToken1() call with an error that indicates PoW is required, clients must create a valid PoW string and include it as the value of an "X-PiCL-PoW:" header in their next call to getToken1().<br />
<br />
The server's error message includes two parameters. The first is a "prefix string": the client's PoW string is required to begin with this prefix. The second is a "threshold hash". SHA256(PoWString) is required to be lexicographically earlier than the thresholdHashString (i.e. the numerical value of its hash must be closer to zero than the threshold). The client is expected to concatenate the prefix with a counter, then repeatedly increment the counter and hash the result until they meet the threshold, then re-submit their getToken1() request with the combined prefix+counter string in the header. If the client has spent more than e.g. 10 seconds doing this, the client should probably help the user cancel the operation and try again.<br />
<br />
When a server is under a DoS attack (either via some manual configuration tool or sensed automatically), it should start requiring valid unique X-PiCL-PoW headers. The server should initially require very little work, by using a threshold hash with just a few leading zero bits. If this is insufficient to reduce the attack volume, the threshold should be lowered, requiring even more work (from both the attacker and legitimate clients).<br />
<br />
The server should create a prefix string that contains a parseable timestamp and a random nonce (e.g. "%d-%d-" % (int(time.time()), b32encode(os.urandom(8)))). The server should also decide on a cutoff time (perhaps ten minutes ago). Each server must then maintain a table of "old PoW strings" to prevent replay attacks (these do not need to be shared among all servers: an in-RAM cache is fine).<br />
<br />
When the server receives a proposed PoW string, it first splits off the leading timestamp, and if the timestamp is older than the cutoff time, it rejects the string (either by dropping the connection, or returning a new "PoW required" error if it's feeling nice). Then it hashes the whole string and compares it against the threshold, rejecting those which fail to meet the threshold. Finally, for strings that pass the hash threshold, it checks the "old strings" table, and rejects any that appear on that list.<br />
<br />
If the PoW string makes it past all these checks, the server should add the string to the "old strings" table, then accept the request (i.e. compute an srpB value and add a session-id table entry for the request).<br />
<br />
The old-strings table check should be optimized to reject present strings quickly (i.e. if we are under attack, we should expect to see lots of duplicates of the same string, and must minimize the work we do when this occurs).<br />
<br />
The server can remove values from the old-strings table that have timestamps older than the cutoff time. The server can also discard values at other times (to avoid consuming too much memory), without losing anything but protection against resource consumption.<br />
<br />
Other notes:<br />
<br />
The server-side code for this can be deferred until we care to have a response to a DoS attack. However the client-side code for this must be present from day one, otherwise we won't be able to turn on the defense later without fear of disabling legitimate old clients.<br />
<br />
The server should perform as little work as possible before rejecting a token. Every extra CPU cycle it spends in this path is increasing the DoS attack amplification factor.<br />
<br />
The nonce in the prefix string exists to make sure that two successive clients get different prefixes, and thus do not come up with the same counter value (and inadvertently create identical strings, looking like a replay attack). If this proved annoying or expensive, we could instead obligate clients to produce their own nonce.<br />
<br />
TBD: Is this worth it? Should the PoW string go into an HTTP header? (I want it to be cheap to extract, and not clutter logs). Should the error response be a distinctive HTTP error code so our monitoring tools can easily count them? We can also use this feature to slow down online guessing attacks (i.e. trigger it either when getToken1 is called too much or when getToken2 produces too many errors). Since getToken1() includes an email address, we could also requires PoWs for some addresses (e.g. those we know to be under attack) but not others.<br />
<br />
= Glossary =<br />
<br />
This defines some of the jargon we've developed for this protocol.<br />
<br />
* data classes: each type of browser data (bookmarks, passwords, history, etc) can be assigned, by the user, to either class-A or class-B<br />
* class-A: data assigned to this class can be recovered, even if the user forgets their password, by proving control over an email address and resetting the account. It can also be read by Mozilla (since it runs the keyserver and knows kA), or by the user's IdP (by resetting the account without the user's permission).<br />
* class-B: data in this class cannot be recovered if the password is forgotten. It cannot be read by the IdP. Mozilla (via the keyserver) cannot read this data, but can attempt a brute-force dictionary attack against the password.<br />
* kA: the master key for data stored as "class-A", a 32-byte binary string. Individual encryption keys for different datatypes are derived from kA.<br />
* kB: the master key for data stored as "class-B", a 32-byte binary string.<br />
* wrap(kB): an encrypted copy of kB. The keyserver stores wrap(kB) and never sees kB itself. The client (browser) uses a key derived from the user's password to decrypt wrap(kB), obtaining the real kB.<br />
* sessionToken: a long-lived per-device token which allows the device to obtained signed BrowserID certificates for the account's identity (GUID@picl-something.org). This token remains valid until the user revokes it (either by changing their password, or triggering some kind of "revoke a specific device" or "revoke all devices" function).<br />
<br />
= Test Vectors =<br />
<br />
The following example uses a non-ASCII email address of "andré@example.org" (with an accented "e", UTF8 encoding is 616e6472c3a9406578616d706c652e6f7267) and a non-ascii password of "pässwörd" (with accents on "a" and "o", UTF8 encoding is 70c3a4737377c3b67264).<br />
<br />
These test vectors were produced by the python code in https://github.com/warner/picl-spec-crypto (revision aa441c6). The diagrams above may lag behind the latest version of that code.<br />
<br />
== stretch-KDF ==<br />
<br />
email:<br />
616e6472c3a94065<br />
78616d706c652e6f<br />
7267<br />
<br />
password:<br />
70c3a4737377c3b6<br />
7264<br />
<br />
K1 (scrypt input):<br />
f84913e3d8e6d624<br />
689d0a3e9678ac8d<br />
cc79d2c2f3d96414<br />
88cd9d6ef6cd83dd<br />
<br />
K2 (scrypt output):<br />
5b82f146a6412692<br />
3e4167a0350bb181<br />
feba61f63cb17140<br />
12b19cb0be0119c5<br />
<br />
stretchedPW:<br />
c16d46c31bee242c<br />
b31f916e9e38d60b<br />
76431d3f5304549c<br />
c75ae4bc20c7108c<br />
<br />
== main-KDF ==<br />
<br />
mainSalt (normally random):<br />
00f0000000000000<br />
0000000000000000<br />
0000000000000000<br />
000000000000034d<br />
<br />
srpPW:<br />
00f9b71800ab5337<br />
d51177d8fbc682a3<br />
653fa6dae5b87628<br />
eeec43a18af59a9d<br />
<br />
unwrapBKey:<br />
6ea660be9c89ec35<br />
5397f89afb282ea0<br />
bf21095760c8c500<br />
9bbcc894155bbe2a<br />
<br />
internal x (base 10):<br />
8192518690918<br />
99580124814080709381476194749939<br />
03899664126296984459627523279550<br />
<br />
internal x (hex):<br />
b5200337cc3f3f92<br />
6cdddae0b2d31029<br />
c069936a844aff58<br />
779a545be89d0abe<br />
<br />
v (verifier as number) (base 10):<br />
114649<br />
57230405843056840989945621595830<br />
71784395917725741221739574165799<br />
54316134303691657140298181419198<br />
87853709633756255809680435884948<br />
69849281177012209169281795507853<br />
57610332070005048463659745521969<br />
83218225819721112680718485091921<br />
64608360806562626442477160609654<br />
43167308814558974899899506977051<br />
96721477608178869100211706638584<br />
53875100985456239693728258285562<br />
04889672594983678412848291529879<br />
88548996842770025110751388952323<br />
22170663943486107183421205517476<br />
84831590615660554713667726412525<br />
73641352721966728239512914666806<br />
49625530438034148797508015907639<br />
67594925530663571631035463732161<br />
30193328802116982288883318596822<br />
<br />
== SRP Verifier ==<br />
<br />
k (base 10):<br />
259003859907<br />
09503006915442163037721228467470<br />
35652616593381637186118123578112<br />
<br />
srpSalt (normally random):<br />
00f1000000000000<br />
0000000000000000<br />
0000000000000000<br />
0000000000000179<br />
<br />
srpVerifier:<br />
00173ffa0263e63c cfd6791b8ee2a40f<br />
048ec94cd95aa8a3 125726f9805e0c82<br />
83c658dc0b607fbb 25db68e68e93f265<br />
8483049c68af7e82 14c49fde2712a775<br />
b63e545160d64b00 189a86708c69657d<br />
a7a1678eda0cd79f 86b8560ebdb1ffc2<br />
21db360eab901d64 3a75bf1205070a57<br />
91230ae56466b8c3 c1eb656e19b794f1<br />
ea0d2a077b3a7553 50208ea0118fec8c<br />
4b2ec344a05c66ae 1449b32609ca7189<br />
451c259d65bd15b3 4d8729afdb5faff8<br />
af1f3437bbdc0c3d 0b069a8ab2a959c9<br />
0c5a43d42082c774 90f3afcc10ef5648<br />
625c0605cdaace6c 6fdc9e9a7e6635d6<br />
19f50af773452247 0502cab26a52a198<br />
f5b00a2798589165 07b0b4e9ef9524d6<br />
<br />
== SRP B ==<br />
<br />
private b (normally random) (base 10):<br />
1198277<br />
66042000957856349411550091527197<br />
08125226960544768993643095227800<br />
29105361555030352774505625606029<br />
71778328003253459733139844872578<br />
33596964143417216389157582554932<br />
02841499373672188315534280693274<br />
23189198736863575142046053414928<br />
39548731879043135718309539706489<br />
29157321423483527294767988835942<br />
53343430842313006332606344714480<br />
99439808861069316482621424231409<br />
08830704769167700098392968117727<br />
43420990997238759832829219109897<br />
32876428831985487823417312772399<br />
92628295469389578458363237146486<br />
38545526799188280210660508721582<br />
00403102624831815596140094933216<br />
29832845626116777080504444704039<br />
04739431335617585333671378812943<br />
<br />
private b (hex):<br />
00f3000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 000000000000000f<br />
<br />
transmitted srpB:<br />
0022ce5a7b9d8127 7172caa20b0f1efb<br />
4643b3becc535664 73959b07b790d3c3<br />
f08650d5531c19ad 30ebb67bdb481d1d<br />
9cf61bf272f84398 48fdda58a4e6abc5<br />
abb2ac496da5098d 5cbf90e29b4b110e<br />
4e2c033c70af7392 5fa37457ee13ea3e<br />
8fde4ab516dff1c2 ae8e57a6b264fb9d<br />
b637eeeae9b5e43d faba9b329d3b8770<br />
ce89888709e02627 0e474eef822436e6<br />
397562f284778673 a1a7bc12b6883d1c<br />
21fbc27ffb3dbeb8 5efda279a69a1941<br />
4969113f10451603 065f0a0126666456<br />
51dde44a52f4d8de 113e2131321df1bf<br />
4369d2585364f9e5 36c39a4dce33221b<br />
e57d50ddccb4384e 3612bbfd03a268a3<br />
6e4f7e01de651401 e108cc247db50392<br />
<br />
== SRP A ==<br />
<br />
private a (normally random) (base 10):<br />
1193346<br />
47663227291363113405741243413916<br />
43482736314616601220006703889414<br />
28162541137108417166380088052095<br />
43910927476491099816542561560345<br />
50331133015255005622124012256352<br />
06121987030570656676375703406470<br />
63422988042473190059156975005813<br />
46381864669664357382020200036915<br />
26156674010218162984912976536206<br />
14440782978764393137821956464627<br />
16314542157937343986808167341567<br />
89864323268060014089757606109012<br />
50649711198896213496068605039486<br />
22864591676298304745954690086093<br />
75374681084741884719851454277570<br />
80362211874088739962880012800917<br />
05751238004976540634839106888223<br />
63866455314898189520502368799907<br />
19946264951520393624479315579863<br />
<br />
private a (hex):<br />
00f2000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 000000000000d3d7<br />
<br />
transmitted srpA:<br />
007da76cb7e77af5 ab61f334dbd5a958<br />
513afcdf0f47ab99 271fc5f7860fe213<br />
2e5802ca79d2e5c0 64bb80a38ee08771<br />
c98a937696698d87 8d78571568c98a1c<br />
40cc6e7cb101988a 2f9ba3d65679027d<br />
4d9068cb8aad6ebf f0101bab6d52b5fd<br />
fa81d2ed48bba119 d4ecdb7f3f478bd2<br />
36d5749f2275e948 4f2d0a9259d05e49<br />
d78a23dd26c60bfb a04fd346e5146469<br />
a8c3f010a627be81 c58ded1caaef2363<br />
635a45f97ca0d895 cc92ace1d09a99d6<br />
beb6b0dc0829535c 857a419e834db128<br />
64cd6ee8a843563b 0240520ff0195735<br />
cd9d316842d5d3f8 ef7209a0bb4b54ad<br />
7374d73e79be2c39 75632de562c59647<br />
0bb27bad79c3e2fc ddf194e1666cb9fc<br />
<br />
== SRP key-agreement ==<br />
<br />
u:<br />
b284aa1064e87751 50da6b5e2147b47c<br />
a7df505bed94a6f4 bb2ad873332ad732<br />
<br />
S:<br />
0092aaf0f527906a a5e8601f5d707907<br />
a03137e1b601e04b 5a1deb02a981f4be<br />
037b39829a27dba5 0f1b27545ff2e287<br />
29c2b79dcbdd32c9 d6b20d340affab91<br />
a626a8075806c26f e39df91d0ad979f9<br />
b2ee8aad1bc783e7 097407b63bfe58d9<br />
118b9b0b2a7c5c4c debaf8e9a460f4bf<br />
6247b0da34b760a5 9fac891757ddedca<br />
f08eed823b090586 c63009b2d740cc9f<br />
5397be89a2c32cdc fe6d6251ce11e44e<br />
6ecbdd9b6d93f30e 90896d2527564c7e<br />
b9ff70aa91acc0ba c1740a11cd184ffb<br />
989554ab58117c21 96b353d70c356160<br />
100ef5f4c28d19f6 e59ea2508e8e8aac<br />
6001497c27f362ed bafb25e0f045bfdf<br />
9fb02db9c908f103 40a639fe84c31b27<br />
<br />
M1:<br />
27949ec1e0f16256<br />
33436865edb037e2<br />
3eb6bf5cb91873f2<br />
a2729373c2039008<br />
<br />
srpK:<br />
e68fd0112bfa31dc<br />
ffc8e9c96a1cbadb<br />
4c3145978ff35c73<br />
e5bf8d30bbc7499a<br />
<br />
== /auth ==<br />
<br />
srpK:<br />
e68fd0112bfa31dc<br />
ffc8e9c96a1cbadb<br />
4c3145978ff35c73<br />
e5bf8d30bbc7499a<br />
<br />
respHMACkey:<br />
6584613597ef012f<br />
f1752b7869f01d03<br />
c72547a7b7199681<br />
531d9df1991edf23<br />
<br />
respXORkey:<br />
455835926ae37a1b<br />
627bd16affbeeab6<br />
27ecc737121826ca<br />
4a2bac2c100bf417<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
plaintext:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
ciphertext:<br />
253957f10e861c7c<br />
0a12bb0193d384d9<br />
579db544666d50bd<br />
3252d6576c768a68<br />
<br />
MAC:<br />
a98c87f5769ab4cc<br />
ca3df863faeb217e<br />
b16ddc29d712b301<br />
12b446324ee806d6<br />
<br />
response:<br />
253957f10e861c7c<br />
0a12bb0193d384d9<br />
579db544666d50bd<br />
3252d6576c768a68<br />
a98c87f5769ab4cc<br />
ca3df863faeb217e<br />
b16ddc29d712b301<br />
12b446324ee806d6<br />
<br />
== authtoken ==<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
tokenID (authToken):<br />
9a39818e3bbe6132<br />
38c9d7ff013a1841<br />
1ed2c66c3565c3c4<br />
de03feefecb7d212<br />
<br />
reqHMACkey:<br />
4a17cbdd54ee17db<br />
426fcd7baddff587<br />
231d7eadb408c091<br />
ce19ca915b715985<br />
<br />
requestKey:<br />
9d93978e662bfc6e<br />
8cc203fa4628ef5a<br />
7bf1ddfd7ee54e97<br />
ec5c033257b4fca9<br />
<br />
== /session ==<br />
<br />
requestKey:<br />
9d93978e662bfc6e<br />
8cc203fa4628ef5a<br />
7bf1ddfd7ee54e97<br />
ec5c033257b4fca9<br />
<br />
respHMACkey:<br />
cd3f50403d060b21<br />
76d32f71ca105bd8<br />
7c9b6c4e10e3ebf9<br />
3f5077bec2db24fa<br />
<br />
respXORkey:<br />
8422c53143dea9c6<br />
044afbe95228f291<br />
74996b830b1794a3<br />
eff132da53174d43<br />
92eeb87ccf8ad7a8<br />
0c432894e066de6b<br />
0ff70658dfbf2f07<br />
b9c7704045edcd54<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
sessionToken:<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
plaintext:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
ciphertext:<br />
04a347b2c75b2f41<br />
8cc37162dea57c1e<br />
e408f9109f820234<br />
7768a841cf8ad3dc<br />
324f1adf6b2f710f<br />
a4ea823f4ccb70c4<br />
bf46b4eb6b0a99b0<br />
017ecafbf95073eb<br />
<br />
MAC:<br />
7973ddbb184b601a<br />
c4df09704028ebfc<br />
754dd50e7d8eebfa<br />
52ce3fd868c69852<br />
<br />
response:<br />
04a347b2c75b2f41<br />
8cc37162dea57c1e<br />
e408f9109f820234<br />
7768a841cf8ad3dc<br />
324f1adf6b2f710f<br />
a4ea823f4ccb70c4<br />
bf46b4eb6b0a99b0<br />
017ecafbf95073eb<br />
7973ddbb184b601a<br />
c4df09704028ebfc<br />
754dd50e7d8eebfa<br />
52ce3fd868c69852<br />
<br />
== /account/keys ==<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
tokenID (keyFetchToken):<br />
3d0a7c02a15a62a2<br />
882f76e39b6494b5<br />
00c022a8816e0486<br />
25a495718998ba60<br />
<br />
reqHMACkey:<br />
87b8937f61d38d0e<br />
29cd2d5600b3f4da<br />
0aa48ac41de36a0e<br />
fe84bb4a9872ceb7<br />
<br />
keyRequestKey:<br />
14f338a9e8c6324d<br />
9e102d4e6ee83b20<br />
9796d5c74bb734a4<br />
10e729e014a4a546<br />
<br />
respHMACkey:<br />
f824d2953aab9faf<br />
51a1cb65ba9e7f9e<br />
5bf91c8d8fd1ac1c<br />
8c2d31853a8a1210<br />
<br />
respXORkey:<br />
ce7d7aa77859b235<br />
9932970bbe2101f2<br />
e80d01faf9191bd5<br />
ee52181d2f0b7809<br />
8281ba8cff392543<br />
3a89f7c3095e0c89<br />
900a469d60790c83<br />
3281c4df1a11c763<br />
<br />
kA:<br />
2021222324252627<br />
28292a2b2c2d2e2f<br />
3031323334353637<br />
38393a3b3c3d3e3f<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
plaintext:<br />
2021222324252627<br />
28292a2b2c2d2e2f<br />
3031323334353637<br />
38393a3b3c3d3e3f<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
ciphertext:<br />
ee5c58845c7c9412<br />
b11bbd20920c2fdd<br />
d83c33c9cd2c2de2<br />
d66b222613364636<br />
c2c0f8cfbb7c6304<br />
72c0bd88451342c6<br />
c05b14ce342c5ad4<br />
6ad89e84464c993c<br />
<br />
MAC:<br />
3927d30230157d08<br />
17a077eef4b20d97<br />
6f7a97363faf3f06<br />
4c003ada7d01aa70<br />
<br />
response:<br />
ee5c58845c7c9412<br />
b11bbd20920c2fdd<br />
d83c33c9cd2c2de2<br />
d66b222613364636<br />
c2c0f8cfbb7c6304<br />
72c0bd88451342c6<br />
c05b14ce342c5ad4<br />
6ad89e84464c993c<br />
3927d30230157d08<br />
17a077eef4b20d97<br />
6f7a97363faf3f06<br />
4c003ada7d01aa70<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
unwrapBKey:<br />
6ea660be9c89ec35<br />
5397f89afb282ea0<br />
bf21095760c8c500<br />
9bbcc894155bbe2a<br />
<br />
kB:<br />
2ee722fdd8ccaa72<br />
1bdeb2d1b76560ef<br />
ef705b04349d9357<br />
c3e592cf4906e075<br />
<br />
== use session (certificate/sign, etc) ==<br />
<br />
sessionToken:<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
tokenID (sessionToken):<br />
c0a29dcf46174973<br />
da1378696e4c82ae<br />
10f723cf4f4d9f75<br />
e39f4ae3851595ab<br />
<br />
reqHMACkey:<br />
9d8f22998ee7f579<br />
8b887042466b72d5<br />
3e56ab0c094388bf<br />
65831f702d2febc0<br />
<br />
== /password/change ==<br />
<br />
requestKey:<br />
9d93978e662bfc6e<br />
8cc203fa4628ef5a<br />
7bf1ddfd7ee54e97<br />
ec5c033257b4fca9<br />
<br />
respHMACkey:<br />
60d01e3d1da53b10<br />
93124a30c26889d7<br />
b2e067e7a09fde14<br />
6f935e3c653614f9<br />
<br />
respXORkey:<br />
3de5bd5e80faf84a<br />
dfca5396148123ef<br />
8184cd4bc10a7c8a<br />
db1688495affee67<br />
e07f80d914c5105f<br />
c86d6af24c4be1b1<br />
ef6c9c661422ac43<br />
181b3d29624a0cc2<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
accountResetToken:<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
plaintext:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
ciphertext:<br />
bd643fdd047f7ecd<br />
5743d91d980cad60<br />
11155fd8559fea1d<br />
438f12d2c66270f8<br />
20be421ad000d698<br />
00a4a03980862f7e<br />
3fbd4eb5c0f77a94<br />
c0c2e7f2be97d21d<br />
<br />
MAC:<br />
804fc4bc30923cc0<br />
d6c07ffea954848e<br />
0076b94f7deee71f<br />
a34db5c106d91980<br />
<br />
response:<br />
bd643fdd047f7ecd<br />
5743d91d980cad60<br />
11155fd8559fea1d<br />
438f12d2c66270f8<br />
20be421ad000d698<br />
00a4a03980862f7e<br />
3fbd4eb5c0f77a94<br />
c0c2e7f2be97d21d<br />
804fc4bc30923cc0<br />
d6c07ffea954848e<br />
0076b94f7deee71f<br />
a34db5c106d91980<br />
<br />
== /account/reset ==<br />
<br />
accountResetToken:<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
tokenID (accountResetToken):<br />
46ec557e56e531a0<br />
58620e9344ca9c75<br />
afac0d0bcbdd6f8c<br />
3c2f36055d9540cf<br />
<br />
reqHMACkey (for HAWK):<br />
716ebc28f5122ef4<br />
8670a48209190a16<br />
05263c3188dfe452<br />
56265929d1c45e48<br />
<br />
requestKey:<br />
aa5906d2318c6e54<br />
ecebfa52f10df4c0<br />
36165c230cc78ee8<br />
59f546c66ea3c126<br />
<br />
reqHMACkey (for ciphertext):<br />
a0d894a6232f2e78<br />
66a51dda3f84e01e<br />
ae5adb812564f391<br />
6c0d3cb16bdb743c<br />
<br />
reqXORkey:<br />
9cbde8fc9df31455 837b881e6c0d7e3c<br />
ca13589bc868c527 95fc00e51f2048ab<br />
d56de37629cda0b0 3f580a9e6c433724<br />
b5df12a735ccf2a1 e232d4f5fef84f86<br />
a1b4fdc47f8d1f73 12a6a230a8742d5b<br />
c144ee9abce25b57 9670b81085064cfb<br />
dcab862d9d57abcc 2142dcdde6682281<br />
d378c89b0dce06ae cd1c1ff68ad6db9a<br />
9cab0b02e160805b 59bb8712c8233056<br />
1b3ded75c430e23c 22338833b6f2ba39<br />
f5015ca7a905d6ee 6ec5b1e3ae5204ba<br />
6f3630ebf30ebbac 1f47329e8fe22770<br />
2a3d61f593328dd4 f0a96b628aa8ffec<br />
181e93d2af8d87ff 2d90d67caaf7f7c9<br />
af024c93cfc79e94 67ba70b3076c20cc<br />
141aa254ff159b25 3125a304441cecf3<br />
4fc1845ce96ee598 21fde83cd24e3209<br />
4d304477bfa2c8ed df236e512560694e<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
newSRPv:<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
<br />
plaintext:<br />
4041424344454647 48494a4b4c4d4e4f<br />
5051525354555657 58595a5b5c5d5e5f<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
<br />
ciphertext:<br />
dcfcaabfd9b65212 cb32c25520403073<br />
9a420ac89c3d9370 cda55abe437d16f4<br />
c47cf26738dcb1a1 2e491b8f7d522635<br />
a4ce03b624dde3b0 f323c5e4efe95e97<br />
b0a5ecd56e9c0e62 03b7b321b9653c4a<br />
d055ff8badf34a46 8761a90194175dea<br />
cdba973c8c46badd 3053cdccf7793390<br />
c269d98a1cdf17bf dc0d0ee79bc7ca8b<br />
8dba1a13f071914a 48aa9603d9322147<br />
0a2cfc64d521f32d 33229922a7e3ab28<br />
e4104db6b814c7ff 7fd4a0f2bf4315ab<br />
7e2721fae21faabd 0e56238f9ef33661<br />
3b2c70e482239cc5 e1b87a739bb9eefd<br />
090f82c3be9c96ee 3c81c76dbbe6e6d8<br />
be135d82ded68f85 76ab61a2167d31dd<br />
050bb345ee048a34 2034b215550dfde2<br />
5ed0954df87ff489 30ecf92dc35f2318<br />
5c215566aeb3d9fc ce327f403471785f<br />
<br />
MAC:<br />
1d3572fe0b4bdf66<br />
f2b2657cb2ee56fc<br />
80f7a82708cafd82<br />
1952e1f01761cb29<br />
<br />
response:<br />
dcfcaabfd9b65212 cb32c25520403073<br />
9a420ac89c3d9370 cda55abe437d16f4<br />
c47cf26738dcb1a1 2e491b8f7d522635<br />
a4ce03b624dde3b0 f323c5e4efe95e97<br />
b0a5ecd56e9c0e62 03b7b321b9653c4a<br />
d055ff8badf34a46 8761a90194175dea<br />
cdba973c8c46badd 3053cdccf7793390<br />
c269d98a1cdf17bf dc0d0ee79bc7ca8b<br />
8dba1a13f071914a 48aa9603d9322147<br />
0a2cfc64d521f32d 33229922a7e3ab28<br />
e4104db6b814c7ff 7fd4a0f2bf4315ab<br />
7e2721fae21faabd 0e56238f9ef33661<br />
3b2c70e482239cc5 e1b87a739bb9eefd<br />
090f82c3be9c96ee 3c81c76dbbe6e6d8<br />
be135d82ded68f85 76ab61a2167d31dd<br />
050bb345ee048a34 2034b215550dfde2<br />
5ed0954df87ff489 30ecf92dc35f2318<br />
5c215566aeb3d9fc ce327f403471785f<br />
1d3572fe0b4bdf66 f2b2657cb2ee56fc<br />
80f7a82708cafd82 1952e1f01761cb29<br />
<br />
== /account/destroy ==<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
tokenID (authToken):<br />
9a39818e3bbe6132<br />
38c9d7ff013a1841<br />
1ed2c66c3565c3c4<br />
de03feefecb7d212<br />
<br />
reqHMACkey:<br />
4a17cbdd54ee17db<br />
426fcd7baddff587<br />
231d7eadb408c091<br />
ce19ca915b715985<br />
<br />
= Keyserver Protocol Summary =<br />
<br />
* POST /account/create (email,srpV,srpSalt) -> ok (server sends verification email)<br />
** creates a user account<br />
* GET /account/devices [sessionToken] () -> list of devices<br />
* GET /account/keys [keyFetchToken,needs-verf] () -> kA/wrap(kB)<br />
** single-use, only if email is verified, encrypted results<br />
* POST /account/reset [authed+encrypted by accountResetToken] (wrap(kB),srpV,srpSalt) -> ok<br />
** single-use, does not require email to be verified, revoke all tokens for account, send notification email to user<br />
* POST /account/delete [authToken] () -> ok, account deleted<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authToken] () -> keyFetchToken, sessionToken<br />
* POST /session/destroy [sessionToken] () -> ok<br />
** for detaching a device, destroy all tokens<br />
* POST /recovery_email/status [sessionToken] () -> "verified" status of email<br />
** use "Accept: text/event-stream" header for server-sent-events; server will send "update" event with the new content of the resource any time it changes.<br />
* POST /recovery_email/resend_code [sessionToken] () -> re-send verification email<br />
* POST /recovery_email/verify_code (code) -> set "verified" flag<br />
** this code will come from a clickable link and is an unauthenticated endpoint<br />
** this could maybe take the recovery method if that would be helpful<br />
** sets verified flag on recovery method<br />
* POST /certificate/sign [sessionToken,needs-verf] (pubkey) -> cert<br />
** only if recovery email is verified<br />
* POST /password/change/start [authToken,needs-verf] () -> accountResetToken, keyFetchToken<br />
* POST /password/forgot/send_code () -> forgotPasswordToken<br />
** sends code to recovery method (email for now, maybe SMS later)<br />
** this is a short code, not a clickable link<br />
* POST /password/forgot/resend_code (forgotPasswordToken) -> re-sends code<br />
* POST /password/forgot/verify_code (forgotPasswordToken, code) -> accountResetToken<br />
** sets verified flag on recovery method<br />
* POST /get_random_bytes<br />
<br />
<br />
== Typical Client Flows ==<br />
<br />
Create account<br />
<br />
* POST /account/create (email,srpV,srpSalt) -> ok (server sends verification email)<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authed with authToken]() -> keyFetchToken, sessionToken<br />
* GET /recovery_email/status [sessionToken] () -> "verified" status<br />
** (optional, only if user requests resend) POST /recovery_email/resend_code [sessionToken]() -> ok<br />
** POST /recovery_email/verify_code (code) -> ok<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
* POST /certificate/sign [sessionToken] (pubkey) -> cert<br />
<br />
<br />
Attach to new device<br />
<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authToken] () -> keyFetchToken, sessionToken<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
** (if unverified-error, do waitUntilEmailVerified, then try again)<br />
* POST /certificate/sign [sessionToken] (pubkey) -> cert<br />
<br />
<br />
Forgot password<br />
<br />
* POST /password/forgot/send_code (email) -> forgotPasswordToken<br />
* POST /password/forgot/verify_code (forgotPasswordToken, code) -> accountResetToken<br />
* POST /account/reset [authed+encrypted by accountResetToken] (0000,srpV,srpSalt) -> ok<br />
* GOTO "Attach to new device"<br />
<br />
<br />
Change Password<br />
<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /password/change/start [authToken] () -> accountResetToken, keyFetchToken<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
* POST /account/reset [authed+encrypted by accountResetToken] (wrap(kB),srpV,srpSalt) -> ok<br />
* GOTO "Attach to new device"</div>Warnerhttps://wiki.mozilla.org/index.php?title=WeeklyUpdates/2014-04-28&diff=969816WeeklyUpdates/2014-04-282014-04-28T06:59:43Z<p>Warner: /* Thursday, {{#time:d F|{{SUBPAGENAME}} +3 days}} */</p>
<hr />
<div><br />
{{WeeklyUpdateNav}}<br />
{{conf|8600}}<br />
<br />
__TOC__<br />
<br />
= All-hands Status Meeting Agenda =<br />
<br />
Items in this section will be shared during the live all-hand status meeting.<br />
<br />
== Friends of Mozilla [[Image:Tree.gif|Friends of Mozilla]] ==<br />
<br />
* Big thanks to add-on reviewer [https://mozillians.org/u/teo951/ Teo], who rolled up his sleeves and reviewed 64 add-ons last week! The add-on review queue currently has a wait time of 3 weeks, so if you have an add-on in the queue and would like to help it move along, please apply to be an add-on reviewer! https://wiki.mozilla.org/Marketplace/Reviewers/Addons<br />
<br />
== Upcoming Events ==<br />
<br />
=== This Week ===<br />
<br />
=== Monday, {{#time:d F|{{SUBPAGENAME}}}} ===<br />
<br />
* One and Done Meeting 2:00-2:30pm PST Liz Vidyo Room full details and guest link on the wiki https://etherpad.mozilla.org/oneanddone-2014q2<br />
<br />
=== Tuesday, {{#time:d F|{{SUBPAGENAME}} +1 day}} ===<br />
<br />
=== Wednesday, {{#time:d F|{{SUBPAGENAME}} +2 days}} ===<br />
<br />
=== Thursday, {{#time:d F|{{SUBPAGENAME}} +3 days}} ===<br />
<br />
* 10:00 AM Pacific / 17:00 UTC: Grow Mozilla discussion -- a forum for discussing community building at Mozilla<br />
** [[Grow/Meeting_05_01_14|Agenda and dial-in information]] (feel free to add items to the agenda)<br />
* 12:00 PM noon Pacific / 19:00 UTC: Firefox Accounts and Sync in FF29 - a brownbag presentation describing Firefox Accounts and changes to the Sync setup process in Firefox 29. Live in San Francisco and on Air Mozilla.<br />
<br />
=== Friday, {{#time:d F|{{SUBPAGENAME}} +4 days}} ===<br />
<br />
=== Saturday, {{#time:d F|{{SUBPAGENAME}} +5 days}} ===<br />
<br />
=== Sunday, {{#time:d F|{{SUBPAGENAME}} +6 days}} ===<br />
<br />
=== Next Week ===<br />
<br />
== Project Status Updates (voice updates) ==<br />
<br />
=== Firefox Desktop ===<br />
''Speaker Location:''<br />
<br />
=== Firefox Mobile ===<br />
''Speaker Location:'' <br />
<br />
=== Firefox OS ===<br />
''Speaker Location:''<br />
<br />
=== [https://webmaker.org/ Webmaker] ===<br />
''Speaker Location:''<br />
<br />
=== [https://openbadges.org/ Open Badges] ===<br />
''Speaker Location:''<br />
<br />
=== [https://mozillascience.org/ Mozilla Science Lab] ===<br />
''Speaker Location:''<br />
<br />
=== Mozilla Reps ===<br />
''Speaker Location:''<br />
<br />
=== Grow Mozilla ===<br />
''Speaker Location:''<br />
<br />
=== Identity ===<br />
''Speaker Location:''<br />
<br />
=== Services ===<br />
''Speaker Location:''<br />
<br />
=== Firefox Marketplace ===<br />
''Speaker Location:''<br />
<br />
=== IT ===<br />
''Speaker Location:''<br />
<br />
=== Web Compat ===<br />
''Speaker Location:''<br />
<br />
== Speakers ==<br />
<br />
The limit is 3 minutes per speaker. It's like a lightning talk, but don't feel that you have to have slides in order to make a presentation. If you plan on showing a video, you need to contact the Air Mozilla team before the day of the meeting or you will be deferred to the next week.<br />
<br />
{| class="fullwidth-table"<br />
|-<br />
! Presenter<br />
! Title<br />
! Topic<br />
! Location<br />
! Share?<br />
! Media<br />
! More Details<br />
|-<br />
| Who Are You?<br />
| What Do You Do?<br />
| What are you going to talk about?<br />
| Where are you presenting from? (Moz Space, your house, space)<br />
| Will you be sharing your screen? (yes/no, other info)<br />
| Links to slides or images you want displayed on screen<br />
| Link to where audience can find out more information<br />
|-<br />
| Rachel Berenbaum and Michelle Marovich<br />
| Recruiting Programs<br />
| Recap 2013 and preview changes coming in 2014<br />
| Mountain View<br />
| No<br />
| https://mana.mozilla.org/wiki/display/globalstaffing/Recruiting+Home We are trying to get this data over to public wiki, but might not have it formatted before project call. <br />
| https://mana.mozilla.org/wiki/display/globalstaffing/Recruiting+Home We are trying to get this data over to public wiki, but might not have it formatted before project call. <br />
|-<br />
| Jean Collings<br />
| Community Engagement<br />
| How you can help spread the word about Firefox on Tuesday and beyond.<br />
| San Francisco<br />
| No<br />
| <br />
| Tomorrow on launch day, visit https://webwewant.mozilla.org/#video - watch, share and interact! <br />
|}<br />
<br />
= Roundtable =<br />
<br />
Do you have a question about a Mozilla Project or initiative? Let us know by Friday- we'll do our best to get you an answer.<br />
<br />
Please note that we may not always be able to get to every item on this list, but we will try!<br />
<br />
{| class="fullwidth-table"<br />
|-<br />
! Who are you?<br />
! Area of question<br />
! Question<br />
|-<br />
| ''What's your name? What do you work on?''<br />
| ''Is your question about policy, a product, a Foundation initiative, etc.''<br />
| ''What would you like to know?''<br />
<!-- Insert new rows here --><br />
|-<br />
|}<br />
<br />
= Welcome! =<br />
<br />
Let's say hello to some new Mozillians! If you are not able to join the meeting live, you can add a link to a short video introducing yourself.<br />
<br />
== Introducing New Volunteers ==<br />
{| class="fullwidth-table"<br />
|-<br />
! New Volunteer(s)<br />
! Introduced by<br />
! Speaker location<br />
! New Volunteer location<br />
! Will be working on<br />
|-<br />
| ''Who is the new volunteer(s)?''<br />
| ''Who will be introducing that person?''<br />
| ''Where is the introducer?''<br />
| ''Where is the new person based?''<br />
| ''What will the new person be doing?''<br />
|-<br />
<!-- Insert new rows here --><br />
|-<br />
|}<br />
<br />
== Introducing New Hires ==<br />
{| class="fullwidth-table"<br />
|-<br />
! New Hire<br />
! Introduced by<br />
! Speaker location<br />
! New Hire location<br />
! Will be working on<br />
|-<br />
| ''Who is the new hire?''<br />
| ''Who will be introducing that person?''<br />
| ''Where is the introducer?''<br />
| ''Where will the new person be working from?''<br />
| ''What will the new person be working on?''<br />
|-<br />
<!-- Insert new rows here --><br />
| Sean Lin<br />
| Marco Chen<br />
| Taipei office<br />
| Taipei office<br />
| Firefox OS<br />
|-<br />
<br />
== Introducing New Interns ==<br />
{| class="fullwidth-table"<br />
|-<br />
! New Intern<br />
! Introduced by<br />
! Speaker location<br />
! New Hire location<br />
! Will be working on<br />
|-<br />
| ''Who is the new intern?''<br />
| ''Who will be introducing that person?''<br />
| ''Where is the introducer?''<br />
| ''Where will the new person be working from?''<br />
| ''What will the new person be working on?''<br />
|-<br />
| Johan Lorenzo<br />
| Jason Smith<br />
| Mountain View, CA<br />
| Mountain View, CA<br />
| Firefox OS QA<br />
|-<br />
| Ahmed Kachkach<br />
| Mark C&ocirc;t&eacute;<br />
| Mountain View, CA<br />
| San Francisco, CA<br />
| Automation<br />
|}<br />
<br />
= &lt;meta&gt; =<br />
<br />
Notes and non-voice status updates that aren't part of the live meeting go here.<br />
<br />
== Status Updates By Team (*non-voice* updates) ==<br />
<br />
=== Firefox ===<br />
<br />
=== Platform ===<br />
<br />
=== Services ===<br />
<br />
=== Messaging ===<br />
<br />
=== Mobile ===<br />
<br />
=== IT ===<br />
<br />
=== Release Engineering ===<br />
<br />
=== QA ===<br />
<br />
==== Test Execution ====<br />
<br />
==== WebQA ====<br />
<br />
==== QA Community ====<br />
<br />
=== Automation & Tools ===<br />
<br />
=== Security ===<br />
<br />
=== Engagement ===<br />
<br />
==== PR ====<br />
<br />
==== Events ====<br />
<br />
==== Social Support ====</div>Warnerhttps://wiki.mozilla.org/index.php?title=Identity/AttachedServices/KeyServerProtocol&diff=953572Identity/AttachedServices/KeyServerProtocol2014-03-20T00:56:40Z<p>Warner: /* SRP Protocol Details */ oops, RFC5054, not 5053</p>
<hr />
<div>= PiCL Key Server / IdP Protocol =<br />
<br />
NOTE: This specification is slowly converging on stability (31-Jul-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.<br />
<br />
The server is being developed in https://github.com/mozilla/picl-idp . This repo currently includes a demonstration client (node.js CLI).<br />
<br />
Note that all messages are delivered over an HTTPS connection. The client browser may also implement cert-pinning to improve on the certificate validation process. The protections described below are in addition to those provided by TLS.<br />
<br />
Remaining TODO items:<br />
<br />
* decide on client-side key-stretching parameters: http://keywrapping.appspot.com can help<br />
* finalize SRP questions (definition of M1, generation of a/b)<br />
* finalize proof-of-work/DoS-prevention details<br />
* decide how to rate-limit account-creation calls<br />
* confirm this is actually implementable inside Firefox (especially w.r.t. NSS and Android/Java crypto)<br />
<br />
= Creating The Account =<br />
<br />
The first act performed by a user is to create the account. They enter email+password into their browser, which then does the following steps:<br />
<br />
* decide upon stretching parameters (perhaps consulting the keyserver for recommendations, but imposing a minimum strength requirement). For now we used a fixed {firstPBKDF:20000, scrypt: {N:65536, r:8, p:1}, secondPBKDF:20000}.<br />
* randomly choose a 32-byte mainSalt (this should be unique, but is not secret)<br />
* choose the SRP group parameters (fixed: use the 2048-bit group described below)<br />
* perform key-stretching (as described below), derive stretchedPW and srpPW<br />
* randomly choose a 32-byte srpSalt (unique, but not secret)<br />
* create srpVerifier from srpPW and srpSalt (as described below)<br />
* deliver (email, stretchParams, mainSalt, srpParams, srpSalt) to the keyserver's "POST /account/create" API<br />
<br />
The server, when creating a new account, creates both kA and wrap(kB) as randomly-generated 256-bit (32-byte) strings. It stores these, along with all the remaining values, indexed by email, in the account table where they can be retrieved by getToken later.<br />
<br />
== Email Verification ==<br />
<br />
To prevent fixation attacks, we require new accounts to verify their configured recovery email address before letting them learn the generated keys or obtain a signed certificate. Nevertheless, we wish clients to forget the user's password while they wait for email verification to complete. To achieve this, clients can obtain a sessionToken before verification, but most APIs that require it will raise errors until verification is finished.<br />
<br />
The server will send email with a URL that contains a long random "verification code" in the "fragment" hash. This URL points to a static page with some javascript that submits the code to the "POST /account/recovery_methods/verify_code" API. The URL can be clicked by any browser (it is not bound to anything), and when the API is hit, the account is marked as verified.<br />
<br />
After the client submits /account/create, it performs the "/session/auth" login sequence below to obtain a sessionToken. It then polls the "GET /account/recovery_methods" (which requires a sessionToken but not account verification) until the user clicks the email link and the API reports verification is complete. Then the client uses "GET /account/keys" and "POST /certificate/sign", described below, to obtain kA, kB, and a signed certificate to talk to the storage server.<br />
<br />
= Login: Obtaining the authToken =<br />
<br />
To connect a browser to an existing account, we use the following login protocol to transform an email+password pair into a single-use authToken. We will use this in the next section to create a session, from which we can obtain encryption keys and signed certificates.<br />
<br />
This protocol starts by using key-stretching to transform the email+password into a "stretchedPW", then feeds this into an SRP protocol to get the authToken.<br />
<br />
[[File:PICL-IdPAuth-auth-start.png|IdP Auth Protocol]]<br />
<br />
This authToken can be used (once) to do one of the following:<br />
<br />
* /session/create: obtain a sessionToken (and keyFetchToken), which enables storage server access<br />
* /password/change: obtain an accountResetToken, to safely reset the account password<br />
* /account/delete: to delete the entire account<br />
<br />
The protocol is designed to enable parallelism between key-stretching and the initial network messages, to reduce the time it takes to connect a browser to the account. In total, the browser requires five messages in four roundtrips (1: /auth/start, 2: /auth/finish, 3: /session/create, 4: /account/keys and /certificate/sign in parallel) before it is ready to talk to the storage server.<br />
<br />
== /auth/start ==<br />
<br />
As soon as the user finishes typing in the email address, the client should send it in the "/auth/start" 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 loginSRPToken that is used to associate this request with the subsequent /auth/finish 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.<br />
<br />
To mitigate DoS abuse, /auth/start may also require a proof-of-work string, described below.<br />
<br />
== Client-Side Key Stretching ==<br />
<br />
"Key Stretching" is the practice of running a password through a computationally-expensive one-way function before using it for encryption or authentication. The goal is to make brute-force dictionary attacks more expensive, by raising the cost of testing each guess.<br />
<br />
To protect the user's class-B data against compromises of our keyserver, we perform this key stretching on the client, rather than on the server. We use the memory-hard "scrypt" function (pronounced "ess-crypt") for this purpose, as motivated by the attacker-cost studies in [[Identity/CryptoIdeas/01-PBKDF-scrypt]]. Slower (low-RAM) devices can still participate by outsourcing the scrypt step to an "scrypt helper". To provide at least minimal protection against a compromise of this helper, we sandwich the scrypt step between two PBKDF2 steps. The complete protocol looks like this:<br />
<br />
[[File:PICL-IdPAuth-stretch-KDF.png|Stretching KDF]]<br />
<br />
Our initial parameter choices for this stretching are to use 20000 iterations of PBKDF2 on each side (40k in all), and to run scrypt with N/r/p set to 64k/8/1. This requires roughly 60MB of RAM for about 1.3 seconds on a 1.8GHz Intel Atom linux box (it will run faster, but use the same memory, on more modern CPUs). We are still studying performance on various platforms to decide what parameters to use in V1: lowering them speeds up the login process, but reduces security (by reducing the cost of an dictionary attack).<br />
<br />
Since the stretching is expected to take a second or two, the client can optimistically start this process (using default parameters) before receiving the auth/start 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 "mainSalt" is added *after* the stretching, to enable this parallelism (at a tiny cost in security).<br />
<br />
After "stretchedPW" is derived, a second HKDF call is used to derive "srpPW" and "unwrapBKey" which will be used later.<br />
<br />
[[File:PICL-IdPAuth-main-KDF.png|masterKey KDF]]<br />
<br />
== SRP Protocol Details ==<br />
<br />
The PiCL client uses the SRP protocol (http://srp.stanford.edu/) to prove that it knows the (stretched) account password without revealing the actual password (or information enabling a brute-force attack) to the server or any eavesdroppers. <br />
<br />
SRP is somewhat underspecified. We use SRP-6a, with SHA256 as the hash, and the 2048-bit modulus defined in RFC 5054 Appendix A. We consistently zero-pad all string values to 256 bytes (2048 bits), and use H(A+B+S) as the key-confirmation message "M1". These details, plus the SRP design papers and RFCs 2945 and 5054, should be enough to build a compatible implementation. The diagrams below and the test vectors at the end of this page can be used to verify compatibility.<br />
<br />
The server should use Jed's SRP module from https://github.com/jedp/node-srp . The client might use SJCL (http://crypto.stanford.edu/sjcl/) or native code (NSS).<br />
<br />
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).<br />
<br />
Note that SRP-6a uses a "k" value which basically encodes the group being used ("N" and "g"). Since all PICL accounts use the same 2048-bit group, they will all use the same "k" value (not to be confused with the per-session shared-secret "K" key that emerges from the protocol). This group's "k" integer is (as a base-10 number):<br />
2590038599070950300691544216303772122846747035652616593381637186118123578112<br />
<br />
=== SRP Verifier Calculation ===<br />
<br />
When the client first creates the account, it must combine the account email address, the stretched password (srpPW), and a randomly-generated srpSalt, to compute the srpVerifier. The server will use this verifier later, to check whether or not the client really knows the password.<br />
<br />
If the server is compromised and an attacker learns the srpVerifier for a given account, they cannot use this to directly log in (the verifier is not "password-equivalent"), but it does allow them to perform an offline brute-force attack against the user's password. In this respect, it is similar to a traditional hashed password. We make these attacks somewhat more expensive by performing the client-side stretching described above, instead of using the raw user password in the SRP calculation.<br />
<br />
[[File:PICL-IdPAuth-SRP-Verifier.png|client-side SRP Verifier calculation]]<br />
<br />
Test vectors are provided at the end of this page. The protocol requires that several values ("a", "b", "srpSalt") are chosen randomly, but for illustrative purposes, the test vectors use carefully-crafted non-random values. For the user's sake, please ensure implementations use proper random values instead of simply copying the test vectors.<br />
<br />
The client sends srpSalt and srpVerifier to the server when it creates the account. It will also re-compute the 'x' value (as an integer) during sign-in. The server will convert the srpVerifier string back into an integer ('v') for use during its own sign-in calculations.<br />
<br />
=== SRP Server-side Sign-In Flow ===<br />
<br />
When the user connects a new device to their account, they use the /auth/start API to start the SRP protocol. This sends the account email address to the server. The server looks up the stored srpVerifier for this account, creates a random 'b' integer, performs some math to compute the "B" number, then converts B into a string known as "srpB". "srpB" is returned to the client, along with srpSalt and the key-stretching parameters. "b" and "srpB" are retained for the subsequent /auth/finish call. '''Note that it is critical that the "b" integer remain secret on the server.''' The server also allocates a random loginSRPToken to connect the two calls.<br />
<br />
[[File:PICL-IdPAuth-SRP-Server.png|server-side SRP]]<br />
<br />
Later, /auth/start will be called with the client's srpA string and its M1 key-confirmation string. srpA is combined with srpB to calculate the "u" integer. srpA is also turned into an integer and used to compute the shared-secret "S" integer. S is then used to compute the shared key "srpK", which is the output of the SRP process.<br />
<br />
=== SRP Client Calculation ===<br />
<br />
While the client is waiting for the response to /auth/start, it begins its key-stretching calculations. Everything else must wait until the response to /auth/start arrives, which includes the key-stretching parameters (which are retroactively confirmed), srpSalt, and the server's generated srpB value.<br />
<br />
[[File:PICL-IdPAuth-SRP-Client.png|client-side SRP]]<br />
<br />
Once the client knows srpSalt, it computes the same "x" integer as it did in the middle of the srpVerifier calculation. It also converts srpB into an integer named "B". Then it creates a random "a" integer, uses it to compute the string "srpA", then combines srpA with the server's srpB to compute the "u" integer. It then combines the static "k", the password-derived "x", the combined "u", and the server's "B", together with some magic math, to derive the "S" integer. If everything went well, the client will compute the same "S" value as the server did. If not (the password was wrong, or the client is talking to a fake server that doesn't really know srpVerifier), then the two "S" values will not match.<br />
<br />
('''Again, it is critical that the client keep its "a" and "x" integers secret, both during and after the protocol run.''')<br />
<br />
To safely tell if the "S" values match, both client and server combine srpA, srpB, and their (independently) generated "S" strings to form a string named "M1". The client sends M1 (along with srpA) in the /session/auth/finish message. The server compares the client's copy of M1 against its own. If they match, the client knew the password and the server can safely respond with the encrypted account data. If they do not match, the client (or a man-in-the-middle attacker) did not know the password, and the client should increment a counter that can trigger defenses against online guessing attacks. The server must then return an error to the client, and '''not''' use or reveal srpK (or the correct M1) in any way.<br />
<br />
Both client and server also hash "S" into "srpK". This is the shared session key, from which specific message encryption and MAC keys are derived (as described below).<br />
<br />
=== SRP Notes ===<br />
<br />
The SRP "g" (generator) and "N" (prime modulus) should use the 2048-bit value from RFC 5054 Appendix A. 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.<br />
<br />
There are several places in SRP where integers (usually in the range 1..N-1) are converted into bytestrings, either for transmission over a wire, or to be passed into a hash function. The SRP spec is somewhat ambiguous about padding here: if the integer happens to be less than 2^2040, the simplest toString() approach will yield a '''255''' byte string, not a 256 byte string. PiCL consistently uses padding, so compatible implementations must prepend one or more NUL bytes to these short strings before transmission or hashing. The test vectors below were brute-forced to ensure that "srpVerifier", "srpA", "srpB", and "S" all wind up with leading zeros, to exercise the padding code in compatible implementations. If you are having problems getting your code to match these results, add some assertions to test that the stringified integers being put into hashes are exactly 256 bytes long.<br />
<br />
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 (/auth/finish).<br />
<br />
The server receives "A" in /auth/finish, 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 keyFetchToken+sessionToken as described below, returning the encrypted/MACed bundle in the response to /auth/finish.<br />
<br />
On the server, it is critical to reject an "A" value that is 0, or some other multiple of N. If the server does not check this, anybody can trivially sign in to any account without knowing the password. Likewise, it is critical for the client to reject a "B" value where B%N==0. If the client does not check this, the server (or an attacker pretending to be the server) will get a value that can be used in an offline brute-force search for the user's password.<br />
<br />
Outstanding crypto questions:<br />
<br />
* How exactly should the "a" and "b" integers be generated? The issue is of how much bias SRP can tolerate. Ideally these integers are uniformly distributed from 1 to N-1 (inclusive). The only way to obtain a purely uniform distribution from a source of random bytes is try-try-again: pick a (integral-number-of-bytes) number, compare it to the desired range, try again if it falls outside the range. If you get unlucky, this can take a lot of guesses, depending upon how close the range is to a power of two. ECDSA implementations tend to pick a number twice as long as the modulus and then modulo it down (rand(2^4096) % N), which yields a tiny fraction of a bit of bias. The cheaper approach is to do the same with a number of equal length (rand(2^2048) % N), which imposes more bias. Some SRP implementations appear to be satisfied by rand(2^256). We need more review here.<br />
* The original SRP papers defined M1=H(A+B+S) as we use here, but other implementation (in particular RFC2945) uses a curious construct that includes the username, the salt, and an odd XOR combination of N and g. We need to decide what to use for M1.<br />
<br />
== /auth/finish ==<br />
<br />
The client-side SRP calculation results in two values that are sent to the server in the /auth/finish 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.<br />
<br />
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.<br />
<br />
The server then allocates a single-use 32-byte random token named "authToken". It encrypts the token with the session key, and returns a success message with the encrypted bundle.<br />
<br />
All tokens have an associated tokenID, described below. The server needs to maintain a table that maps the tokenID to the token itself, so it can derive other values from the token later. The tokens are also associated with a specific account, so later API requests do not specify an email address or account ID.<br />
<br />
== Decrypting the /auth/finish Response ==<br />
<br />
The SRP session key ("srpK") is used to derive two other keys: respHMACkey and respXORkey.<br />
<br />
[[File:PICL-IdPAuth-encrypt-authToken.png|Decrypting the authToken]]<br />
<br />
The respXORkey is used to encrypt the authToken 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.<br />
<br />
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 returns the authToken value.<br />
<br />
= After Login: Using the authToken =<br />
<br />
After the authToken is acquired, the client can create a session and fetch the encryption keys. The high-level flow looks like this:<br />
<br />
[[File:PICL-IdPAuth-session-start.png|Using the authToken]]<br />
<br />
= Creating a Session =<br />
<br />
For login, the single-use authToken is spent on a call to /session/create .<br />
This allocates two new (random 32-byte) tokens: a long-lived "sessionToken", and a single-use "keyFetchToken". The /session/create call returns an encrypted bundle containing the two tokens.<br />
<br />
[[File:PICL-IdPAuth-encrypt-sessionToken.png|Decrypting the sessionToken and keyFetchToken]]<br />
<br />
For calls which accept an authToken, the client uses authToken to derive three values:<br />
<br />
* tokenID<br />
* reqHMACkey<br />
* requestKey<br />
<br />
<br />
The client uses tokenID and reqHMACkey for a HAWK (https://github.com/hueniverse/hawk/) request to the "POST /session/create" API, using tokenID as "credentials.id" and reqHMACkey as "credentials.key". The server uses tokenID to look up the corresponding token, then derives reqHMACkey to validate the request.<br />
<br />
Each authToken-using call then derives additional API-specific values from requestKey. /session/create uses two derived values:<br />
<br />
* respHMACkey<br />
* respXORkey<br />
<br />
<br />
When the server receives a valid /session/create request, it allocates sessionToken and keyFetchToken, concatenates them, encrypts the pair by XORing it with the derived respXORkey, and attaches a MAC generated with respHMACkey. The encrypted MACed bundle is returned to the client.<br />
<br />
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 keyFetchToken and sessionToken values.<br />
<br />
Each authToken is single-use: once a successful request has been made with it, the authToken and its corresponding ID is removed from the server's memory, and subsequent attempts to use it will return a "no such token" error. The token is consumed even if the request fails (e.g. the MAC did not match).<br />
<br />
The authToken can be used (once) by multiple APIs. The server only needs to maintain one table mapping tokenID to authToken, because all these APIs share the same method of deriving a tokenID and reqHMACkey from the authToken.<br />
<br />
When a HAWK request appears, it should look up the tokenID in this table, retrieve the authToken, recompute reqHMACkey and requestKey, validate the request, delete the table entry, then hand the HTTP request and requestKey to the specific API handler. That handler should derive the API-specific values (respHMACkey, respXORkey, etc) to process the request or construct the response.<br />
<br />
The server can support multiple sessions per account (typically one per client device, plus perhaps others for account-management portals). There can also be multiple outstanding keyFetchTokens. The sessionToken lasts forever (until revoked by a password change or explicit revocation command), and can be used an unlimited number of times. The keyFetchToken expires after 60 seconds, and is single-use.<br />
<br />
= Obtaining keys kA and kB =<br />
<br />
Clients which have exchanged an authToken for either a sessionToken or an accountResetToken will also receive a keyFetchToken. This single-use token allows the client to retrieve kA and wrap(kB), which enables the client to encrypt and decrypt browser data (bookmarks, open-tabs, etc) correctly. As above, the keyFetchToken is used to derive tokenID, reqHMACkey, respHMACkey, and respXORkey, which are used in a HAWK request to the "GET /account/keys" API.<br />
<br />
The server pulls kA and wrap(kB) from the account table, concatenates them, encrypts the pair by XORing it with the derived respXORkey, and attaches a MAC generated with respHMACkey.<br />
<br />
[[File:PICL-IdPAuth-keys-server.png|keyFetchToken: server encrypts keys]]<br />
<br />
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 and wrap(kB) values.<br />
<br />
[[File:PICL-IdPAuth-keys-client.png|keyFetchToken: client decrypts keys]]<br />
<br />
Finally, the server-provided wrap(kB) value is simply XORed with the password-derived unwrapBKey (both are 32-byte strings) to obtain kB. There is no MAC on wrap(kB).<br />
<br />
[[File:PICL-IdPAuth-key-unwrap.png|unwrapping kB]]<br />
<br />
"kA" and "kB" enable the browser to encrypt/decrypt synchronized data records. They will be used to derive separate encryption and HMAC keys for each data collection (bookmarks, form-fill data, saved-password, open-tabs, etc). This will allow the user to share some data, but not everything, with a third party. The client may intentionally forget kA and kB (only retaining the derived keys) to reduce the power available to someone who steals their device.<br />
<br />
Note that /account/keys will not succeed until the account's email address has been verified. Also note that each keyFetchToken is single-use and short-lived. The token is consumed even if the request fails (e.g. the MAC does not match).<br />
<br />
= Signing Certificates =<br />
<br />
The sessionToken is used to derive two values:<br />
<br />
* tokenID<br />
* request HMAC key<br />
<br />
<br />
[[File:PICL-IdPAuth-use-session.png|Using the sessionToken, signing certificates]]<br />
<br />
The requestHMACkey is used in a HAWK request to provide integrity over many APIs, including /certificate/sign. requestHMACkey 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.<br />
<br />
For /certificate/sign, it is critical to enable payload verification by setting options.payload=true (on both client and server). Otherwise a man-in-the-middle could submit their own public key, get it signed, and then delete the user's data on the storage servers.<br />
<br />
The following keyserver APIs require a HAWK-protected request that uses the sessionToken. In addition, some require that the account be in the "verified" state:<br />
<br />
* GET /account/devices<br />
* POST /session/destroy<br />
* GET /recovery_email/status<br />
* POST /recovery_email/resend_code<br />
* POST /certificate/sign (requires "verified" account)<br />
<br />
= Resetting The Account =<br />
<br />
The account may be reset in two circumstances: when the user changes their password, and when the user forgets their password. In both cases, the client first obtains an "accountResetToken". This token is then used to change the SRP Verifier and either reset or replace the wrap(kB) value.<br />
<br />
== Changing the Password ==<br />
<br />
When the user wishes to change their password (i.e. they still know the old password), they first use the /auth/start+/auth/finish SRP protocol safely obtain an "authToken". They they use the "/password/change/start" API to exchange the authToken for an accountResetToken and a keyFetchToken.<br />
<br />
[[File:PICL-IdPAuth-encrypt-passwordChange.png|Server encrypts passwordChange response]]<br />
<br />
The accountResetToken will be used below to set the new password safely. The keyFetchToken should be used first, to obtain kB, so the subsequent account reset can replace wrap(kB) with a new value. This allows the password-changing client to retain their class-B data.<br />
<br />
Requiring an authToken proves that the user has provided the correct account password recently. When the account is reset, all active sessions and tokens will be cancelled (disconnecting all devices from the account). The client should immediately establish a new session as described above.<br />
<br />
This API is only used when the user knows their old password: if they have forgotten the password, use the "/password/forgot" APIs below.<br />
<br />
== Handling a Forgotten Password ==<br />
<br />
When the user has forgotten their password, they can use one of their "recovery methods" to obtain an accountResetToken. For now, this means we send a random code to the email address associated with their account. The user must copy this code from the email into their client, whereupon the client will get an accountResetToken that can be delivered to the API below.<br />
<br />
Note that, since the forgotten-password client never learns kB, any class-B data will be lost. This is necessary to protect class-B data from attackers who can read the users's email but do not know the account password (including those who compromise the IdP and the keyserver itself). When using /account/reset below, they will set wrap(kB) to a string of all zeros, which means the server should generate a new random wrap(kB) (just as it does during account creation).<br />
<br />
The /password/forgot/send_code API is used to ask the server to send a recovery code. This takes a recovery method, which for now is just an email address. This API is unauthenticated (after all, the user who has forgotten their password knows nothing but their email address). The server marks the corresponding account as "pending recovery", allocates a random forgotPasswordToken for the account, creates a recovery code, and sends the code (with instructions) via email. The API returns forgotPasswordToken to the client.<br />
<br />
The user must copy the recovery code into the same browser where they started the process. The client then submits the code to /password/forgot/verify_code along with the forgotPasswordToken they received. If they match, the server allocates a accountResetToken and returns it to the client. If they do not match, the server increments a counter (which is used to decide if an online guessing attack is happening).<br />
<br />
forgotPasswordToken can be used three times before it is exhausted. If the user guesses incorrectly this often, the client must call send_code again to get a new token and code. Each account has at most one token+code active at a time.<br />
<br />
The recovery code is initially a random 8-digit decimal number. If an attacker tries to sign in as someone else, hits the "forgot my password" button, then submits a guess to /password/forgot/verify_code, they will have a 1-in-100-million chance of success. If the server detects too many wrong guesses, it should increase the length of new codes. Another defensive technique is to require that users click an email link before being given the code: the server is told when the link is clicked, so the code will not be enabled until the email has been read. It remains to be seen whether this will be sufficient.<br />
<br />
The exact thresholds are TBD, but a nominal goal is to keep the chances of any attack succeeding to below 1-in-a-million per year. To achieve this, we can tolerate 100 verify_code failures in a single year before we must increase the length of the code.<br />
<br />
== Using accountResetToken ==<br />
<br />
An accountResetToken, obtained through either of the methods above, is used to invoke the /account/reset API. If the request is accepted, the server replaces the account password with a new one, updates the wrap(kB) value, cancels all active sessions and tokens (disconnecting all devices from the account), and sends a "your password has been changed" email to the user.<br />
<br />
If the client knew the old password, it can supply a new wrap(kB) value that will yield the same kB key as before, so no data will be lost. If the client did *not* know the old password, then it will supply a wrap(kB) of all-zeros, which tells the server to generate a new random wrap(kB) (just like during account creation), which means the class-B data will be lost.<br />
<br />
The client puts their new password through the same stretching procedure as described in the new-account section above, resulting in a new srpVerifier and unwrapBKey. If they knew the old kB, they XOR it with the new unwrapBKey to obtain a new wrap(kB).<br />
<br />
/account/reset needs request confidentiality, since the arguments include the newly wrapped kB value and the new SRP verifier, both of which enable a brute-force attack against the password. HAWK provides request integrity. The response is a single "ok" or "fail", conveyed by the HTTP headers, so we do not require response confidentiality, and can live without response integrity.<br />
<br />
So the single-use resetToken is used to derive three values:<br />
<br />
* tokenID<br />
* request HMAC key<br />
* request XOR key<br />
<br />
<br />
[[File:PICL-IdPAuth-encrypt-resetAccount.png|Client encrypts resetAccount request]]<br />
<br />
The request data will contain wrap(kB) and the new SRP verifier, concatenated together. Since we always pad the SRP verifier to the full (256-byte) group length, both pieces are fixed-length. We generate enough reqXORkey bytes to cover both values.<br />
<br />
The request data is XORed with requestXORkey, then delivered in the body of a HAWK request that uses tokenID as credentials.id and requestHMACkey as credentials.key . Note: it is critical to include the request body in the HAWK integrity check (options.payload=true, on both client and server), otherwise a man-in-the-middle could substitute their own SRP verifier, giving them control over the account (access to the user's class-A data, and a brute-force attack on their password).<br />
<br />
The client submits other values in the same request:<br />
<br />
* stretchParams<br />
* mainKDFSalt<br />
* srpSalt<br />
<br />
<br />
These values do not require confidentiality, so are not included in the encrypted bundle. They are still protected by the HAWK integrity check. Note that the server should assert that both salts are different than the previously stored values.<br />
<br />
After using /account/reset, clients should immediately perform the login protocol from above. If the old password was forgotten, this is necessary to fetch kA. In either case, a new sessionToken is required, since old sessions and tokens are revoked by /account/reset. Clients should retain the new srpPassword value during this process to avoid needing to run the lengthy key-stretching routine a second time.<br />
<br />
= Deleting The Account =<br />
<br />
When the user wishes to completely delete their account, the browser needs to perform two actions:<br />
<br />
* contact the storage servers and delete all records and collections<br />
* contact the keyserver and delete the account information<br />
<br />
The user should be prompted for their password as confirmation (i.e. a browser in the normal attached-and-synchronizing state should not be able to erase the account information: it must acquire a new authToken first).<br />
<br />
The device then obtains an authToken as described above, then spends it on a HAWK-protected request to the /account/destroy endpoint. This request contains no body and returns only a success code.<br />
<br />
[[File:PICL-IdPAuth-deleteAccount.png|Deleting the Account]]<br />
<br />
= Crypto Notes =<br />
<br />
Strong entropy is needed in the following places:<br />
<br />
* (client) creation of private "a" value inside SRP<br />
* (server) initial creation of kA and wrap(kB)<br />
* (server) creation of private "B" value inside SRP<br />
* (server) creation of signToken and resetToken<br />
<br />
<br />
On the server, code should get entropy from /dev/urandom via a function that uses it, like "crypto.randomBytes()" in node.js or "os.urandom()" in python. On the client, code should combine local entropy with some fetched from the keyserver via getEntropy(), to guard against failures in the local entropy pool. Something like HKDF(IKM=localEntropy+remoteEntropy, salt="", info=KW("mergeEntropy")).<br />
<br />
An HKDF-based stream cipher is used to protect the response for getToken2(), and the request for resetAccount(). HKDF is used to create a number of random bytes equal to the length of the message, then these are XORed with the plaintext to produce the ciphertext. An HMAC is then computed from the ciphertext, to protect the integrity of the message.<br />
<br />
HKDF, like any KDF, is defined to produce output that is indistinguishable from random data ("The HKDF Scheme", http://eprint.iacr.org/2010/264.pdf , by Hugo Krawczyk, section 3). XORing a plaintext with a random keystream to produce ciphertext is a simple and secure approach to data encryption, epitomized by AES-CTR or a stream cipher (http://cr.yp.to/snuffle/design.pdf). HKDF is not the fastest way to generate such a keystream, but it is safe, easy to specify, and easy to implement (just HMAC and XOR).<br />
<br />
Each keystream must be unique. SRP is defined to produce a random session key for each run (as long as at least one of the sides provides a random ephemeral key). We define resetToken to be a single-use randomly-generated value. Hence our two HKDF-XOR keystreams will be unique.<br />
<br />
A slightly more-traditional alternative would be to use AES-CTR (with the same HMAC-SHA256 used here), with a randomly-generated IV. This is equally secure, but requires implementors to obtain an AES library (with CTR mode, which does not seem to be universal). An even more traditional technique would be AES-CBC, which introduces the need for padding and a way to specify the length of the plaintext. The additional specification complexity, plus the library load, leads me to prefer HKDF+XOR.<br />
<br />
kB is equal to the XOR of wrapKey (which is a deterministic function of the user's email address, password, mainSalt, and the stretching parameters) and the server's randomly-generated wrap(kB) value, making kB a random value too. Using XOR as a wrapping function allows us to avoid sending kB or wrap(kB) in the initial createAccount arguments, which are not protected by SRP, and thus would enable an eavesdropper to mount a dictionary attack on the password (using wrap(kB) as their oracle).<br />
<br />
Likewise, allowing the server to generate kA avoids exposure during createAccount. The only point of vulnerability is a forgotten-password resetAccount() message, if an eavesdropper can learn the resetToken. In this case, the attacker might either use the resetToken themselves (then use signToken to learn kA and wrap(kB)), or passively decrypt the resetAccount() arguments to retrieve just wrap(kB). Given wrap(kB) and some encrypted browser data, the attacker can guess passwords (and derive kB, etc) until the data decrypts properly.<br />
<br />
To make this technique safe, any time kB or the password is changed, the mainSalt should be changed too. Otherwise knowledge of both wrap(old-kB) and old-kB would reveal wrapKey, making it easy to deduce the new kB. Changing mainSalt causes wrapKey to change too, preventing this.<br />
<br />
mainSalt is incorporated at the end of the stretching process to allow it to proceed in parallel with the getToken1() call that retrieves the salt. The inputs to the lengthy stretch come entirely from the user (email and password) or are optimistically (stretching parameters). This speedup seems more important than the minor security benefit of including the salt at the beginning of the stretch.<br />
<br />
There is no MAC on wrap(kB). If the keyserver chooses to deliver a bogus wrap(kB) or kA, the client will discover the problem a moment later when it talks to a storage server and attempts to retrieve data from an unrecognized collection-ID (since we intend to derive collection-IDs from the key used to encrypt their data, which will be derived from kA or kB as appropriate). It might be useful to add a checksum to kA and wrap(kB) to detect accidental corruption (e.g. store and deliver kA+SHA256(kA)), but this doesn't protect against intentional changes. We omit this checksum for now, assuming that disks will be reliable enough to let us never experience such failures.<br />
<br />
We use scrypt without a per-user salt. This is safe because the "password" input to scrypt is already diversified by the user's email address. The intention is to allow the scrypt-helper to run on anonymous data, so that an attacker who compromises this helper cannot easily learn the email addresses of the partially-stretched K1 values that it is receiving, confounding their attack.<br />
<br />
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. <br />
For /certificate/sign, we do not need request confidentiality or response confidentiality, since the client's pubkey and the resulting certificate will both be exposed over a similar SSL connection to the storage server later. And it is sufficient to rely on the response integrity provided by SSL, since the client can verify the returned certificate for itself. For the other keyserver APIs protected by HAWK, these properties are either unnecessary, or are provided by additional mechanisms.<br />
<br />
= Proof-Of-Work =<br />
<br />
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(). The server can control how much work is required.<br />
<br />
The getToken1() call looks for a "X-PiCL-PoW:" HTTP header. Most of the time, clients don't supply this header. But if the server responds to the getToken1() call with an error that indicates PoW is required, clients must create a valid PoW string and include it as the value of an "X-PiCL-PoW:" header in their next call to getToken1().<br />
<br />
The server's error message includes two parameters. The first is a "prefix string": the client's PoW string is required to begin with this prefix. The second is a "threshold hash". SHA256(PoWString) is required to be lexicographically earlier than the thresholdHashString (i.e. the numerical value of its hash must be closer to zero than the threshold). The client is expected to concatenate the prefix with a counter, then repeatedly increment the counter and hash the result until they meet the threshold, then re-submit their getToken1() request with the combined prefix+counter string in the header. If the client has spent more than e.g. 10 seconds doing this, the client should probably help the user cancel the operation and try again.<br />
<br />
When a server is under a DoS attack (either via some manual configuration tool or sensed automatically), it should start requiring valid unique X-PiCL-PoW headers. The server should initially require very little work, by using a threshold hash with just a few leading zero bits. If this is insufficient to reduce the attack volume, the threshold should be lowered, requiring even more work (from both the attacker and legitimate clients).<br />
<br />
The server should create a prefix string that contains a parseable timestamp and a random nonce (e.g. "%d-%d-" % (int(time.time()), b32encode(os.urandom(8)))). The server should also decide on a cutoff time (perhaps ten minutes ago). Each server must then maintain a table of "old PoW strings" to prevent replay attacks (these do not need to be shared among all servers: an in-RAM cache is fine).<br />
<br />
When the server receives a proposed PoW string, it first splits off the leading timestamp, and if the timestamp is older than the cutoff time, it rejects the string (either by dropping the connection, or returning a new "PoW required" error if it's feeling nice). Then it hashes the whole string and compares it against the threshold, rejecting those which fail to meet the threshold. Finally, for strings that pass the hash threshold, it checks the "old strings" table, and rejects any that appear on that list.<br />
<br />
If the PoW string makes it past all these checks, the server should add the string to the "old strings" table, then accept the request (i.e. compute an srpB value and add a session-id table entry for the request).<br />
<br />
The old-strings table check should be optimized to reject present strings quickly (i.e. if we are under attack, we should expect to see lots of duplicates of the same string, and must minimize the work we do when this occurs).<br />
<br />
The server can remove values from the old-strings table that have timestamps older than the cutoff time. The server can also discard values at other times (to avoid consuming too much memory), without losing anything but protection against resource consumption.<br />
<br />
Other notes:<br />
<br />
The server-side code for this can be deferred until we care to have a response to a DoS attack. However the client-side code for this must be present from day one, otherwise we won't be able to turn on the defense later without fear of disabling legitimate old clients.<br />
<br />
The server should perform as little work as possible before rejecting a token. Every extra CPU cycle it spends in this path is increasing the DoS attack amplification factor.<br />
<br />
The nonce in the prefix string exists to make sure that two successive clients get different prefixes, and thus do not come up with the same counter value (and inadvertently create identical strings, looking like a replay attack). If this proved annoying or expensive, we could instead obligate clients to produce their own nonce.<br />
<br />
TBD: Is this worth it? Should the PoW string go into an HTTP header? (I want it to be cheap to extract, and not clutter logs). Should the error response be a distinctive HTTP error code so our monitoring tools can easily count them? We can also use this feature to slow down online guessing attacks (i.e. trigger it either when getToken1 is called too much or when getToken2 produces too many errors). Since getToken1() includes an email address, we could also requires PoWs for some addresses (e.g. those we know to be under attack) but not others.<br />
<br />
= Glossary =<br />
<br />
This defines some of the jargon we've developed for this protocol.<br />
<br />
* data classes: each type of browser data (bookmarks, passwords, history, etc) can be assigned, by the user, to either class-A or class-B<br />
* class-A: data assigned to this class can be recovered, even if the user forgets their password, by proving control over an email address and resetting the account. It can also be read by Mozilla (since it runs the keyserver and knows kA), or by the user's IdP (by resetting the account without the user's permission).<br />
* class-B: data in this class cannot be recovered if the password is forgotten. It cannot be read by the IdP. Mozilla (via the keyserver) cannot read this data, but can attempt a brute-force dictionary attack against the password.<br />
* kA: the master key for data stored as "class-A", a 32-byte binary string. Individual encryption keys for different datatypes are derived from kA.<br />
* kB: the master key for data stored as "class-B", a 32-byte binary string.<br />
* wrap(kB): an encrypted copy of kB. The keyserver stores wrap(kB) and never sees kB itself. The client (browser) uses a key derived from the user's password to decrypt wrap(kB), obtaining the real kB.<br />
* sessionToken: a long-lived per-device token which allows the device to obtained signed BrowserID certificates for the account's identity (GUID@picl-something.org). This token remains valid until the user revokes it (either by changing their password, or triggering some kind of "revoke a specific device" or "revoke all devices" function).<br />
<br />
= Test Vectors =<br />
<br />
The following example uses a non-ASCII email address of "andré@example.org" (with an accented "e", UTF8 encoding is 616e6472c3a9406578616d706c652e6f7267) and a non-ascii password of "pässwörd" (with accents on "a" and "o", UTF8 encoding is 70c3a4737377c3b67264).<br />
<br />
These test vectors were produced by the python code in https://github.com/warner/picl-spec-crypto (revision aa441c6). The diagrams above may lag behind the latest version of that code.<br />
<br />
== stretch-KDF ==<br />
<br />
email:<br />
616e6472c3a94065<br />
78616d706c652e6f<br />
7267<br />
<br />
password:<br />
70c3a4737377c3b6<br />
7264<br />
<br />
K1 (scrypt input):<br />
f84913e3d8e6d624<br />
689d0a3e9678ac8d<br />
cc79d2c2f3d96414<br />
88cd9d6ef6cd83dd<br />
<br />
K2 (scrypt output):<br />
5b82f146a6412692<br />
3e4167a0350bb181<br />
feba61f63cb17140<br />
12b19cb0be0119c5<br />
<br />
stretchedPW:<br />
c16d46c31bee242c<br />
b31f916e9e38d60b<br />
76431d3f5304549c<br />
c75ae4bc20c7108c<br />
<br />
== main-KDF ==<br />
<br />
mainSalt (normally random):<br />
00f0000000000000<br />
0000000000000000<br />
0000000000000000<br />
000000000000034d<br />
<br />
srpPW:<br />
00f9b71800ab5337<br />
d51177d8fbc682a3<br />
653fa6dae5b87628<br />
eeec43a18af59a9d<br />
<br />
unwrapBKey:<br />
6ea660be9c89ec35<br />
5397f89afb282ea0<br />
bf21095760c8c500<br />
9bbcc894155bbe2a<br />
<br />
internal x (base 10):<br />
8192518690918<br />
99580124814080709381476194749939<br />
03899664126296984459627523279550<br />
<br />
internal x (hex):<br />
b5200337cc3f3f92<br />
6cdddae0b2d31029<br />
c069936a844aff58<br />
779a545be89d0abe<br />
<br />
v (verifier as number) (base 10):<br />
114649<br />
57230405843056840989945621595830<br />
71784395917725741221739574165799<br />
54316134303691657140298181419198<br />
87853709633756255809680435884948<br />
69849281177012209169281795507853<br />
57610332070005048463659745521969<br />
83218225819721112680718485091921<br />
64608360806562626442477160609654<br />
43167308814558974899899506977051<br />
96721477608178869100211706638584<br />
53875100985456239693728258285562<br />
04889672594983678412848291529879<br />
88548996842770025110751388952323<br />
22170663943486107183421205517476<br />
84831590615660554713667726412525<br />
73641352721966728239512914666806<br />
49625530438034148797508015907639<br />
67594925530663571631035463732161<br />
30193328802116982288883318596822<br />
<br />
== SRP Verifier ==<br />
<br />
k (base 10):<br />
259003859907<br />
09503006915442163037721228467470<br />
35652616593381637186118123578112<br />
<br />
srpSalt (normally random):<br />
00f1000000000000<br />
0000000000000000<br />
0000000000000000<br />
0000000000000179<br />
<br />
srpVerifier:<br />
00173ffa0263e63c cfd6791b8ee2a40f<br />
048ec94cd95aa8a3 125726f9805e0c82<br />
83c658dc0b607fbb 25db68e68e93f265<br />
8483049c68af7e82 14c49fde2712a775<br />
b63e545160d64b00 189a86708c69657d<br />
a7a1678eda0cd79f 86b8560ebdb1ffc2<br />
21db360eab901d64 3a75bf1205070a57<br />
91230ae56466b8c3 c1eb656e19b794f1<br />
ea0d2a077b3a7553 50208ea0118fec8c<br />
4b2ec344a05c66ae 1449b32609ca7189<br />
451c259d65bd15b3 4d8729afdb5faff8<br />
af1f3437bbdc0c3d 0b069a8ab2a959c9<br />
0c5a43d42082c774 90f3afcc10ef5648<br />
625c0605cdaace6c 6fdc9e9a7e6635d6<br />
19f50af773452247 0502cab26a52a198<br />
f5b00a2798589165 07b0b4e9ef9524d6<br />
<br />
== SRP B ==<br />
<br />
private b (normally random) (base 10):<br />
1198277<br />
66042000957856349411550091527197<br />
08125226960544768993643095227800<br />
29105361555030352774505625606029<br />
71778328003253459733139844872578<br />
33596964143417216389157582554932<br />
02841499373672188315534280693274<br />
23189198736863575142046053414928<br />
39548731879043135718309539706489<br />
29157321423483527294767988835942<br />
53343430842313006332606344714480<br />
99439808861069316482621424231409<br />
08830704769167700098392968117727<br />
43420990997238759832829219109897<br />
32876428831985487823417312772399<br />
92628295469389578458363237146486<br />
38545526799188280210660508721582<br />
00403102624831815596140094933216<br />
29832845626116777080504444704039<br />
04739431335617585333671378812943<br />
<br />
private b (hex):<br />
00f3000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 000000000000000f<br />
<br />
transmitted srpB:<br />
0022ce5a7b9d8127 7172caa20b0f1efb<br />
4643b3becc535664 73959b07b790d3c3<br />
f08650d5531c19ad 30ebb67bdb481d1d<br />
9cf61bf272f84398 48fdda58a4e6abc5<br />
abb2ac496da5098d 5cbf90e29b4b110e<br />
4e2c033c70af7392 5fa37457ee13ea3e<br />
8fde4ab516dff1c2 ae8e57a6b264fb9d<br />
b637eeeae9b5e43d faba9b329d3b8770<br />
ce89888709e02627 0e474eef822436e6<br />
397562f284778673 a1a7bc12b6883d1c<br />
21fbc27ffb3dbeb8 5efda279a69a1941<br />
4969113f10451603 065f0a0126666456<br />
51dde44a52f4d8de 113e2131321df1bf<br />
4369d2585364f9e5 36c39a4dce33221b<br />
e57d50ddccb4384e 3612bbfd03a268a3<br />
6e4f7e01de651401 e108cc247db50392<br />
<br />
== SRP A ==<br />
<br />
private a (normally random) (base 10):<br />
1193346<br />
47663227291363113405741243413916<br />
43482736314616601220006703889414<br />
28162541137108417166380088052095<br />
43910927476491099816542561560345<br />
50331133015255005622124012256352<br />
06121987030570656676375703406470<br />
63422988042473190059156975005813<br />
46381864669664357382020200036915<br />
26156674010218162984912976536206<br />
14440782978764393137821956464627<br />
16314542157937343986808167341567<br />
89864323268060014089757606109012<br />
50649711198896213496068605039486<br />
22864591676298304745954690086093<br />
75374681084741884719851454277570<br />
80362211874088739962880012800917<br />
05751238004976540634839106888223<br />
63866455314898189520502368799907<br />
19946264951520393624479315579863<br />
<br />
private a (hex):<br />
00f2000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 000000000000d3d7<br />
<br />
transmitted srpA:<br />
007da76cb7e77af5 ab61f334dbd5a958<br />
513afcdf0f47ab99 271fc5f7860fe213<br />
2e5802ca79d2e5c0 64bb80a38ee08771<br />
c98a937696698d87 8d78571568c98a1c<br />
40cc6e7cb101988a 2f9ba3d65679027d<br />
4d9068cb8aad6ebf f0101bab6d52b5fd<br />
fa81d2ed48bba119 d4ecdb7f3f478bd2<br />
36d5749f2275e948 4f2d0a9259d05e49<br />
d78a23dd26c60bfb a04fd346e5146469<br />
a8c3f010a627be81 c58ded1caaef2363<br />
635a45f97ca0d895 cc92ace1d09a99d6<br />
beb6b0dc0829535c 857a419e834db128<br />
64cd6ee8a843563b 0240520ff0195735<br />
cd9d316842d5d3f8 ef7209a0bb4b54ad<br />
7374d73e79be2c39 75632de562c59647<br />
0bb27bad79c3e2fc ddf194e1666cb9fc<br />
<br />
== SRP key-agreement ==<br />
<br />
u:<br />
b284aa1064e87751 50da6b5e2147b47c<br />
a7df505bed94a6f4 bb2ad873332ad732<br />
<br />
S:<br />
0092aaf0f527906a a5e8601f5d707907<br />
a03137e1b601e04b 5a1deb02a981f4be<br />
037b39829a27dba5 0f1b27545ff2e287<br />
29c2b79dcbdd32c9 d6b20d340affab91<br />
a626a8075806c26f e39df91d0ad979f9<br />
b2ee8aad1bc783e7 097407b63bfe58d9<br />
118b9b0b2a7c5c4c debaf8e9a460f4bf<br />
6247b0da34b760a5 9fac891757ddedca<br />
f08eed823b090586 c63009b2d740cc9f<br />
5397be89a2c32cdc fe6d6251ce11e44e<br />
6ecbdd9b6d93f30e 90896d2527564c7e<br />
b9ff70aa91acc0ba c1740a11cd184ffb<br />
989554ab58117c21 96b353d70c356160<br />
100ef5f4c28d19f6 e59ea2508e8e8aac<br />
6001497c27f362ed bafb25e0f045bfdf<br />
9fb02db9c908f103 40a639fe84c31b27<br />
<br />
M1:<br />
27949ec1e0f16256<br />
33436865edb037e2<br />
3eb6bf5cb91873f2<br />
a2729373c2039008<br />
<br />
srpK:<br />
e68fd0112bfa31dc<br />
ffc8e9c96a1cbadb<br />
4c3145978ff35c73<br />
e5bf8d30bbc7499a<br />
<br />
== /auth ==<br />
<br />
srpK:<br />
e68fd0112bfa31dc<br />
ffc8e9c96a1cbadb<br />
4c3145978ff35c73<br />
e5bf8d30bbc7499a<br />
<br />
respHMACkey:<br />
6584613597ef012f<br />
f1752b7869f01d03<br />
c72547a7b7199681<br />
531d9df1991edf23<br />
<br />
respXORkey:<br />
455835926ae37a1b<br />
627bd16affbeeab6<br />
27ecc737121826ca<br />
4a2bac2c100bf417<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
plaintext:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
ciphertext:<br />
253957f10e861c7c<br />
0a12bb0193d384d9<br />
579db544666d50bd<br />
3252d6576c768a68<br />
<br />
MAC:<br />
a98c87f5769ab4cc<br />
ca3df863faeb217e<br />
b16ddc29d712b301<br />
12b446324ee806d6<br />
<br />
response:<br />
253957f10e861c7c<br />
0a12bb0193d384d9<br />
579db544666d50bd<br />
3252d6576c768a68<br />
a98c87f5769ab4cc<br />
ca3df863faeb217e<br />
b16ddc29d712b301<br />
12b446324ee806d6<br />
<br />
== authtoken ==<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
tokenID (authToken):<br />
9a39818e3bbe6132<br />
38c9d7ff013a1841<br />
1ed2c66c3565c3c4<br />
de03feefecb7d212<br />
<br />
reqHMACkey:<br />
4a17cbdd54ee17db<br />
426fcd7baddff587<br />
231d7eadb408c091<br />
ce19ca915b715985<br />
<br />
requestKey:<br />
9d93978e662bfc6e<br />
8cc203fa4628ef5a<br />
7bf1ddfd7ee54e97<br />
ec5c033257b4fca9<br />
<br />
== /session ==<br />
<br />
requestKey:<br />
9d93978e662bfc6e<br />
8cc203fa4628ef5a<br />
7bf1ddfd7ee54e97<br />
ec5c033257b4fca9<br />
<br />
respHMACkey:<br />
cd3f50403d060b21<br />
76d32f71ca105bd8<br />
7c9b6c4e10e3ebf9<br />
3f5077bec2db24fa<br />
<br />
respXORkey:<br />
8422c53143dea9c6<br />
044afbe95228f291<br />
74996b830b1794a3<br />
eff132da53174d43<br />
92eeb87ccf8ad7a8<br />
0c432894e066de6b<br />
0ff70658dfbf2f07<br />
b9c7704045edcd54<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
sessionToken:<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
plaintext:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
ciphertext:<br />
04a347b2c75b2f41<br />
8cc37162dea57c1e<br />
e408f9109f820234<br />
7768a841cf8ad3dc<br />
324f1adf6b2f710f<br />
a4ea823f4ccb70c4<br />
bf46b4eb6b0a99b0<br />
017ecafbf95073eb<br />
<br />
MAC:<br />
7973ddbb184b601a<br />
c4df09704028ebfc<br />
754dd50e7d8eebfa<br />
52ce3fd868c69852<br />
<br />
response:<br />
04a347b2c75b2f41<br />
8cc37162dea57c1e<br />
e408f9109f820234<br />
7768a841cf8ad3dc<br />
324f1adf6b2f710f<br />
a4ea823f4ccb70c4<br />
bf46b4eb6b0a99b0<br />
017ecafbf95073eb<br />
7973ddbb184b601a<br />
c4df09704028ebfc<br />
754dd50e7d8eebfa<br />
52ce3fd868c69852<br />
<br />
== /account/keys ==<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
tokenID (keyFetchToken):<br />
3d0a7c02a15a62a2<br />
882f76e39b6494b5<br />
00c022a8816e0486<br />
25a495718998ba60<br />
<br />
reqHMACkey:<br />
87b8937f61d38d0e<br />
29cd2d5600b3f4da<br />
0aa48ac41de36a0e<br />
fe84bb4a9872ceb7<br />
<br />
keyRequestKey:<br />
14f338a9e8c6324d<br />
9e102d4e6ee83b20<br />
9796d5c74bb734a4<br />
10e729e014a4a546<br />
<br />
respHMACkey:<br />
f824d2953aab9faf<br />
51a1cb65ba9e7f9e<br />
5bf91c8d8fd1ac1c<br />
8c2d31853a8a1210<br />
<br />
respXORkey:<br />
ce7d7aa77859b235<br />
9932970bbe2101f2<br />
e80d01faf9191bd5<br />
ee52181d2f0b7809<br />
8281ba8cff392543<br />
3a89f7c3095e0c89<br />
900a469d60790c83<br />
3281c4df1a11c763<br />
<br />
kA:<br />
2021222324252627<br />
28292a2b2c2d2e2f<br />
3031323334353637<br />
38393a3b3c3d3e3f<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
plaintext:<br />
2021222324252627<br />
28292a2b2c2d2e2f<br />
3031323334353637<br />
38393a3b3c3d3e3f<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
ciphertext:<br />
ee5c58845c7c9412<br />
b11bbd20920c2fdd<br />
d83c33c9cd2c2de2<br />
d66b222613364636<br />
c2c0f8cfbb7c6304<br />
72c0bd88451342c6<br />
c05b14ce342c5ad4<br />
6ad89e84464c993c<br />
<br />
MAC:<br />
3927d30230157d08<br />
17a077eef4b20d97<br />
6f7a97363faf3f06<br />
4c003ada7d01aa70<br />
<br />
response:<br />
ee5c58845c7c9412<br />
b11bbd20920c2fdd<br />
d83c33c9cd2c2de2<br />
d66b222613364636<br />
c2c0f8cfbb7c6304<br />
72c0bd88451342c6<br />
c05b14ce342c5ad4<br />
6ad89e84464c993c<br />
3927d30230157d08<br />
17a077eef4b20d97<br />
6f7a97363faf3f06<br />
4c003ada7d01aa70<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
unwrapBKey:<br />
6ea660be9c89ec35<br />
5397f89afb282ea0<br />
bf21095760c8c500<br />
9bbcc894155bbe2a<br />
<br />
kB:<br />
2ee722fdd8ccaa72<br />
1bdeb2d1b76560ef<br />
ef705b04349d9357<br />
c3e592cf4906e075<br />
<br />
== use session (certificate/sign, etc) ==<br />
<br />
sessionToken:<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
tokenID (sessionToken):<br />
c0a29dcf46174973<br />
da1378696e4c82ae<br />
10f723cf4f4d9f75<br />
e39f4ae3851595ab<br />
<br />
reqHMACkey:<br />
9d8f22998ee7f579<br />
8b887042466b72d5<br />
3e56ab0c094388bf<br />
65831f702d2febc0<br />
<br />
== /password/change ==<br />
<br />
requestKey:<br />
9d93978e662bfc6e<br />
8cc203fa4628ef5a<br />
7bf1ddfd7ee54e97<br />
ec5c033257b4fca9<br />
<br />
respHMACkey:<br />
60d01e3d1da53b10<br />
93124a30c26889d7<br />
b2e067e7a09fde14<br />
6f935e3c653614f9<br />
<br />
respXORkey:<br />
3de5bd5e80faf84a<br />
dfca5396148123ef<br />
8184cd4bc10a7c8a<br />
db1688495affee67<br />
e07f80d914c5105f<br />
c86d6af24c4be1b1<br />
ef6c9c661422ac43<br />
181b3d29624a0cc2<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
accountResetToken:<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
plaintext:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
ciphertext:<br />
bd643fdd047f7ecd<br />
5743d91d980cad60<br />
11155fd8559fea1d<br />
438f12d2c66270f8<br />
20be421ad000d698<br />
00a4a03980862f7e<br />
3fbd4eb5c0f77a94<br />
c0c2e7f2be97d21d<br />
<br />
MAC:<br />
804fc4bc30923cc0<br />
d6c07ffea954848e<br />
0076b94f7deee71f<br />
a34db5c106d91980<br />
<br />
response:<br />
bd643fdd047f7ecd<br />
5743d91d980cad60<br />
11155fd8559fea1d<br />
438f12d2c66270f8<br />
20be421ad000d698<br />
00a4a03980862f7e<br />
3fbd4eb5c0f77a94<br />
c0c2e7f2be97d21d<br />
804fc4bc30923cc0<br />
d6c07ffea954848e<br />
0076b94f7deee71f<br />
a34db5c106d91980<br />
<br />
== /account/reset ==<br />
<br />
accountResetToken:<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
tokenID (accountResetToken):<br />
46ec557e56e531a0<br />
58620e9344ca9c75<br />
afac0d0bcbdd6f8c<br />
3c2f36055d9540cf<br />
<br />
reqHMACkey (for HAWK):<br />
716ebc28f5122ef4<br />
8670a48209190a16<br />
05263c3188dfe452<br />
56265929d1c45e48<br />
<br />
requestKey:<br />
aa5906d2318c6e54<br />
ecebfa52f10df4c0<br />
36165c230cc78ee8<br />
59f546c66ea3c126<br />
<br />
reqHMACkey (for ciphertext):<br />
a0d894a6232f2e78<br />
66a51dda3f84e01e<br />
ae5adb812564f391<br />
6c0d3cb16bdb743c<br />
<br />
reqXORkey:<br />
9cbde8fc9df31455 837b881e6c0d7e3c<br />
ca13589bc868c527 95fc00e51f2048ab<br />
d56de37629cda0b0 3f580a9e6c433724<br />
b5df12a735ccf2a1 e232d4f5fef84f86<br />
a1b4fdc47f8d1f73 12a6a230a8742d5b<br />
c144ee9abce25b57 9670b81085064cfb<br />
dcab862d9d57abcc 2142dcdde6682281<br />
d378c89b0dce06ae cd1c1ff68ad6db9a<br />
9cab0b02e160805b 59bb8712c8233056<br />
1b3ded75c430e23c 22338833b6f2ba39<br />
f5015ca7a905d6ee 6ec5b1e3ae5204ba<br />
6f3630ebf30ebbac 1f47329e8fe22770<br />
2a3d61f593328dd4 f0a96b628aa8ffec<br />
181e93d2af8d87ff 2d90d67caaf7f7c9<br />
af024c93cfc79e94 67ba70b3076c20cc<br />
141aa254ff159b25 3125a304441cecf3<br />
4fc1845ce96ee598 21fde83cd24e3209<br />
4d304477bfa2c8ed df236e512560694e<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
newSRPv:<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
<br />
plaintext:<br />
4041424344454647 48494a4b4c4d4e4f<br />
5051525354555657 58595a5b5c5d5e5f<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
<br />
ciphertext:<br />
dcfcaabfd9b65212 cb32c25520403073<br />
9a420ac89c3d9370 cda55abe437d16f4<br />
c47cf26738dcb1a1 2e491b8f7d522635<br />
a4ce03b624dde3b0 f323c5e4efe95e97<br />
b0a5ecd56e9c0e62 03b7b321b9653c4a<br />
d055ff8badf34a46 8761a90194175dea<br />
cdba973c8c46badd 3053cdccf7793390<br />
c269d98a1cdf17bf dc0d0ee79bc7ca8b<br />
8dba1a13f071914a 48aa9603d9322147<br />
0a2cfc64d521f32d 33229922a7e3ab28<br />
e4104db6b814c7ff 7fd4a0f2bf4315ab<br />
7e2721fae21faabd 0e56238f9ef33661<br />
3b2c70e482239cc5 e1b87a739bb9eefd<br />
090f82c3be9c96ee 3c81c76dbbe6e6d8<br />
be135d82ded68f85 76ab61a2167d31dd<br />
050bb345ee048a34 2034b215550dfde2<br />
5ed0954df87ff489 30ecf92dc35f2318<br />
5c215566aeb3d9fc ce327f403471785f<br />
<br />
MAC:<br />
1d3572fe0b4bdf66<br />
f2b2657cb2ee56fc<br />
80f7a82708cafd82<br />
1952e1f01761cb29<br />
<br />
response:<br />
dcfcaabfd9b65212 cb32c25520403073<br />
9a420ac89c3d9370 cda55abe437d16f4<br />
c47cf26738dcb1a1 2e491b8f7d522635<br />
a4ce03b624dde3b0 f323c5e4efe95e97<br />
b0a5ecd56e9c0e62 03b7b321b9653c4a<br />
d055ff8badf34a46 8761a90194175dea<br />
cdba973c8c46badd 3053cdccf7793390<br />
c269d98a1cdf17bf dc0d0ee79bc7ca8b<br />
8dba1a13f071914a 48aa9603d9322147<br />
0a2cfc64d521f32d 33229922a7e3ab28<br />
e4104db6b814c7ff 7fd4a0f2bf4315ab<br />
7e2721fae21faabd 0e56238f9ef33661<br />
3b2c70e482239cc5 e1b87a739bb9eefd<br />
090f82c3be9c96ee 3c81c76dbbe6e6d8<br />
be135d82ded68f85 76ab61a2167d31dd<br />
050bb345ee048a34 2034b215550dfde2<br />
5ed0954df87ff489 30ecf92dc35f2318<br />
5c215566aeb3d9fc ce327f403471785f<br />
1d3572fe0b4bdf66 f2b2657cb2ee56fc<br />
80f7a82708cafd82 1952e1f01761cb29<br />
<br />
== /account/destroy ==<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
tokenID (authToken):<br />
9a39818e3bbe6132<br />
38c9d7ff013a1841<br />
1ed2c66c3565c3c4<br />
de03feefecb7d212<br />
<br />
reqHMACkey:<br />
4a17cbdd54ee17db<br />
426fcd7baddff587<br />
231d7eadb408c091<br />
ce19ca915b715985<br />
<br />
= Keyserver Protocol Summary =<br />
<br />
* POST /account/create (email,srpV,srpSalt) -> ok (server sends verification email)<br />
** creates a user account<br />
* GET /account/devices [sessionToken] () -> list of devices<br />
* GET /account/keys [keyFetchToken,needs-verf] () -> kA/wrap(kB)<br />
** single-use, only if email is verified, encrypted results<br />
* POST /account/reset [authed+encrypted by accountResetToken] (wrap(kB),srpV,srpSalt) -> ok<br />
** single-use, does not require email to be verified, revoke all tokens for account, send notification email to user<br />
* POST /account/delete [authToken] () -> ok, account deleted<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authToken] () -> keyFetchToken, sessionToken<br />
* POST /session/destroy [sessionToken] () -> ok<br />
** for detaching a device, destroy all tokens<br />
* POST /recovery_email/status [sessionToken] () -> "verified" status of email<br />
** use "Accept: text/event-stream" header for server-sent-events; server will send "update" event with the new content of the resource any time it changes.<br />
* POST /recovery_email/resend_code [sessionToken] () -> re-send verification email<br />
* POST /recovery_email/verify_code (code) -> set "verified" flag<br />
** this code will come from a clickable link and is an unauthenticated endpoint<br />
** this could maybe take the recovery method if that would be helpful<br />
** sets verified flag on recovery method<br />
* POST /certificate/sign [sessionToken,needs-verf] (pubkey) -> cert<br />
** only if recovery email is verified<br />
* POST /password/change/start [authToken,needs-verf] () -> accountResetToken, keyFetchToken<br />
* POST /password/forgot/send_code () -> forgotPasswordToken<br />
** sends code to recovery method (email for now, maybe SMS later)<br />
** this is a short code, not a clickable link<br />
* POST /password/forgot/resend_code (forgotPasswordToken) -> re-sends code<br />
* POST /password/forgot/verify_code (forgotPasswordToken, code) -> accountResetToken<br />
** sets verified flag on recovery method<br />
* POST /get_random_bytes<br />
<br />
<br />
== Typical Client Flows ==<br />
<br />
Create account<br />
<br />
* POST /account/create (email,srpV,srpSalt) -> ok (server sends verification email)<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authed with authToken]() -> keyFetchToken, sessionToken<br />
* GET /recovery_email/status [sessionToken] () -> "verified" status<br />
** (optional, only if user requests resend) POST /recovery_email/resend_code [sessionToken]() -> ok<br />
** POST /recovery_email/verify_code (code) -> ok<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
* POST /certificate/sign [sessionToken] (pubkey) -> cert<br />
<br />
<br />
Attach to new device<br />
<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authToken] () -> keyFetchToken, sessionToken<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
** (if unverified-error, do waitUntilEmailVerified, then try again)<br />
* POST /certificate/sign [sessionToken] (pubkey) -> cert<br />
<br />
<br />
Forgot password<br />
<br />
* POST /password/forgot/send_code (email) -> forgotPasswordToken<br />
* POST /password/forgot/verify_code (forgotPasswordToken, code) -> accountResetToken<br />
* POST /account/reset [authed+encrypted by accountResetToken] (0000,srpV,srpSalt) -> ok<br />
* GOTO "Attach to new device"<br />
<br />
<br />
Change Password<br />
<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /password/change/start [authToken] () -> accountResetToken, keyFetchToken<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
* POST /account/reset [authed+encrypted by accountResetToken] (wrap(kB),srpV,srpSalt) -> ok<br />
* GOTO "Attach to new device"</div>Warnerhttps://wiki.mozilla.org/index.php?title=Identity/WeeklyMeeting/2014-03-10&diff=948611Identity/WeeklyMeeting/2014-03-102014-03-11T00:37:10Z<p>Warner: add notes</p>
<hr />
<div>=Agenda:=<br />
* Discuss this future of this meeting<br />
1. Status Updates<br />
2. Awesome - What happened last week that was awesome?<br />
3. Help - Where could you use help? What is blocking you?<br />
* Awesome and Help are fully led by the crowd, if there are 12 seconds of silence we move onto the next topic.<br />
* We need volunteers for meeting notetakes - please signup - https://id.etherpad.mozilla.org/weekly-notetakers<br />
Work Week<br />
* Is-you-is-or-is-you-ain't signed up?<br />
* link?<br />
<br />
General Reminders:<br />
* Have you signed up for note taking?<br />
** See: https://id.etherpad.mozilla.org/weekly-notetakers<br />
Key wiki pages to point folks at:<br />
** FxA Wiki: https://wiki.mozilla.org/Identity/FirefoxAccounts <br />
** UX: https://wiki.mozilla.org/Identity/UX<br />
= Meeting Notes =<br />
* future of this meeting? deferred to future<br />
* fruux: added provisioning-with-assertion feature, UX work starting (most work is down in the contacts pages, adding as little as possible outside of there). Not sure how much UI will be done, short on gaia hackers. jedp+francois working on b2g app to capture contacts changes and convert into vcard (for exporter). Then will make sure the existing b2g exporter can round-trip its data. on-track, having fun.<br />
* docs: yvan+warner working on brownbag for april, persona transition blog post went live, coverage on techcrunch, HN. Brendan discussed briefly in internal moco staff meeting.<br />
* warner would like to see public numbers on how much persona servers are costing, might help community understand mozilla's financial commitment. gene is workworking on reducing redundancy to appropriate levels, computing a number. Nobody is worried about server costs right now.<br />
* QA victories: TPS (end-to-end FF nightly to sync servers test suite) is running again, fixed by automation team, finding bugs. A contributor added mozmill (automated browser tests) test to sign in and add a bookmark. So yay CI. Trains: will deploy train 4 (from last week) to prod today, waiting for QA signoff. Will probably do train 5 later this week or derail it, then train 6 next week.<br />
* Jared getting settings review is awesome!<br />
* Persona conversation is tough, but awesome that it's happening!<br />
* Vidyo 3 beta available! Yay! No more Flash! More info: https://www.yammer.com/mozilla.com/graph/356894980194306?trk_event=search_ac&trk_model=open_graph_object&trk_len=5&trk_pos=0<br />
* Employees: Crystal did an ergo evaluation (available in Moz Spaces). She highly recommends it. Request via ServiceNow.<br />
= Fruux =<br />
* 1200pm standups in identity vidyo and #fruux<br />
** wiki: https://wiki.mozilla.org/Identity/Contacts-Backup<br />
** lastest meeting notes: https://id.etherpad.mozilla.org/contacts-sync-fruux<br />
** some ux from skinny and john<br />
** https://www.dropbox.com/s/vfuvnwrkvqcy33d/FxA_FFOS_System_Flow_Fruux_Edition.pdf<br />
** http://cl.ly/image/080H3l282U1E<br />
** vcard exporting:<br />
** https://bugzilla.mozilla.org/show_bug.cgi?id=978288 (see esp test cases)<br />
<br />
= FxA/Sync =<br />
* Work week in SF now. Focus: fix race conditions, sign in / out bugs, keeping ui in sync, etc.<br />
= FxA on FxOS =<br />
* We got a Settings Review!!<br />
* Jared has been all over Arthur's feedback<br />
* Missed no-blockers target of last Thursday<br />
* by, um, 6<br />
* Still looking pretty good for landing preffed off by Code Freeze next Monday 17th<br />
* Adam Rogers, just promoted, has a lot of work for FxA on device this year<br />
* https://bugzilla.mozilla.org/show_bug.cgi?id=941723#c15<br />
<br />
= UX =<br />
* Wiki is (falling increasingly) up-to-date. Check out the latest recent, problematically-behind-reality from John and Ryan<br />
** https://wiki.mozilla.org/Identity/UX<br />
= Docs & Engagement =<br />
* Warner + Yvan working on early April re: Sync.<br />
* Persona transition announcement live. Highly recommend watching today's MoCo meeting and reading the HN thread, especially mmayo's comment<br />
** Blog post: http://identity.mozilla.com/post/78873831485/transitioning-persona-to-community-ownership<br />
** HN: https://news.ycombinator.com/item?id=7362613<br />
** MMayo's comment: https://news.ycombinator.com/item?id=7364465<br />
= QA =<br />
* New automated tests! (MozMill upgrade, TPS...)<br />
* Last Monday's train (4) for FxA should go live today. May derail train 5 as a result.<br />
* We had an FxA + Sync test day on Thursday. Less participation than expected. Back to drawing board on figuring out publicity.<br />
= PTO/OOO (kept up-to-date in the Identity calendar for everyone's reference)<br />
* Francois: 7 and 10 March PTO<br />
* Francois: 14, 17, 18, 19 March PTO<br />
* Shane: Feb 26 - Mar 12 :: Tentative dates per Baby Ryan Tomlinson's decision on when to arrive. The dates will adjust as needed :)<br />
* Katie: 27-28 March: PTO<br />
* John G: 27.5-28 Mar: PTO<br />
* warner: apr-10 - apr-18 (PyCon Montreal+PTO)<br />
* chilts: 7th Apr - 2nd May PTO (in UK)</div>Warnerhttps://wiki.mozilla.org/index.php?title=Identity/Firefox_Accounts&diff=933014Identity/Firefox Accounts2014-02-19T23:45:16Z<p>Warner: minor grammar fix</p>
<hr />
<div>{{LastUpdated}}<br />
<br />
=What Is Firefox Accounts? =<br />
<br />
Firefox Accounts is a consumer account system which provides access to services run by Mozilla, such as [https://marketplace.firefox.com/ Firefox Marketplace] and the [https://wiki.mozilla.org/User_Services/Sync next version of Firefox Sync]. A user can sign in with a Firefox Account to all her "Foxes": Firefox on Desktop, Firefox for Android, and Firefox OS. Signing into a Firefox browser or device gives the user access to integrated Mozilla Services on that browser or device that requires authentication (e.g., Firefox Sync). Longer term we envision that non-Mozilla services and applications will be able to delegate authentication to Firefox Accounts.<br />
<br />
Firefox Accounts from a literal, technical perspective is not much to look at. It's a thin service that does only a few things, e.g.,<br />
<br />
* It allows users to create a Firefox Account. <br />
* It allows existing account holders to authenticate themselves, and perform related operations (reset password, change password, etc.)<br />
* It provides a delegated authentication API to relying Mozilla services (possibly third services in the future). Just like how Google Drive delegates authentication to Google Accounts, Firefox Marketplace will delegate authentication to Firefox Accounts. <br />
<br />
Firefox Accounts itself doesn't store much about users, and we intend to keep it that way:<br />
<br />
* Email address<br />
* Password verifier<br />
* User id<br />
* Sync encryption keys<br />
* Whether the user has accepted the ToS and PP<br />
* A log of security events about the user (from where and when logins, passwords resets, etc. happen), and from what devices the user is currently logged in.<br />
<br />
Firefox Accounts on it's own is boring and useless. Firefox Accounts is only interesting and valuable when you start attaching services to it, like Firefox Marketplace, Where's My Fox, and Firefox Sync. These services will manage their own data, but rely on Firefox Accounts for single sign-on and related authentication services.<br />
<br />
=FAQ=<br />
<br />
== Will I be required to create a Firefox Account to use Firefox? ==<br />
<br />
No, of course not! Firefox Accounts will only be required for Mozilla Services that require authentication, such as Firefox Sync and Firefox Marketplace.<br />
<br />
==How does a user create and sign in to a Firefox Account?==<br />
Firefox Accounts will work much like authentication works just about everywhere else. You create a Firefox Account with a verified email and password. You sign in to Firefox Accounts with your email and password. We are currently evaluating creating and logging in to a Firefox Account with a mobile number.<br />
<br />
==Why does Firefox Accounts require me to choose a password?==<br />
The first relying service we're targeting with Firefox Accounts is Firefox Sync. Current Firefox Sync encrypts all your data in our servers, and we will continue to do so in the Firefox Accounts backed version of Sync. However, in the FxA backed version of Firefox Sync, we will encrypt your Sync data with a key derived from your Firefox Account password, instead a random key managed by the J-PAKE pairing protocol. This technique of using a password derived sync key is similar to how data protection in [https://support.google.com/chrome/answer/1181035?hl=en&ref_topic=1693469 Chrome Sync] works.<br />
<br />
==What is the UX for signing in to a Firefox Account?==<br />
NOTE: This is a work in progress!<br />
<br />
Here are some Lucidchart flow diagrams for FxA: https://www.lucidchart.com/documents/edit/4f34-ef24-52695ddf-8057-72580a00d543<br />
===Web===<br />
We anticipate the majority of Firefox Account sign ins and account creations will be driven by flows from Mozilla relying services, such as Firefox Marketplace. We propose relying Mozilla services present account controls and signal the FxA logged in state in the upper right corner of their Web properties:<br />
<br />
[[File:Sign-in.png|400px]]<br />
<br />
If a user clicks on the "Sign Up" or "Log In" button, it will take her to to a FxA page that will allow her to sign in or create an account. After completing sign in or account creation, she will be redirected back to the relying Mozilla service.<br />
<br />
===FxOS===<br />
TODO<br />
<br />
==How do relying Mozilla services authenticate an FxA user?==<br />
Great question. We're still working out the details. We're first going to figure out the [https://wiki.mozilla.org/Identity/Firefox_Accounts/SSO product requirements of SSO with FxA] and go from there.<br />
<br />
==How does a user reset her Firefox Account password?==<br />
Password reset works by responding to an email challenge.<br />
<br />
==What's the difference between Persona and Firefox Accounts?==<br />
Persona is not intended to provide you with a new account, and it's not a new account system. Persona is a federated login protocol. You use Persona to log in to relying sites, and it's not intended that you need to "sign up" for Persona before you can use it. If you would need to sign up for anything, you would need to create an account at an IdP that supports Persona.<br />
<br />
One *huge* confusing point about Persona today is a service called the "Persona Fallback", which serves as a proxy IdP if your actual IdP doesn't support Persona (or isn't bridged), which just about every IdP except for Google and Yahoo. In this case, you currently have to sign up for a "Persona Fallback Account" (i.e. choose a password and verify your email) to use Persona. <br />
<br />
But a Persona Fallback Account is not a Persona Account, it's not the long term vision of Persona, and that's not supposed to be the happy path of the Persona login experience. <br />
<br />
More importantly, for the purposes of this question, a Persona Fallback Account is definitely not a Firefox Account.<br />
<br />
So why Firefox Accounts and what will one do?<br />
<br />
Mozilla needs an account database to deliver a fantastic, integrated experience across all its products. Unfortunately, delivering awesome services involves some less exciting, but still important aspects, like making sure users have had a chance to inspect our terms of service and privacy policies. We must also comply with local laws and regulations, e.g., [http://www.coppa.org/ COPPA]. It would be inconvenient for users to have to verify a terms of service, a privacy policy, and COPPA at each individual Mozilla service. We believe that users should only have to inspect our terms of service, privacy policy, and go through COPPA verification '''once''' for all our services. Firefox Accounts enables us to do that. One we get the basics down and enable single sign-on for relying Mozilla Services with your Firefox Account, we hope integrate Firefox Accounts with Persona on the Web and Firefox user agents to make logging in everywhere as painless as it should be.<br />
<br />
== What information does Firefox Accounts store about the user? Can I use it to store user data for my application or service? ==<br />
Firefox Accounts stores limited user information, and only stores information that will deliver significant user value across applications or is tightly related to the user's identity. It will not store user data for relying services. Relying Mozilla services can use Firefox Accounts for authentication, but application data storage is the responsibility of the individual applications.<br />
<br />
Currently, Firefox Accounts stores the user's email address, a unique identifier, sync encryption key material, and whether they have read and accepted the terms of service, privacy policy, etc. The existence of a Firefox Account also indicates the user has passed COPPA verification.<br />
<br />
Possible future plans:<br />
* "screen name"<br />
* avatar<br />
* mobile number<br />
<br />
== Can I use Persona to log in to my Firefox Account? ==<br />
Not initially, but it's something we're investigating to add in the future.<br />
<br />
== Can I use my Firefox Account to log in to non-Mozilla services? ==<br />
Not initially, but it's something we're investigating to support in the future.<br />
<br />
== Does Firefox Accounts provide email? ==<br />
No.<br />
<br />
== What services will use Firefox Accounts? ==<br />
Here's a (probably incomplete) list of services we anticipate you'll be able to log into with your Firefox Account:<br />
* Firefox Sync<br />
* Firefox Marketplace<br />
* Where's My Fox?<br />
* [https://wiki.mozilla.org/User:Dria/PiCL_Future_Ideas crazy future ideas]<br />
<br />
== What do these terms mean? ==<br />
* FTU, FTE: First Time Experience on Firefox OS<br />
* FxA : Firefox Accounts. It may also refer to a user's particular Firefox Account.<br />
* Jelly: A confusing term that refers to a hosted web page that is injected into more native-looking browser UI. An example of this is about:healthreport.<br />
* Doughnut: The browser code that wraps the "Jelly" and enables it to interact with chrome code in the browser. <br />
* RP : Relying Party. Services that use Firefox Accounts for authentication and identity. Currently these are limited to services run by Mozilla.<br />
* PiCL : Profile in the Cloud. This is a deprecated term that was used to refer to Firefox Accounts + attached services (i.e., relying parties).<br />
<br />
== Where is the schedule for FxA? ==<br />
https://wiki.mozilla.org/Identity/Roadmap<br />
<br />
== Where is the FxA for Web addition to the Arch section below? ==<br />
<br />
== What are the similarities/differences between FxA for Web and the Dev work already being done for desktop and android? ==<br />
<br />
== Have a question not covered here? Add it in this section and we'll answer it! ==<br />
Is it possible to host your own Firefox accounts, like with Firefox Sync?<br />
<br />
=Architecture=<br />
[[File:Firefox_Accounts_Architecture.png]]<br />
<br />
[[File:Firefox_Accounts_and_Sync_Architecture.png]]<br />
<br />
https://mana.mozilla.org/wiki/display/services/Firefox+Accounts+Architecture<br />
<br />
<br />
== Cloud Services ==<br />
<br />
Firefox Accounts Cloud Services is composed of several sub-services, an '''auth server''', a '''content server''', and a '''crypto helper'''.<br />
<br />
=== Auth Server ===<br />
<br />
The Auth Server provides an HTTP API that:<br />
<br />
* authenticates the user<br />
* enables the user to authenticate to other services via BrowserID assertions<br />
* enables change and reset password operations<br />
<br />
Links:<br />
* Code: https://github.com/mozilla/fxa-auth-server<br />
* API documentation: https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md<br />
* API design document: [[Identity/AttachedServices/KeyServerProtocol]]<br />
* Dev deployment: https://github.com/mozilla/fxa-auth-server#dev-deployment<br />
* Python API client (primarily a reference client): https://github.com/warner/picl-spec-crypto<br />
<br />
=== Content Server ===<br />
<br />
The Content Server hosts static assets (HTML, Javascript, CSS, etc.) that support user interactions with the Firefox Accounts. The responsibilities of the Content Server include:<br />
<br />
* hosting Gherkin, a Javascript library that supports interactions with the Auth Server<br />
* hosting login and create account pages<br />
* hosting password reset pages<br />
* hosting landing pages for email verification links<br />
<br />
Links:<br />
* Code: https://github.com/mozilla/fxa-content-server<br />
* Deployments: <br />
** dev stable: https://accounts.dev.lcip.org/<br />
** dev latest: https://accounts-latest.dev.lcip.org/<br />
** prod: https://accounts.firefox.com/<br />
<br />
=== JS Client Library ===<br />
<br />
Firefox Accounts provides a Javascript client library for the Web that supports operations with Firefox Accounts. In addition to communicating with the Auth Server, it also performs local key stretching (PBKDF2 and scrypt) on the user's password before it's used in the API. It is hosted by the Content Server. This library was at one time called "Gherkin".<br />
<br />
Links:<br />
* Code: https://github.com/mozilla/fxa-js-client<br />
* Key stretching details: https://wiki.mozilla.org/Identity/AttachedServices/KeyServerProtocol#Client-Side_Key_Stretching<br />
* Key stretching performance tests: https://wiki.mozilla.org/Identity/AttachedServices/Key_Stretching_Performance_Tests<br />
<br />
=== scrypt Helper ===<br />
<br />
A portion of the key stretching process uses [http://en.wikipedia.org/wiki/Scrypt scrypt], a password-based key derivation function that uses significant amounts of memory. On memory constrained devices, Firefox Accounts provides a helper service for this portion of the key stretching process.<br />
<br />
Links:<br />
* Code: https://github.com/mozilla/fxa-scrypt-helper<br />
* Dev deployment: https://scrypt-accounts.dev.lcip.org/<br />
<br />
=== Verifier ===<br />
<br />
FxA will host its own BID verifier for relying Mozilla service to verify the assertions FxA generates. The reason is that the <code>principal</code> will be <code>{ email: <user's real email>, uid: <FxA user id> }</code>, which is not BID-kosher.<br />
<br />
Update (11/14/13): A separate verifier is currently off the table. The proposal is to use the current verifier, with some changes.<br />
Update (11/15/13): it sure seems like it is back on the table. Can someone confirm?<br />
<br />
Discussion:<br />
* https://github.com/mozilla/fxa-auth-server/issues/292<br />
* https://github.com/mozilla/fxa-auth-server/pull/275<br />
* https://groups.google.com/forum/#!topic/mozilla.dev.identity/1ecTUrOFzbQ<br />
<br />
== Desktop ==<br />
<br />
Firefox Accounts integration on Firefox for Desktop is happening in the [https://tbpl.mozilla.org/?tree=Elm "elm" project branch]. We are also working out of a [https://github.com/mhammond/mozilla-central/tree/experiment/elm-fxaccount-sync github repo] for "pre-elm" experimentation.<br />
<br />
Tracking bug:<br />
* https://bugzilla.mozilla.org/showdependencytree.cgi?id=905997&hide_resolved=1<br />
<br />
== Android ==<br />
<br />
Firefox Accounts integration on Firefox for Android is happening in the [https://tbpl.mozilla.org/?tree=Elm "elm" project branch].<br />
<br />
Tracking bug:<br />
* https://bugzilla.mozilla.org/showdependencytree.cgi?id=799726&hide_resolved=1<br />
<br />
== Firefox OS ==<br />
Implementation of Firefox Accounts in FirefoxOS is committed for b2g v1.4. This is a collaborative effort working closely with TEF and Telenor engineers. <br />
<br />
Demo:<br />
* https://vimeo.com/79618371<br />
<br />
Tracking bug:<br />
* https://bugzilla.mozilla.org/showdependencytree.cgi?id=941723&hide_resolved=1<br />
<br />
Our current line of thought is below and a work-in-progress:<br />
* UX: https://wiki.mozilla.org/Identity/UX#FXOS<br />
* https://github.com/SamPenrose/fxa-fxos/blob/master/dependencies.md<br />
<br />
===Tracking===<br />
<bugzilla><br />
{<br />
"whiteboard":"ft:FirefoxAccounts",<br />
"whiteboard_type":"contains",<br />
"include_fields": "id, summary, status, resolution, assigned_to, depends_on, blocks, whiteboard, cf_blocking_b2g"<br />
}<br />
</bugzilla><br />
<br />
== Operations ==<br />
For now, here are some useful links about Firefox Accounts Operations:<br />
* Q3 load testing results: https://id.etherpad.mozilla.org/fxa-q3-load-testing-summary<br />
* Deployment planning: https://wiki.mozilla.org/Identity/AttachedServices/DeploymentPlanning/<br />
* Traffic model: https://wiki.mozilla.org/Identity/AttachedServices/DeploymentPlanning/TrafficModel<br />
* Notes on the operational costs of the Auth Server: https://mail.mozilla.org/pipermail/sync-dev/2013-July/000043.html<br />
<br />
=== Deployments ===<br />
* production (proposed) https://github.com/mozilla/fxa-auth-server/issues/295#issuecomment-28614668<br />
* stage (proposed): https://github.com/mozilla/fxa-auth-server/issues/295#issuecomment-28725360<br />
* dev:<br />
** Auth server: https://api-accounts.dev.lcip.org<br />
** Auth server (auto-pushed from master): https://api-accounts-latest.dev.lcip.org/<br />
** Content server: https://accounts.dev.lcip.org/flow<br />
** Scrypt helper: https://scrypt-accounts.dev.lcip.org<br />
<br />
== Metrics ==<br />
https://wiki.mozilla.org/Identity/Firefox_Accounts/Minimum_Viable_Metrics<br />
<br />
== Fraud and Abuse ==<br />
https://id.etherpad.mozilla.org/fxacct-metrics-fraud-detection<br />
<br />
=Resources=<br />
<br />
== Mailing Lists ==<br />
* Firefox Accounts development: https://mail.mozilla.org/listinfo/dev-fxacct<br />
* Sync development: https://mail.mozilla.org/listinfo/sync-dev<br />
<br />
{{FxA Team}}<br />
<br />
== Related ==<br />
* [https://wiki.mozilla.org/Identity/Roadmap Identity Roadmap]<br />
* [https://wiki.mozilla.org/User_Services/Sync Firefox Sync.next]<br />
* [https://wiki.mozilla.org/Identity/PiCL Identity and PiCL]<br />
* [https://wiki.mozilla.org/Identity/UX FxA UX/UI]<br />
* [https://wiki.mozilla.org/QA/Services/SyncTestPlanV1 QA Team Test Plan]<br />
* [https://wiki.mozilla.org/Identity/WhatDoesFxAMeanToYou What Does Firefox Accounts Mean To You?]<br />
<br />
== Demos ==<br />
* Firefox Accounts + Firefox Sync on Android: https://vimeo.com/77667079<br />
* Firefox Accounts + Firefox Sync on Desktop: https://vimeo.com/77717494</div>Warnerhttps://wiki.mozilla.org/index.php?title=File:FxA-states.png&diff=896726File:FxA-states.png2014-01-18T01:38:13Z<p>Warner: </p>
<hr />
<div></div>Warnerhttps://wiki.mozilla.org/index.php?title=User_Services/Sync/FxA_Client_States&diff=896725User Services/Sync/FxA Client States2014-01-18T01:37:48Z<p>Warner: Created page with "== FxA Client States == FxA Client States Firefox Account clients start in the "single" state, which means the browser profile has never been attache..."</p>
<hr />
<div>== FxA Client States ==<br />
<br />
[[File:FxA-states.png|FxA Client States]]<br />
<br />
Firefox Account clients start in the "single" state, which means the browser profile has never been attached to a Firefox Account.<br />
<br />
When the "sign in to firefox" UI gets far enough to obtain a "session token" and a "key fetch token", the profile is now in the "engaged" state. In this state, the client is polling the fxa-auth-server, awaiting notification that the account's email address has been verified, and then fetching the account keys. When the kA/kB keys have been obtained, the profile moves into the "married" state. The transition into "married" causes the "fxacct:onlogin" event to be published.<br />
<br />
This will cause services like Sync to wake up, to ask the FxAccount object for encryption keys and a signed assertion, and then begins the process of synchronizing data like bookmarks and passwords. The FxAccount object interacts with the fxa-auth-server on-demand, rather than using a timer-driven schedule. It will generate Persona keypairs, ask the fxa-auth-server to sign pubkeys, and create signed assertions if and when a service asks for a signed assertion.<br />
<br />
If at any point the FxAccount objects concludes that its session has been revoked (e.g. when it is asked for an assertion, and it calls the fxa-auth-server's /certificate/sign API, and receives a "bad session token" error), the profile is moved to the "separated" state. All transitions out of the "married" state, including this one, cause the "fxacct:onlogout" event to be published. The FxAccount code is responsible for providing UI that informs the user that they need to re-login.<br />
<br />
If the user explicitly "signs out of Firefox", the profile moves to the "divorced" state (possibly causing an "fxacct:onlogout" event). The FxAccount UI for this state is different than for the "separated" state: instead of suggesting the user needs to sign back in (using the same email address), it mostly looks like the original "single" UI.<br />
<br />
"divorced" and "single" differ mainly by a persistent flag (which might be a boolean, or might remember the last-used email address). This flag enables the UI to warn the user about the "all your stuff will get merged with theirs" confusion that could result when you sign in with a different address. This is not a complete solution: there are other situations that start from the "single" state which will also result in confusion, but perhaps this flag can help somewhat.<br />
<br />
When Sync makes calls into the FxAccounts object (like getAssertion), it should treat any error it receives as transient, and should arrange to try again later (preferably using a randomized exponential backoff to avoid clobbering an overloaded server). If the error is in fact a persistent revocation, the FxAccount object will internally move itself into the "separated" state, and will notify Sync (via "fxacct:onlogout") that it should stop trying.<br />
<br />
Any unusual errors (of the "this should never happen" variety) should move the profile into the "separated" state, from which hopefully the user can recover by signing back in. It may also be appropriate to display some UI that mentions "a weird error as occurred, maybe signing back in will help", and/or to deliver some telemetry back to our servers so we can investigate.<br />
<br />
If a dependent service like Sync independently discovers evidence that the account may not be working properly (e.g. assertions are rejected by the Tokenserver), it should call an FxAccounts API (name TBD) that means roughly "I know you think you're in the 'married' state, but I have evidence to the contrary, do with it what you will". This API does not provide a response. The FxAccount object should then try to confirm the state of its account (either by getting a fresh signed certificate, or making some other session-status call). If this reveals that the session has been revoked, the FxAccount object moves to the "separated" state, fxacct:onlogout is posted, and the UI is updated as usual. If everything still looks fine, the FxAccount can continue in the "married" state.<br />
<br />
== Internal Details ==<br />
<br />
TBD: expand on the diagram with internal states<br />
<br />
"married": flowchart showing getAssertion() response: do we have an assertion, do we have a signed certificate, do we have a new-enough keypair, create keypair, ask fxa-auth-server to /certificate/sign, sign assertion<br />
<br />
"engaged": verification polling, key fetching<br />
<br />
"single": jelly-frame loading, jelly-side password handling, delivery of sessionToken/keyFetchToken to chrome-side code</div>Warnerhttps://wiki.mozilla.org/index.php?title=User_Services/Sync&diff=896709User Services/Sync2014-01-18T01:15:09Z<p>Warner: /* Technical Materials */</p>
<hr />
<div>{{LastUpdated}}<br />
<br />
The new home of all things related to the design & development of the '''New Sync''' service that is slated to replace the existing Sync service in late 2013/early 2014. A working name for this project has been PiCL ("Profile in the Cloud"), and the below documents may use this acronym. <br />
<br />
== What Problems Are We Solving? ==<br />
<br />
Right now we have four problems with our existing Sync service. In priority order they are:<br />
<br />
1. '''Usability''': First, it's too hard to set up - only 1% of our users have actually done so. Second, it's too hard to add a second device - only 20% of sync users have more than one device (and hence are actually getting any value at all from sync).<br />
<br />
2. '''Quality''': The existing Sync codebase and protocol are unable to adequately address the reliability, performance, fault recovery, and efficiency requirements of a high-usage sync system. <br />
<br />
3. '''Scalability''': Our sync servers are failing far too frequently resulting in user facing outages.<br />
<br />
4. '''Features''': Features we want to build require that we can access data via web apps, which the current sync product cannot support. Competing browsers are building interesting features that can do this. We are at a disadvantage, and it's not for want of ideas.<br />
<br />
== Upcoming releases ==<br />
<br />
'''Current'''<br />
* [[User Services/Sync/Relaunch | Firefox 29 Cycle Relaunch]]<br />
<br />
'''Archived'''<br />
* [[User Services/Sync/v1 | New Sync v1 (MVP)]]<br />
* [[User Services/Sync/v2 | New Sync v2]]<br />
<br />
== Meetings ==<br />
Archive the of the Engineering meetings can be viewed at:<br />
* [https://wiki.mozilla.org/Identity/PiCL/Engineering_Meeting_Notes Engineering Coordination Meeting Notes]<br />
<br />
== Product Requirements and User Stories ==<br />
* Firefox for Android: https://wiki.mozilla.org/Mobile/Projects/Firefox_Accounts_with_Sync_1.1_integration<br />
* Firefox for Desktop: https://wiki.mozilla.org/User_Services/Sync/Relaunch#Desktop_MVP_User_Stories<br />
<br />
== UX Designs ==<br />
* "Sign in to Desktop Firefox" mocks: http://people.mozilla.com/~zfang/FirefoxAccount/PiCL_0710.pdf<br />
* "Sign in to Android Firefox" mocks: https://www.dropbox.com/s/qyvd2p71jifqnmi/picl%20sign%20in%20flows%20android.psd<br />
* Sign in/Sign up Usability Test Results: http://people.mozilla.org/~jgruen/pdx_deck/<br />
<br />
== Technical Materials ==<br />
* [[Identity/AttachedServices/Architecture | High level architecture]]<br />
* Dev mailing list: https://mail.mozilla.org/listinfo/sync-dev<br />
* [https://wiki.mozilla.org/Identity/PiCL/Engineering_Meeting_Notes Meeting notes]<br />
* Authentication<br />
** Protocol: [[Identity/AttachedServices/KeyServerProtocol]]<br />
** Code: https://github.com/mozilla/picl-idp<br />
** API documentation: https://github.com/mozilla/picl-idp/blob/master/docs/api.md<br />
** Dev servers<br />
*** FxA API: http://idp.dev.lcip.org<br />
*** FxA Jelly: http://accounts.dev.lcip.org/flow (code at https://github.com/mozilla/firefox-account-bridge)<br />
** Key stretching performance tests: https://wiki.mozilla.org/Identity/AttachedServices/Key_Stretching_Performance_Tests<br />
* Service discovery<br />
** Device management (sits next to/in key server): TBD<br />
** Meta server: [[User_Services/Meta]]<br />
* Milestone 1 Storage<br />
** Docs<br />
*** Token server API: https://docs.services.mozilla.com/token/apis.html<br />
** Dev servers<br />
*** Token server: http://auth.oldsync.dev.lcip.org/<br />
*** Storage server: http://db1.oldsync.dev.lcip.org/<br />
*** README: https://mail.mozilla.org/pipermail/sync-dev/2013-August/000392.html<br />
* Storage Research<br />
** [[Identity/CryptoIdeas/06-Queue-Sync-CouchDB]]<br />
** [[User:Rnewman/TreeSync]]<br />
** Sync 2.0: http://docs.services.mozilla.com/storage/apis-2.0.html<br />
** Draft data formats: https://mobile.etherpad.mozilla.org/draft-bookmark-password-formats<br />
** Existing sync data formats: http://docs.services.mozilla.com/sync/objectformats.html<br />
* Design musings<br />
** Enumeration of different failures and in existing Sync product: https://mobile.etherpad.mozilla.org/sync-failures<br />
** Design contraints: https://services.etherpad.mozilla.org/2019<br />
** Exploration of the design space: https://mobile.etherpad.mozilla.org/sync-design-faq<br />
** User data stats in the existing Sync product: https://id.etherpad.mozilla.org/picl-user-model<br />
** Technical design review meeting notes: https://etherpad.mozilla.org/identitydesignreview<br />
** FxA client state transitions: [[User_Services/Sync/FxA_Client_States]]<br />
* QA plan: https://wiki.mozilla.org/QA/Services/SyncTestPlanV1<br />
* Server deployment planning: [[Identity/AttachedServices/DeploymentPlanning]]<br />
* Bugzilla Meta bugs/dependency trees<br />
** Product user stories: https://bugzilla.mozilla.org/showdependencytree.cgi?id=909322&hide_resolved=0<br />
** Desktop: https://bugzilla.mozilla.org/showdependencytree.cgi?id=905997&hide_resolved=0<br />
** Android: https://bugzilla.mozilla.org/showdependencytree.cgi?id=799726&hide_resolved=0<br />
** Server-side: https://bugzilla.mozilla.org/showdependencytree.cgi?id=907475&hide_resolved=0<br />
* ELM Nightly Builds<br />
** Desktop<br />
*** http://ftp.mozilla.org/pub/mozilla.org/firefox/nightly/latest-elm/ <br />
** Android<br />
*** http://ftp.mozilla.org/pub/mozilla.org/mobile/nightly/latest-elm-android/<br />
*** http://ftp.mozilla.org/pub/mozilla.org/mobile/nightly/latest-elm-android-x86/<br />
*** http://ftp.mozilla.org/pub/mozilla.org/mobile/nightly/latest-elm-android-armv6/<br />
<br />
== User Research ==<br />
TBD<br />
<br />
== Team ==<br />
<br />
=== Identity ===<br />
* Lloyd Hilaiel<br />
* Tauni Oxborrow<br />
* Chris Karlof<br />
* Brian Warner<br />
* Danny Coates<br />
* Zach Carter<br />
* Vlad Filippov<br />
<br />
=== UX ===<br />
* John Gruen<br />
* Ian Barlow<br />
* Zhenshuo Fang<br />
<br />
=== Services ===<br />
* Mark Mayo<br />
* Ryan Kelly<br />
<br />
=== Product ===<br />
* Karen Rudnitski<br />
* Deb Richardson<br />
* Rob Lord<br />
* Chris Lee<br />
<br />
=== Desktop ===<br />
* Gavin Sharp<br />
* Justin Dolske<br />
<br />
=== Android ===<br />
* Mark Finkle<br />
* Richard Newman<br />
* Nick Alexander<br />
<br />
=== FirefoxOS ===<br />
* Dale Harvey<br />
<br />
=== QA ===<br />
* James Bonacci</div>Warnerhttps://wiki.mozilla.org/index.php?title=User_Services/Sync&diff=896704User Services/Sync2014-01-18T01:13:07Z<p>Warner: link to "FxA client states" page</p>
<hr />
<div>{{LastUpdated}}<br />
<br />
The new home of all things related to the design & development of the '''New Sync''' service that is slated to replace the existing Sync service in late 2013/early 2014. A working name for this project has been PiCL ("Profile in the Cloud"), and the below documents may use this acronym. <br />
<br />
== What Problems Are We Solving? ==<br />
<br />
Right now we have four problems with our existing Sync service. In priority order they are:<br />
<br />
1. '''Usability''': First, it's too hard to set up - only 1% of our users have actually done so. Second, it's too hard to add a second device - only 20% of sync users have more than one device (and hence are actually getting any value at all from sync).<br />
<br />
2. '''Quality''': The existing Sync codebase and protocol are unable to adequately address the reliability, performance, fault recovery, and efficiency requirements of a high-usage sync system. <br />
<br />
3. '''Scalability''': Our sync servers are failing far too frequently resulting in user facing outages.<br />
<br />
4. '''Features''': Features we want to build require that we can access data via web apps, which the current sync product cannot support. Competing browsers are building interesting features that can do this. We are at a disadvantage, and it's not for want of ideas.<br />
<br />
== Upcoming releases ==<br />
<br />
'''Current'''<br />
* [[User Services/Sync/Relaunch | Firefox 29 Cycle Relaunch]]<br />
<br />
'''Archived'''<br />
* [[User Services/Sync/v1 | New Sync v1 (MVP)]]<br />
* [[User Services/Sync/v2 | New Sync v2]]<br />
<br />
== Meetings ==<br />
Archive the of the Engineering meetings can be viewed at:<br />
* [https://wiki.mozilla.org/Identity/PiCL/Engineering_Meeting_Notes Engineering Coordination Meeting Notes]<br />
<br />
== Product Requirements and User Stories ==<br />
* Firefox for Android: https://wiki.mozilla.org/Mobile/Projects/Firefox_Accounts_with_Sync_1.1_integration<br />
* Firefox for Desktop: https://wiki.mozilla.org/User_Services/Sync/Relaunch#Desktop_MVP_User_Stories<br />
<br />
== UX Designs ==<br />
* "Sign in to Desktop Firefox" mocks: http://people.mozilla.com/~zfang/FirefoxAccount/PiCL_0710.pdf<br />
* "Sign in to Android Firefox" mocks: https://www.dropbox.com/s/qyvd2p71jifqnmi/picl%20sign%20in%20flows%20android.psd<br />
* Sign in/Sign up Usability Test Results: http://people.mozilla.org/~jgruen/pdx_deck/<br />
<br />
== Technical Materials ==<br />
* [[Identity/AttachedServices/Architecture | High level architecture]]<br />
* Dev mailing list: https://mail.mozilla.org/listinfo/sync-dev<br />
* [https://wiki.mozilla.org/Identity/PiCL/Engineering_Meeting_Notes Meeting notes]<br />
* Authentication<br />
** Protocol: [[Identity/AttachedServices/KeyServerProtocol]]<br />
** Code: https://github.com/mozilla/picl-idp<br />
** API documentation: https://github.com/mozilla/picl-idp/blob/master/docs/api.md<br />
** Dev servers<br />
*** FxA API: http://idp.dev.lcip.org<br />
*** FxA Jelly: http://accounts.dev.lcip.org/flow (code at https://github.com/mozilla/firefox-account-bridge)<br />
** Key stretching performance tests: https://wiki.mozilla.org/Identity/AttachedServices/Key_Stretching_Performance_Tests<br />
* Service discovery<br />
** Device management (sits next to/in key server): TBD<br />
** Meta server: [[User_Services/Meta]]<br />
* Milestone 1 Storage<br />
** Docs<br />
*** Token server API: https://docs.services.mozilla.com/token/apis.html<br />
** Dev servers<br />
*** Token server: http://auth.oldsync.dev.lcip.org/<br />
*** Storage server: http://db1.oldsync.dev.lcip.org/<br />
*** README: https://mail.mozilla.org/pipermail/sync-dev/2013-August/000392.html<br />
* Storage Research<br />
** [[Identity/CryptoIdeas/06-Queue-Sync-CouchDB]]<br />
** [[User:Rnewman/TreeSync]]<br />
** Sync 2.0: http://docs.services.mozilla.com/storage/apis-2.0.html<br />
** Draft data formats: https://mobile.etherpad.mozilla.org/draft-bookmark-password-formats<br />
** Existing sync data formats: http://docs.services.mozilla.com/sync/objectformats.html<br />
* Design musings<br />
** Enumeration of different failures and in existing Sync product: https://mobile.etherpad.mozilla.org/sync-failures<br />
** Design contraints: https://services.etherpad.mozilla.org/2019<br />
** Exploration of the design space: https://mobile.etherpad.mozilla.org/sync-design-faq<br />
** User data stats in the existing Sync product: https://id.etherpad.mozilla.org/picl-user-model<br />
** Technical design review meeting notes: https://etherpad.mozilla.org/identitydesignreview<br />
** FxA client state transitions: [[User_Services/Sync/FxAClientStates]]<br />
* QA plan: https://wiki.mozilla.org/QA/Services/SyncTestPlanV1<br />
* Server deployment planning: [[Identity/AttachedServices/DeploymentPlanning]]<br />
* Bugzilla Meta bugs/dependency trees<br />
** Product user stories: https://bugzilla.mozilla.org/showdependencytree.cgi?id=909322&hide_resolved=0<br />
** Desktop: https://bugzilla.mozilla.org/showdependencytree.cgi?id=905997&hide_resolved=0<br />
** Android: https://bugzilla.mozilla.org/showdependencytree.cgi?id=799726&hide_resolved=0<br />
** Server-side: https://bugzilla.mozilla.org/showdependencytree.cgi?id=907475&hide_resolved=0<br />
* ELM Nightly Builds<br />
** Desktop<br />
*** http://ftp.mozilla.org/pub/mozilla.org/firefox/nightly/latest-elm/ <br />
** Android<br />
*** http://ftp.mozilla.org/pub/mozilla.org/mobile/nightly/latest-elm-android/<br />
*** http://ftp.mozilla.org/pub/mozilla.org/mobile/nightly/latest-elm-android-x86/<br />
*** http://ftp.mozilla.org/pub/mozilla.org/mobile/nightly/latest-elm-android-armv6/<br />
<br />
== User Research ==<br />
TBD<br />
<br />
== Team ==<br />
<br />
=== Identity ===<br />
* Lloyd Hilaiel<br />
* Tauni Oxborrow<br />
* Chris Karlof<br />
* Brian Warner<br />
* Danny Coates<br />
* Zach Carter<br />
* Vlad Filippov<br />
<br />
=== UX ===<br />
* John Gruen<br />
* Ian Barlow<br />
* Zhenshuo Fang<br />
<br />
=== Services ===<br />
* Mark Mayo<br />
* Ryan Kelly<br />
<br />
=== Product ===<br />
* Karen Rudnitski<br />
* Deb Richardson<br />
* Rob Lord<br />
* Chris Lee<br />
<br />
=== Desktop ===<br />
* Gavin Sharp<br />
* Justin Dolske<br />
<br />
=== Android ===<br />
* Mark Finkle<br />
* Richard Newman<br />
* Nick Alexander<br />
<br />
=== FirefoxOS ===<br />
* Dale Harvey<br />
<br />
=== QA ===<br />
* James Bonacci</div>Warnerhttps://wiki.mozilla.org/index.php?title=Identity/AttachedServices/Key_Stretching_Performance_Tests&diff=768837Identity/AttachedServices/Key Stretching Performance Tests2013-11-23T01:12:18Z<p>Warner: fix one other s-vs-ms mistake</p>
<hr />
<div>{{LastUpdated}}<br />
<br />
{| border="1" cellpadding="2"<br />
|+ Test Results (in seconds per operation)<br />
|-<br />
! Browser / Test !! Firefox 23 Desktop (MBP) !! Google Chrome 28 (MBP) !! Firefox Android Beta 24 (Nexus4) !! Firefox OS (Keon) !! Android WebView (Nexus4) !! Android + OpenSSL<br />
|-<br />
! [1] 20k PBKDF2-SHA256<br />
| 186ms (5.39 ops/sec ±4.20%, 30 runs sampled) || 123ms (8.12 ops/sec ±1.15%, 43 runs sampled) || 2.13s (0.47 ops/sec ±7.73%, 7 runs sampled) || 4.17s (0.24 ops/sec ±7.06%, 6 runs sampled) || 1.52s (0.66 ops/sec ±4.35%, 8 runs sampled) || 351ms (2.85 ops/sec, **1 run sampled**)<br />
|-<br />
! [2] 20k*PBKDF + remote(scrypt(64k,8,1)) + 20k*PBKDF<br />
| 1.52s (0.66 ops/sec ±10.70%, 8 runs sampled) || 1.27s (0.79 ops/sec ±1.98%, 8 runs sampled) || 6.25s (0.16 ops/sec ±9.02%, 5 runs sampled) || N/A || 5.56s (0.18 ops/sec ±21.96%, 6 runs sampled) || --<br />
|-<br />
|-<br />
! [2.1] running on m1.large<br />
| 1.05s (0.94 ops/sec ±7.06% (9 runs sampled)) || 846ms (1.18 ops/sec ±0.55% (10 runs sampled)) || 5.25s (0.19 opts/sec ±8.91% (5 runs sampled) || 11.79s (0.08 opts/sec ±1.98% (5 runs sampled) || 3.94s (0.25 opts/sec ±5.78% (6 runs sampled) || --<br />
|-<br />
|-<br />
! [3] 20kPBKDF + local(scrypt(64k,8,1)) + 20kPBKDF<br />
| -- || -- || -- || -- || -- || 7.3s (0.1369 ops/sec, 10 runs sampled, extremes discarded -- 7362-7505msec)<br />
|-<br />
|-<br />
! [4] 20kPBKDF + emscripten(scrypt(64k,8,1)) + 20kPBKDF<br />
| 3.6s (0.28 ops/sec ±26.77%, 6 runs sampled) || 3.2s (0.31 ops/sec ±1.46%, 6 runs sampled) || 20s (0.05 ops/sec ±25.20%, 5 runs sampled) || Crash. || 33s (0.03 ops/sec ±2.86%, 5 runs sampled) || --<br />
|-<br />
|}<br />
<br />
<br />
'''Tests'''<br />
* [1] '''20k PBKDF2-SHA256''', pdbkdf2.derive(...) - Client pdbkdf2 derivation [https://wiki.mozilla.org/Identity/AttachedServices/KeyServerProtocol#stretch-KDF #stretch-KDF]<br />
* [2] '''20k*PBKDF+scrypt(64k,8,1)+20k*PBKDF''' keyStretch.derive(email, password) - Full Key Stretch with a remote scrypt helper at "http://scrypt.dev.lcip.org/" (EC2 m1.small, est scrypt(64k,8,1) time = 2.2s). [https://wiki.mozilla.org/Identity/AttachedServices/KeyServerProtocol#stretch-KDF #stretch-KDF + #main-KDF]. m1.small instance runs scrypt in 2.2s, whereas the more cost-effective (given serious load) c1.medium or c1.xlarge instances will run it in 1.0s .<br />
* [2.1] running on m1.large remote instance <br />
* [3] '''20kPBKDF + local(scrypt(64k,8,1)) + 20kPBKDF'''<br />
* [4] '''20kPBKDF + emscripten(scrypt(64k,8,1)) + 20kPBKDF''' Using the [https://github.com/tonyg/js-scrypt Pure-Javascript Emscripten-compiled scrypt routine]. Modified 'TOTAL_MEMORY' to 'scrypt_module_factory(134217728);'<br />
<br />
<br />
<br />
'''Test Framework'''<br />
<br />
These performance tests use [http://benchmarkjs.com/ Benchmark.js]<br />
<br />
* "ops/sec" stands for operations per second. That is how many times a test is projected to execute in a second.<br />
* A test is repeatedly executed until it reaches the minimum time needed to get a percentage uncertainty for the measurement of less than or equal to 1%. The number of iterations will vary depending on the resolution of the environment’s timer and how many times a test can execute in the minimum run time. We collect completed test runs for 5 seconds (configurable), or at least 5 runs (also configurable), and then perform statistical analysis on the sample. So, a test may be repeated 100,000 times in 50 ms (the minimum run time for most environments), and then repeated 100 times more (5 seconds). A larger sample size (in this example, 100), leads to a smaller margin of error.<br />
* benchmark.js returns ops/sec, rounded to hundredths. This number was inverted to obtain secs/op, losing precision in the process.<br />
<br />
'''Hardware Used'''<br />
<br />
* Firefox Desktop, Google Chrome - MacBook Pro 2.7 GHz i7, 16 GB 1600MHz DDR3<br />
* Firefox OS - Keon (Snapdragon S1 7225AB 1Ghz, 512 RAM)<br />
* Firefox Android, Android Webview - Nexus 4 (1.512 GHz quad-core, 2GB RAM)<br />
<br />
Test Suite Page: [http://v14d.com/picl/benchmark.html http://v14d.com/picl/benchmark.html]</div>Warnerhttps://wiki.mozilla.org/index.php?title=File:PICL-IdPAuth-session-start.png&diff=768735File:PICL-IdPAuth-session-start.png2013-11-22T22:48:33Z<p>Warner: </p>
<hr />
<div></div>Warnerhttps://wiki.mozilla.org/index.php?title=Identity/AttachedServices/KeyServerProtocol&diff=768733Identity/AttachedServices/KeyServerProtocol2013-11-22T22:47:11Z<p>Warner: add use-the-auth-token high-level picture</p>
<hr />
<div>= PiCL Key Server / IdP Protocol =<br />
<br />
NOTE: This specification is slowly converging on stability (31-Jul-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.<br />
<br />
The server is being developed in https://github.com/mozilla/picl-idp . This repo currently includes a demonstration client (node.js CLI).<br />
<br />
Note that all messages are delivered over an HTTPS connection. The client browser may also implement cert-pinning to improve on the certificate validation process. The protections described below are in addition to those provided by TLS.<br />
<br />
Remaining TODO items:<br />
<br />
* decide on client-side key-stretching parameters: http://keywrapping.appspot.com can help<br />
* finalize SRP questions (definition of M1, generation of a/b)<br />
* finalize proof-of-work/DoS-prevention details<br />
* decide how to rate-limit account-creation calls<br />
* confirm this is actually implementable inside Firefox (especially w.r.t. NSS and Android/Java crypto)<br />
<br />
= Creating The Account =<br />
<br />
The first act performed by a user is to create the account. They enter email+password into their browser, which then does the following steps:<br />
<br />
* decide upon stretching parameters (perhaps consulting the keyserver for recommendations, but imposing a minimum strength requirement). For now we used a fixed {firstPBKDF:20000, scrypt: {N:65536, r:8, p:1}, secondPBKDF:20000}.<br />
* randomly choose a 32-byte mainSalt (this should be unique, but is not secret)<br />
* choose the SRP group parameters (fixed: use the 2048-bit group described below)<br />
* perform key-stretching (as described below), derive stretchedPW and srpPW<br />
* randomly choose a 32-byte srpSalt (unique, but not secret)<br />
* create srpVerifier from srpPW and srpSalt (as described below)<br />
* deliver (email, stretchParams, mainSalt, srpParams, srpSalt) to the keyserver's "POST /account/create" API<br />
<br />
The server, when creating a new account, creates both kA and wrap(kB) as randomly-generated 256-bit (32-byte) strings. It stores these, along with all the remaining values, indexed by email, in the account table where they can be retrieved by getToken later.<br />
<br />
== Email Verification ==<br />
<br />
To prevent fixation attacks, we require new accounts to verify their configured recovery email address before letting them learn the generated keys or obtain a signed certificate. Nevertheless, we wish clients to forget the user's password while they wait for email verification to complete. To achieve this, clients can obtain a sessionToken before verification, but most APIs that require it will raise errors until verification is finished.<br />
<br />
The server will send email with a URL that contains a long random "verification code" in the "fragment" hash. This URL points to a static page with some javascript that submits the code to the "POST /account/recovery_methods/verify_code" API. The URL can be clicked by any browser (it is not bound to anything), and when the API is hit, the account is marked as verified.<br />
<br />
After the client submits /account/create, it performs the "/session/auth" login sequence below to obtain a sessionToken. It then polls the "GET /account/recovery_methods" (which requires a sessionToken but not account verification) until the user clicks the email link and the API reports verification is complete. Then the client uses "GET /account/keys" and "POST /certificate/sign", described below, to obtain kA, kB, and a signed certificate to talk to the storage server.<br />
<br />
= Login: Obtaining the authToken =<br />
<br />
To connect a browser to an existing account, we use the following login protocol to transform an email+password pair into a single-use authToken. We will use this in the next section to create a session, from which we can obtain encryption keys and signed certificates.<br />
<br />
This protocol starts by using key-stretching to transform the email+password into a "stretchedPW", then feeds this into an SRP protocol to get the authToken.<br />
<br />
[[File:PICL-IdPAuth-auth-start.png|IdP Auth Protocol]]<br />
<br />
This authToken can be used (once) to do one of the following:<br />
<br />
* /session/create: obtain a sessionToken (and keyFetchToken), which enables storage server access<br />
* /password/change: obtain an accountResetToken, to safely reset the account password<br />
* /account/delete: to delete the entire account<br />
<br />
The protocol is designed to enable parallelism between key-stretching and the initial network messages, to reduce the time it takes to connect a browser to the account. In total, the browser requires five messages in four roundtrips (1: /auth/start, 2: /auth/finish, 3: /session/create, 4: /account/keys and /certificate/sign in parallel) before it is ready to talk to the storage server.<br />
<br />
== /auth/start ==<br />
<br />
As soon as the user finishes typing in the email address, the client should send it in the "/auth/start" 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 loginSRPToken that is used to associate this request with the subsequent /auth/finish 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.<br />
<br />
To mitigate DoS abuse, /auth/start may also require a proof-of-work string, described below.<br />
<br />
== Client-Side Key Stretching ==<br />
<br />
"Key Stretching" is the practice of running a password through a computationally-expensive one-way function before using it for encryption or authentication. The goal is to make brute-force dictionary attacks more expensive, by raising the cost of testing each guess.<br />
<br />
To protect the user's class-B data against compromises of our keyserver, we perform this key stretching on the client, rather than on the server. We use the memory-hard "scrypt" function (pronounced "ess-crypt") for this purpose, as motivated by the attacker-cost studies in [[Identity/CryptoIdeas/01-PBKDF-scrypt]]. Slower (low-RAM) devices can still participate by outsourcing the scrypt step to an "scrypt helper". To provide at least minimal protection against a compromise of this helper, we sandwich the scrypt step between two PBKDF2 steps. The complete protocol looks like this:<br />
<br />
[[File:PICL-IdPAuth-stretch-KDF.png|Stretching KDF]]<br />
<br />
Our initial parameter choices for this stretching are to use 20000 iterations of PBKDF2 on each side (40k in all), and to run scrypt with N/r/p set to 64k/8/1. This requires roughly 60MB of RAM for about 1.3 seconds on a 1.8GHz Intel Atom linux box (it will run faster, but use the same memory, on more modern CPUs). We are still studying performance on various platforms to decide what parameters to use in V1: lowering them speeds up the login process, but reduces security (by reducing the cost of an dictionary attack).<br />
<br />
Since the stretching is expected to take a second or two, the client can optimistically start this process (using default parameters) before receiving the auth/start 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 "mainSalt" is added *after* the stretching, to enable this parallelism (at a tiny cost in security).<br />
<br />
After "stretchedPW" is derived, a second HKDF call is used to derive "srpPW" and "unwrapBKey" which will be used later.<br />
<br />
[[File:PICL-IdPAuth-main-KDF.png|masterKey KDF]]<br />
<br />
== SRP Protocol Details ==<br />
<br />
The PiCL client uses the SRP protocol (http://srp.stanford.edu/) to prove that it knows the (stretched) account password without revealing the actual password (or information enabling a brute-force attack) to the server or any eavesdroppers. <br />
<br />
SRP is somewhat underspecified. We use SRP-6a, with SHA256 as the hash, and the 2048-bit modulus defined in RFC 5053 Appendix A. We consistently zero-pad all string values to 256 bytes (2048 bits), and use H(A+B+S) as the key-confirmation message "M1". These details, plus the SRP design papers and RFCs 2945 and 5054, should be enough to build a compatible implementation. The diagrams below and the test vectors at the end of this page can be used to verify compatibility.<br />
<br />
The server should use Jed's SRP module from https://github.com/jedp/node-srp . The client might use SJCL (http://crypto.stanford.edu/sjcl/) or native code (NSS).<br />
<br />
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).<br />
<br />
Note that SRP-6a uses a "k" value which basically encodes the group being used ("N" and "g"). Since all PICL accounts use the same 2048-bit group, they will all use the same "k" value (not to be confused with the per-session shared-secret "K" key that emerges from the protocol). This group's "k" integer is (as a base-10 number):<br />
2590038599070950300691544216303772122846747035652616593381637186118123578112<br />
<br />
=== SRP Verifier Calculation ===<br />
<br />
When the client first creates the account, it must combine the account email address, the stretched password (srpPW), and a randomly-generated srpSalt, to compute the srpVerifier. The server will use this verifier later, to check whether or not the client really knows the password.<br />
<br />
If the server is compromised and an attacker learns the srpVerifier for a given account, they cannot use this to directly log in (the verifier is not "password-equivalent"), but it does allow them to perform an offline brute-force attack against the user's password. In this respect, it is similar to a traditional hashed password. We make these attacks somewhat more expensive by performing the client-side stretching described above, instead of using the raw user password in the SRP calculation.<br />
<br />
[[File:PICL-IdPAuth-SRP-Verifier.png|client-side SRP Verifier calculation]]<br />
<br />
Test vectors are provided at the end of this page. The protocol requires that several values ("a", "b", "srpSalt") are chosen randomly, but for illustrative purposes, the test vectors use carefully-crafted non-random values. For the user's sake, please ensure implementations use proper random values instead of simply copying the test vectors.<br />
<br />
The client sends srpSalt and srpVerifier to the server when it creates the account. It will also re-compute the 'x' value (as an integer) during sign-in. The server will convert the srpVerifier string back into an integer ('v') for use during its own sign-in calculations.<br />
<br />
=== SRP Server-side Sign-In Flow ===<br />
<br />
When the user connects a new device to their account, they use the /auth/start API to start the SRP protocol. This sends the account email address to the server. The server looks up the stored srpVerifier for this account, creates a random 'b' integer, performs some math to compute the "B" number, then converts B into a string known as "srpB". "srpB" is returned to the client, along with srpSalt and the key-stretching parameters. "b" and "srpB" are retained for the subsequent /auth/finish call. '''Note that it is critical that the "b" integer remain secret on the server.''' The server also allocates a random loginSRPToken to connect the two calls.<br />
<br />
[[File:PICL-IdPAuth-SRP-Server.png|server-side SRP]]<br />
<br />
Later, /auth/start will be called with the client's srpA string and its M1 key-confirmation string. srpA is combined with srpB to calculate the "u" integer. srpA is also turned into an integer and used to compute the shared-secret "S" integer. S is then used to compute the shared key "srpK", which is the output of the SRP process.<br />
<br />
=== SRP Client Calculation ===<br />
<br />
While the client is waiting for the response to /auth/start, it begins its key-stretching calculations. Everything else must wait until the response to /auth/start arrives, which includes the key-stretching parameters (which are retroactively confirmed), srpSalt, and the server's generated srpB value.<br />
<br />
[[File:PICL-IdPAuth-SRP-Client.png|client-side SRP]]<br />
<br />
Once the client knows srpSalt, it computes the same "x" integer as it did in the middle of the srpVerifier calculation. It also converts srpB into an integer named "B". Then it creates a random "a" integer, uses it to compute the string "srpA", then combines srpA with the server's srpB to compute the "u" integer. It then combines the static "k", the password-derived "x", the combined "u", and the server's "B", together with some magic math, to derive the "S" integer. If everything went well, the client will compute the same "S" value as the server did. If not (the password was wrong, or the client is talking to a fake server that doesn't really know srpVerifier), then the two "S" values will not match.<br />
<br />
('''Again, it is critical that the client keep its "a" and "x" integers secret, both during and after the protocol run.''')<br />
<br />
To safely tell if the "S" values match, both client and server combine srpA, srpB, and their (independently) generated "S" strings to form a string named "M1". The client sends M1 (along with srpA) in the /session/auth/finish message. The server compares the client's copy of M1 against its own. If they match, the client knew the password and the server can safely respond with the encrypted account data. If they do not match, the client (or a man-in-the-middle attacker) did not know the password, and the client should increment a counter that can trigger defenses against online guessing attacks. The server must then return an error to the client, and '''not''' use or reveal srpK (or the correct M1) in any way.<br />
<br />
Both client and server also hash "S" into "srpK". This is the shared session key, from which specific message encryption and MAC keys are derived (as described below).<br />
<br />
=== SRP Notes ===<br />
<br />
The SRP "g" (generator) and "N" (prime modulus) should use the 2048-bit value from RFC 5054 Appendix A. 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.<br />
<br />
There are several places in SRP where integers (usually in the range 1..N-1) are converted into bytestrings, either for transmission over a wire, or to be passed into a hash function. The SRP spec is somewhat ambiguous about padding here: if the integer happens to be less than 2^2040, the simplest toString() approach will yield a '''255''' byte string, not a 256 byte string. PiCL consistently uses padding, so compatible implementations must prepend one or more NUL bytes to these short strings before transmission or hashing. The test vectors below were brute-forced to ensure that "srpVerifier", "srpA", "srpB", and "S" all wind up with leading zeros, to exercise the padding code in compatible implementations. If you are having problems getting your code to match these results, add some assertions to test that the stringified integers being put into hashes are exactly 256 bytes long.<br />
<br />
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 (/auth/finish).<br />
<br />
The server receives "A" in /auth/finish, 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 keyFetchToken+sessionToken as described below, returning the encrypted/MACed bundle in the response to /auth/finish.<br />
<br />
On the server, it is critical to reject an "A" value that is 0, or some other multiple of N. If the server does not check this, anybody can trivially sign in to any account without knowing the password. Likewise, it is critical for the client to reject a "B" value where B%N==0. If the client does not check this, the server (or an attacker pretending to be the server) will get a value that can be used in an offline brute-force search for the user's password.<br />
<br />
Outstanding crypto questions:<br />
<br />
* How exactly should the "a" and "b" integers be generated? The issue is of how much bias SRP can tolerate. Ideally these integers are uniformly distributed from 1 to N-1 (inclusive). The only way to obtain a purely uniform distribution from a source of random bytes is try-try-again: pick a (integral-number-of-bytes) number, compare it to the desired range, try again if it falls outside the range. If you get unlucky, this can take a lot of guesses, depending upon how close the range is to a power of two. ECDSA implementations tend to pick a number twice as long as the modulus and then modulo it down (rand(2^4096) % N), which yields a tiny fraction of a bit of bias. The cheaper approach is to do the same with a number of equal length (rand(2^2048) % N), which imposes more bias. Some SRP implementations appear to be satisfied by rand(2^256). We need more review here.<br />
* The original SRP papers defined M1=H(A+B+S) as we use here, but other implementation (in particular RFC2945) uses a curious construct that includes the username, the salt, and an odd XOR combination of N and g. We need to decide what to use for M1.<br />
<br />
== /auth/finish ==<br />
<br />
The client-side SRP calculation results in two values that are sent to the server in the /auth/finish 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.<br />
<br />
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.<br />
<br />
The server then allocates a single-use 32-byte random token named "authToken". It encrypts the token with the session key, and returns a success message with the encrypted bundle.<br />
<br />
All tokens have an associated tokenID, described below. The server needs to maintain a table that maps the tokenID to the token itself, so it can derive other values from the token later. The tokens are also associated with a specific account, so later API requests do not specify an email address or account ID.<br />
<br />
== Decrypting the /auth/finish Response ==<br />
<br />
The SRP session key ("srpK") is used to derive two other keys: respHMACkey and respXORkey.<br />
<br />
[[File:PICL-IdPAuth-encrypt-authToken.png|Decrypting the authToken]]<br />
<br />
The respXORkey is used to encrypt the authToken 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.<br />
<br />
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 returns the authToken value.<br />
<br />
= After Login: Using the authToken =<br />
<br />
After the authToken is acquired, the client can create a session and fetch the encryption keys. The high-level flow looks like this:<br />
<br />
[[File:PICL-IdPAuth-session-start.png|Using the authToken]]<br />
<br />
= Creating a Session =<br />
<br />
For login, the single-use authToken is spent on a call to /session/create .<br />
This allocates two new (random 32-byte) tokens: a long-lived "sessionToken", and a single-use "keyFetchToken". The /session/create call returns an encrypted bundle containing the two tokens.<br />
<br />
[[File:PICL-IdPAuth-encrypt-sessionToken.png|Decrypting the sessionToken and keyFetchToken]]<br />
<br />
For calls which accept an authToken, the client uses authToken to derive three values:<br />
<br />
* tokenID<br />
* reqHMACkey<br />
* requestKey<br />
<br />
<br />
The client uses tokenID and reqHMACkey for a HAWK (https://github.com/hueniverse/hawk/) request to the "POST /session/create" API, using tokenID as "credentials.id" and reqHMACkey as "credentials.key". The server uses tokenID to look up the corresponding token, then derives reqHMACkey to validate the request.<br />
<br />
Each authToken-using call then derives additional API-specific values from requestKey. /session/create uses two derived values:<br />
<br />
* respHMACkey<br />
* respXORkey<br />
<br />
<br />
When the server receives a valid /session/create request, it allocates sessionToken and keyFetchToken, concatenates them, encrypts the pair by XORing it with the derived respXORkey, and attaches a MAC generated with respHMACkey. The encrypted MACed bundle is returned to the client.<br />
<br />
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 keyFetchToken and sessionToken values.<br />
<br />
Each authToken is single-use: once a successful request has been made with it, the authToken and its corresponding ID is removed from the server's memory, and subsequent attempts to use it will return a "no such token" error. The token is consumed even if the request fails (e.g. the MAC did not match).<br />
<br />
The authToken can be used (once) by multiple APIs. The server only needs to maintain one table mapping tokenID to authToken, because all these APIs share the same method of deriving a tokenID and reqHMACkey from the authToken.<br />
<br />
When a HAWK request appears, it should look up the tokenID in this table, retrieve the authToken, recompute reqHMACkey and requestKey, validate the request, delete the table entry, then hand the HTTP request and requestKey to the specific API handler. That handler should derive the API-specific values (respHMACkey, respXORkey, etc) to process the request or construct the response.<br />
<br />
The server can support multiple sessions per account (typically one per client device, plus perhaps others for account-management portals). There can also be multiple outstanding keyFetchTokens. The sessionToken lasts forever (until revoked by a password change or explicit revocation command), and can be used an unlimited number of times. The keyFetchToken expires after 60 seconds, and is single-use.<br />
<br />
= Obtaining keys kA and kB =<br />
<br />
Clients which have exchanged an authToken for either a sessionToken or an accountResetToken will also receive a keyFetchToken. This single-use token allows the client to retrieve kA and wrap(kB), which enables the client to encrypt and decrypt browser data (bookmarks, open-tabs, etc) correctly. As above, the keyFetchToken is used to derive tokenID, reqHMACkey, respHMACkey, and respXORkey, which are used in a HAWK request to the "GET /account/keys" API.<br />
<br />
The server pulls kA and wrap(kB) from the account table, concatenates them, encrypts the pair by XORing it with the derived respXORkey, and attaches a MAC generated with respHMACkey.<br />
<br />
[[File:PICL-IdPAuth-keys-server.png|keyFetchToken: server encrypts keys]]<br />
<br />
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 and wrap(kB) values.<br />
<br />
[[File:PICL-IdPAuth-keys-client.png|keyFetchToken: client decrypts keys]]<br />
<br />
Finally, the server-provided wrap(kB) value is simply XORed with the password-derived unwrapBKey (both are 32-byte strings) to obtain kB. There is no MAC on wrap(kB).<br />
<br />
[[File:PICL-IdPAuth-key-unwrap.png|unwrapping kB]]<br />
<br />
"kA" and "kB" enable the browser to encrypt/decrypt synchronized data records. They will be used to derive separate encryption and HMAC keys for each data collection (bookmarks, form-fill data, saved-password, open-tabs, etc). This will allow the user to share some data, but not everything, with a third party. The client may intentionally forget kA and kB (only retaining the derived keys) to reduce the power available to someone who steals their device.<br />
<br />
Note that /account/keys will not succeed until the account's email address has been verified. Also note that each keyFetchToken is single-use and short-lived. The token is consumed even if the request fails (e.g. the MAC does not match).<br />
<br />
= Signing Certificates =<br />
<br />
The sessionToken is used to derive two values:<br />
<br />
* tokenID<br />
* request HMAC key<br />
<br />
<br />
[[File:PICL-IdPAuth-use-session.png|Using the sessionToken, signing certificates]]<br />
<br />
The requestHMACkey is used in a HAWK request to provide integrity over many APIs, including /certificate/sign. requestHMACkey 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.<br />
<br />
For /certificate/sign, it is critical to enable payload verification by setting options.payload=true (on both client and server). Otherwise a man-in-the-middle could submit their own public key, get it signed, and then delete the user's data on the storage servers.<br />
<br />
The following keyserver APIs require a HAWK-protected request that uses the sessionToken. In addition, some require that the account be in the "verified" state:<br />
<br />
* GET /account/devices<br />
* POST /session/destroy<br />
* GET /recovery_email/status<br />
* POST /recovery_email/resend_code<br />
* POST /certificate/sign (requires "verified" account)<br />
<br />
= Resetting The Account =<br />
<br />
The account may be reset in two circumstances: when the user changes their password, and when the user forgets their password. In both cases, the client first obtains an "accountResetToken". This token is then used to change the SRP Verifier and either reset or replace the wrap(kB) value.<br />
<br />
== Changing the Password ==<br />
<br />
When the user wishes to change their password (i.e. they still know the old password), they first use the /auth/start+/auth/finish SRP protocol safely obtain an "authToken". They they use the "/password/change/start" API to exchange the authToken for an accountResetToken and a keyFetchToken.<br />
<br />
[[File:PICL-IdPAuth-encrypt-passwordChange.png|Server encrypts passwordChange response]]<br />
<br />
The accountResetToken will be used below to set the new password safely. The keyFetchToken should be used first, to obtain kB, so the subsequent account reset can replace wrap(kB) with a new value. This allows the password-changing client to retain their class-B data.<br />
<br />
Requiring an authToken proves that the user has provided the correct account password recently. When the account is reset, all active sessions and tokens will be cancelled (disconnecting all devices from the account). The client should immediately establish a new session as described above.<br />
<br />
This API is only used when the user knows their old password: if they have forgotten the password, use the "/password/forgot" APIs below.<br />
<br />
== Handling a Forgotten Password ==<br />
<br />
When the user has forgotten their password, they can use one of their "recovery methods" to obtain an accountResetToken. For now, this means we send a random code to the email address associated with their account. The user must copy this code from the email into their client, whereupon the client will get an accountResetToken that can be delivered to the API below.<br />
<br />
Note that, since the forgotten-password client never learns kB, any class-B data will be lost. This is necessary to protect class-B data from attackers who can read the users's email but do not know the account password (including those who compromise the IdP and the keyserver itself). When using /account/reset below, they will set wrap(kB) to a string of all zeros, which means the server should generate a new random wrap(kB) (just as it does during account creation).<br />
<br />
The /password/forgot/send_code API is used to ask the server to send a recovery code. This takes a recovery method, which for now is just an email address. This API is unauthenticated (after all, the user who has forgotten their password knows nothing but their email address). The server marks the corresponding account as "pending recovery", allocates a random forgotPasswordToken for the account, creates a recovery code, and sends the code (with instructions) via email. The API returns forgotPasswordToken to the client.<br />
<br />
The user must copy the recovery code into the same browser where they started the process. The client then submits the code to /password/forgot/verify_code along with the forgotPasswordToken they received. If they match, the server allocates a accountResetToken and returns it to the client. If they do not match, the server increments a counter (which is used to decide if an online guessing attack is happening).<br />
<br />
forgotPasswordToken can be used three times before it is exhausted. If the user guesses incorrectly this often, the client must call send_code again to get a new token and code. Each account has at most one token+code active at a time.<br />
<br />
The recovery code is initially a random 8-digit decimal number. If an attacker tries to sign in as someone else, hits the "forgot my password" button, then submits a guess to /password/forgot/verify_code, they will have a 1-in-100-million chance of success. If the server detects too many wrong guesses, it should increase the length of new codes. Another defensive technique is to require that users click an email link before being given the code: the server is told when the link is clicked, so the code will not be enabled until the email has been read. It remains to be seen whether this will be sufficient.<br />
<br />
The exact thresholds are TBD, but a nominal goal is to keep the chances of any attack succeeding to below 1-in-a-million per year. To achieve this, we can tolerate 100 verify_code failures in a single year before we must increase the length of the code.<br />
<br />
== Using accountResetToken ==<br />
<br />
An accountResetToken, obtained through either of the methods above, is used to invoke the /account/reset API. If the request is accepted, the server replaces the account password with a new one, updates the wrap(kB) value, cancels all active sessions and tokens (disconnecting all devices from the account), and sends a "your password has been changed" email to the user.<br />
<br />
If the client knew the old password, it can supply a new wrap(kB) value that will yield the same kB key as before, so no data will be lost. If the client did *not* know the old password, then it will supply a wrap(kB) of all-zeros, which tells the server to generate a new random wrap(kB) (just like during account creation), which means the class-B data will be lost.<br />
<br />
The client puts their new password through the same stretching procedure as described in the new-account section above, resulting in a new srpVerifier and unwrapBKey. If they knew the old kB, they XOR it with the new unwrapBKey to obtain a new wrap(kB).<br />
<br />
/account/reset needs request confidentiality, since the arguments include the newly wrapped kB value and the new SRP verifier, both of which enable a brute-force attack against the password. HAWK provides request integrity. The response is a single "ok" or "fail", conveyed by the HTTP headers, so we do not require response confidentiality, and can live without response integrity.<br />
<br />
So the single-use resetToken is used to derive three values:<br />
<br />
* tokenID<br />
* request HMAC key<br />
* request XOR key<br />
<br />
<br />
[[File:PICL-IdPAuth-encrypt-resetAccount.png|Client encrypts resetAccount request]]<br />
<br />
The request data will contain wrap(kB) and the new SRP verifier, concatenated together. Since we always pad the SRP verifier to the full (256-byte) group length, both pieces are fixed-length. We generate enough reqXORkey bytes to cover both values.<br />
<br />
The request data is XORed with requestXORkey, then delivered in the body of a HAWK request that uses tokenID as credentials.id and requestHMACkey as credentials.key . Note: it is critical to include the request body in the HAWK integrity check (options.payload=true, on both client and server), otherwise a man-in-the-middle could substitute their own SRP verifier, giving them control over the account (access to the user's class-A data, and a brute-force attack on their password).<br />
<br />
The client submits other values in the same request:<br />
<br />
* stretchParams<br />
* mainKDFSalt<br />
* srpSalt<br />
<br />
<br />
These values do not require confidentiality, so are not included in the encrypted bundle. They are still protected by the HAWK integrity check. Note that the server should assert that both salts are different than the previously stored values.<br />
<br />
After using /account/reset, clients should immediately perform the login protocol from above. If the old password was forgotten, this is necessary to fetch kA. In either case, a new sessionToken is required, since old sessions and tokens are revoked by /account/reset. Clients should retain the new srpPassword value during this process to avoid needing to run the lengthy key-stretching routine a second time.<br />
<br />
= Deleting The Account =<br />
<br />
When the user wishes to completely delete their account, the browser needs to perform two actions:<br />
<br />
* contact the storage servers and delete all records and collections<br />
* contact the keyserver and delete the account information<br />
<br />
The user should be prompted for their password as confirmation (i.e. a browser in the normal attached-and-synchronizing state should not be able to erase the account information: it must acquire a new authToken first).<br />
<br />
The device then obtains an authToken as described above, then spends it on a HAWK-protected request to the /account/destroy endpoint. This request contains no body and returns only a success code.<br />
<br />
[[File:PICL-IdPAuth-deleteAccount.png|Deleting the Account]]<br />
<br />
= Crypto Notes =<br />
<br />
Strong entropy is needed in the following places:<br />
<br />
* (client) creation of private "a" value inside SRP<br />
* (server) initial creation of kA and wrap(kB)<br />
* (server) creation of private "B" value inside SRP<br />
* (server) creation of signToken and resetToken<br />
<br />
<br />
On the server, code should get entropy from /dev/urandom via a function that uses it, like "crypto.randomBytes()" in node.js or "os.urandom()" in python. On the client, code should combine local entropy with some fetched from the keyserver via getEntropy(), to guard against failures in the local entropy pool. Something like HKDF(IKM=localEntropy+remoteEntropy, salt="", info=KW("mergeEntropy")).<br />
<br />
An HKDF-based stream cipher is used to protect the response for getToken2(), and the request for resetAccount(). HKDF is used to create a number of random bytes equal to the length of the message, then these are XORed with the plaintext to produce the ciphertext. An HMAC is then computed from the ciphertext, to protect the integrity of the message.<br />
<br />
HKDF, like any KDF, is defined to produce output that is indistinguishable from random data ("The HKDF Scheme", http://eprint.iacr.org/2010/264.pdf , by Hugo Krawczyk, section 3). XORing a plaintext with a random keystream to produce ciphertext is a simple and secure approach to data encryption, epitomized by AES-CTR or a stream cipher (http://cr.yp.to/snuffle/design.pdf). HKDF is not the fastest way to generate such a keystream, but it is safe, easy to specify, and easy to implement (just HMAC and XOR).<br />
<br />
Each keystream must be unique. SRP is defined to produce a random session key for each run (as long as at least one of the sides provides a random ephemeral key). We define resetToken to be a single-use randomly-generated value. Hence our two HKDF-XOR keystreams will be unique.<br />
<br />
A slightly more-traditional alternative would be to use AES-CTR (with the same HMAC-SHA256 used here), with a randomly-generated IV. This is equally secure, but requires implementors to obtain an AES library (with CTR mode, which does not seem to be universal). An even more traditional technique would be AES-CBC, which introduces the need for padding and a way to specify the length of the plaintext. The additional specification complexity, plus the library load, leads me to prefer HKDF+XOR.<br />
<br />
kB is equal to the XOR of wrapKey (which is a deterministic function of the user's email address, password, mainSalt, and the stretching parameters) and the server's randomly-generated wrap(kB) value, making kB a random value too. Using XOR as a wrapping function allows us to avoid sending kB or wrap(kB) in the initial createAccount arguments, which are not protected by SRP, and thus would enable an eavesdropper to mount a dictionary attack on the password (using wrap(kB) as their oracle).<br />
<br />
Likewise, allowing the server to generate kA avoids exposure during createAccount. The only point of vulnerability is a forgotten-password resetAccount() message, if an eavesdropper can learn the resetToken. In this case, the attacker might either use the resetToken themselves (then use signToken to learn kA and wrap(kB)), or passively decrypt the resetAccount() arguments to retrieve just wrap(kB). Given wrap(kB) and some encrypted browser data, the attacker can guess passwords (and derive kB, etc) until the data decrypts properly.<br />
<br />
To make this technique safe, any time kB or the password is changed, the mainSalt should be changed too. Otherwise knowledge of both wrap(old-kB) and old-kB would reveal wrapKey, making it easy to deduce the new kB. Changing mainSalt causes wrapKey to change too, preventing this.<br />
<br />
mainSalt is incorporated at the end of the stretching process to allow it to proceed in parallel with the getToken1() call that retrieves the salt. The inputs to the lengthy stretch come entirely from the user (email and password) or are optimistically (stretching parameters). This speedup seems more important than the minor security benefit of including the salt at the beginning of the stretch.<br />
<br />
There is no MAC on wrap(kB). If the keyserver chooses to deliver a bogus wrap(kB) or kA, the client will discover the problem a moment later when it talks to a storage server and attempts to retrieve data from an unrecognized collection-ID (since we intend to derive collection-IDs from the key used to encrypt their data, which will be derived from kA or kB as appropriate). It might be useful to add a checksum to kA and wrap(kB) to detect accidental corruption (e.g. store and deliver kA+SHA256(kA)), but this doesn't protect against intentional changes. We omit this checksum for now, assuming that disks will be reliable enough to let us never experience such failures.<br />
<br />
We use scrypt without a per-user salt. This is safe because the "password" input to scrypt is already diversified by the user's email address. The intention is to allow the scrypt-helper to run on anonymous data, so that an attacker who compromises this helper cannot easily learn the email addresses of the partially-stretched K1 values that it is receiving, confounding their attack.<br />
<br />
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. <br />
For /certificate/sign, we do not need request confidentiality or response confidentiality, since the client's pubkey and the resulting certificate will both be exposed over a similar SSL connection to the storage server later. And it is sufficient to rely on the response integrity provided by SSL, since the client can verify the returned certificate for itself. For the other keyserver APIs protected by HAWK, these properties are either unnecessary, or are provided by additional mechanisms.<br />
<br />
= Proof-Of-Work =<br />
<br />
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(). The server can control how much work is required.<br />
<br />
The getToken1() call looks for a "X-PiCL-PoW:" HTTP header. Most of the time, clients don't supply this header. But if the server responds to the getToken1() call with an error that indicates PoW is required, clients must create a valid PoW string and include it as the value of an "X-PiCL-PoW:" header in their next call to getToken1().<br />
<br />
The server's error message includes two parameters. The first is a "prefix string": the client's PoW string is required to begin with this prefix. The second is a "threshold hash". SHA256(PoWString) is required to be lexicographically earlier than the thresholdHashString (i.e. the numerical value of its hash must be closer to zero than the threshold). The client is expected to concatenate the prefix with a counter, then repeatedly increment the counter and hash the result until they meet the threshold, then re-submit their getToken1() request with the combined prefix+counter string in the header. If the client has spent more than e.g. 10 seconds doing this, the client should probably help the user cancel the operation and try again.<br />
<br />
When a server is under a DoS attack (either via some manual configuration tool or sensed automatically), it should start requiring valid unique X-PiCL-PoW headers. The server should initially require very little work, by using a threshold hash with just a few leading zero bits. If this is insufficient to reduce the attack volume, the threshold should be lowered, requiring even more work (from both the attacker and legitimate clients).<br />
<br />
The server should create a prefix string that contains a parseable timestamp and a random nonce (e.g. "%d-%d-" % (int(time.time()), b32encode(os.urandom(8)))). The server should also decide on a cutoff time (perhaps ten minutes ago). Each server must then maintain a table of "old PoW strings" to prevent replay attacks (these do not need to be shared among all servers: an in-RAM cache is fine).<br />
<br />
When the server receives a proposed PoW string, it first splits off the leading timestamp, and if the timestamp is older than the cutoff time, it rejects the string (either by dropping the connection, or returning a new "PoW required" error if it's feeling nice). Then it hashes the whole string and compares it against the threshold, rejecting those which fail to meet the threshold. Finally, for strings that pass the hash threshold, it checks the "old strings" table, and rejects any that appear on that list.<br />
<br />
If the PoW string makes it past all these checks, the server should add the string to the "old strings" table, then accept the request (i.e. compute an srpB value and add a session-id table entry for the request).<br />
<br />
The old-strings table check should be optimized to reject present strings quickly (i.e. if we are under attack, we should expect to see lots of duplicates of the same string, and must minimize the work we do when this occurs).<br />
<br />
The server can remove values from the old-strings table that have timestamps older than the cutoff time. The server can also discard values at other times (to avoid consuming too much memory), without losing anything but protection against resource consumption.<br />
<br />
Other notes:<br />
<br />
The server-side code for this can be deferred until we care to have a response to a DoS attack. However the client-side code for this must be present from day one, otherwise we won't be able to turn on the defense later without fear of disabling legitimate old clients.<br />
<br />
The server should perform as little work as possible before rejecting a token. Every extra CPU cycle it spends in this path is increasing the DoS attack amplification factor.<br />
<br />
The nonce in the prefix string exists to make sure that two successive clients get different prefixes, and thus do not come up with the same counter value (and inadvertently create identical strings, looking like a replay attack). If this proved annoying or expensive, we could instead obligate clients to produce their own nonce.<br />
<br />
TBD: Is this worth it? Should the PoW string go into an HTTP header? (I want it to be cheap to extract, and not clutter logs). Should the error response be a distinctive HTTP error code so our monitoring tools can easily count them? We can also use this feature to slow down online guessing attacks (i.e. trigger it either when getToken1 is called too much or when getToken2 produces too many errors). Since getToken1() includes an email address, we could also requires PoWs for some addresses (e.g. those we know to be under attack) but not others.<br />
<br />
= Glossary =<br />
<br />
This defines some of the jargon we've developed for this protocol.<br />
<br />
* data classes: each type of browser data (bookmarks, passwords, history, etc) can be assigned, by the user, to either class-A or class-B<br />
* class-A: data assigned to this class can be recovered, even if the user forgets their password, by proving control over an email address and resetting the account. It can also be read by Mozilla (since it runs the keyserver and knows kA), or by the user's IdP (by resetting the account without the user's permission).<br />
* class-B: data in this class cannot be recovered if the password is forgotten. It cannot be read by the IdP. Mozilla (via the keyserver) cannot read this data, but can attempt a brute-force dictionary attack against the password.<br />
* kA: the master key for data stored as "class-A", a 32-byte binary string. Individual encryption keys for different datatypes are derived from kA.<br />
* kB: the master key for data stored as "class-B", a 32-byte binary string.<br />
* wrap(kB): an encrypted copy of kB. The keyserver stores wrap(kB) and never sees kB itself. The client (browser) uses a key derived from the user's password to decrypt wrap(kB), obtaining the real kB.<br />
* sessionToken: a long-lived per-device token which allows the device to obtained signed BrowserID certificates for the account's identity (GUID@picl-something.org). This token remains valid until the user revokes it (either by changing their password, or triggering some kind of "revoke a specific device" or "revoke all devices" function).<br />
<br />
= Test Vectors =<br />
<br />
The following example uses a non-ASCII email address of "andré@example.org" (with an accented "e", UTF8 encoding is 616e6472c3a9406578616d706c652e6f7267) and a non-ascii password of "pässwörd" (with accents on "a" and "o", UTF8 encoding is 70c3a4737377c3b67264).<br />
<br />
These test vectors were produced by the python code in https://github.com/warner/picl-spec-crypto (revision aa441c6). The diagrams above may lag behind the latest version of that code.<br />
<br />
== stretch-KDF ==<br />
<br />
email:<br />
616e6472c3a94065<br />
78616d706c652e6f<br />
7267<br />
<br />
password:<br />
70c3a4737377c3b6<br />
7264<br />
<br />
K1 (scrypt input):<br />
f84913e3d8e6d624<br />
689d0a3e9678ac8d<br />
cc79d2c2f3d96414<br />
88cd9d6ef6cd83dd<br />
<br />
K2 (scrypt output):<br />
5b82f146a6412692<br />
3e4167a0350bb181<br />
feba61f63cb17140<br />
12b19cb0be0119c5<br />
<br />
stretchedPW:<br />
c16d46c31bee242c<br />
b31f916e9e38d60b<br />
76431d3f5304549c<br />
c75ae4bc20c7108c<br />
<br />
== main-KDF ==<br />
<br />
mainSalt (normally random):<br />
00f0000000000000<br />
0000000000000000<br />
0000000000000000<br />
000000000000034d<br />
<br />
srpPW:<br />
00f9b71800ab5337<br />
d51177d8fbc682a3<br />
653fa6dae5b87628<br />
eeec43a18af59a9d<br />
<br />
unwrapBKey:<br />
6ea660be9c89ec35<br />
5397f89afb282ea0<br />
bf21095760c8c500<br />
9bbcc894155bbe2a<br />
<br />
internal x (base 10):<br />
8192518690918<br />
99580124814080709381476194749939<br />
03899664126296984459627523279550<br />
<br />
internal x (hex):<br />
b5200337cc3f3f92<br />
6cdddae0b2d31029<br />
c069936a844aff58<br />
779a545be89d0abe<br />
<br />
v (verifier as number) (base 10):<br />
114649<br />
57230405843056840989945621595830<br />
71784395917725741221739574165799<br />
54316134303691657140298181419198<br />
87853709633756255809680435884948<br />
69849281177012209169281795507853<br />
57610332070005048463659745521969<br />
83218225819721112680718485091921<br />
64608360806562626442477160609654<br />
43167308814558974899899506977051<br />
96721477608178869100211706638584<br />
53875100985456239693728258285562<br />
04889672594983678412848291529879<br />
88548996842770025110751388952323<br />
22170663943486107183421205517476<br />
84831590615660554713667726412525<br />
73641352721966728239512914666806<br />
49625530438034148797508015907639<br />
67594925530663571631035463732161<br />
30193328802116982288883318596822<br />
<br />
== SRP Verifier ==<br />
<br />
k (base 10):<br />
259003859907<br />
09503006915442163037721228467470<br />
35652616593381637186118123578112<br />
<br />
srpSalt (normally random):<br />
00f1000000000000<br />
0000000000000000<br />
0000000000000000<br />
0000000000000179<br />
<br />
srpVerifier:<br />
00173ffa0263e63c cfd6791b8ee2a40f<br />
048ec94cd95aa8a3 125726f9805e0c82<br />
83c658dc0b607fbb 25db68e68e93f265<br />
8483049c68af7e82 14c49fde2712a775<br />
b63e545160d64b00 189a86708c69657d<br />
a7a1678eda0cd79f 86b8560ebdb1ffc2<br />
21db360eab901d64 3a75bf1205070a57<br />
91230ae56466b8c3 c1eb656e19b794f1<br />
ea0d2a077b3a7553 50208ea0118fec8c<br />
4b2ec344a05c66ae 1449b32609ca7189<br />
451c259d65bd15b3 4d8729afdb5faff8<br />
af1f3437bbdc0c3d 0b069a8ab2a959c9<br />
0c5a43d42082c774 90f3afcc10ef5648<br />
625c0605cdaace6c 6fdc9e9a7e6635d6<br />
19f50af773452247 0502cab26a52a198<br />
f5b00a2798589165 07b0b4e9ef9524d6<br />
<br />
== SRP B ==<br />
<br />
private b (normally random) (base 10):<br />
1198277<br />
66042000957856349411550091527197<br />
08125226960544768993643095227800<br />
29105361555030352774505625606029<br />
71778328003253459733139844872578<br />
33596964143417216389157582554932<br />
02841499373672188315534280693274<br />
23189198736863575142046053414928<br />
39548731879043135718309539706489<br />
29157321423483527294767988835942<br />
53343430842313006332606344714480<br />
99439808861069316482621424231409<br />
08830704769167700098392968117727<br />
43420990997238759832829219109897<br />
32876428831985487823417312772399<br />
92628295469389578458363237146486<br />
38545526799188280210660508721582<br />
00403102624831815596140094933216<br />
29832845626116777080504444704039<br />
04739431335617585333671378812943<br />
<br />
private b (hex):<br />
00f3000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 000000000000000f<br />
<br />
transmitted srpB:<br />
0022ce5a7b9d8127 7172caa20b0f1efb<br />
4643b3becc535664 73959b07b790d3c3<br />
f08650d5531c19ad 30ebb67bdb481d1d<br />
9cf61bf272f84398 48fdda58a4e6abc5<br />
abb2ac496da5098d 5cbf90e29b4b110e<br />
4e2c033c70af7392 5fa37457ee13ea3e<br />
8fde4ab516dff1c2 ae8e57a6b264fb9d<br />
b637eeeae9b5e43d faba9b329d3b8770<br />
ce89888709e02627 0e474eef822436e6<br />
397562f284778673 a1a7bc12b6883d1c<br />
21fbc27ffb3dbeb8 5efda279a69a1941<br />
4969113f10451603 065f0a0126666456<br />
51dde44a52f4d8de 113e2131321df1bf<br />
4369d2585364f9e5 36c39a4dce33221b<br />
e57d50ddccb4384e 3612bbfd03a268a3<br />
6e4f7e01de651401 e108cc247db50392<br />
<br />
== SRP A ==<br />
<br />
private a (normally random) (base 10):<br />
1193346<br />
47663227291363113405741243413916<br />
43482736314616601220006703889414<br />
28162541137108417166380088052095<br />
43910927476491099816542561560345<br />
50331133015255005622124012256352<br />
06121987030570656676375703406470<br />
63422988042473190059156975005813<br />
46381864669664357382020200036915<br />
26156674010218162984912976536206<br />
14440782978764393137821956464627<br />
16314542157937343986808167341567<br />
89864323268060014089757606109012<br />
50649711198896213496068605039486<br />
22864591676298304745954690086093<br />
75374681084741884719851454277570<br />
80362211874088739962880012800917<br />
05751238004976540634839106888223<br />
63866455314898189520502368799907<br />
19946264951520393624479315579863<br />
<br />
private a (hex):<br />
00f2000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 000000000000d3d7<br />
<br />
transmitted srpA:<br />
007da76cb7e77af5 ab61f334dbd5a958<br />
513afcdf0f47ab99 271fc5f7860fe213<br />
2e5802ca79d2e5c0 64bb80a38ee08771<br />
c98a937696698d87 8d78571568c98a1c<br />
40cc6e7cb101988a 2f9ba3d65679027d<br />
4d9068cb8aad6ebf f0101bab6d52b5fd<br />
fa81d2ed48bba119 d4ecdb7f3f478bd2<br />
36d5749f2275e948 4f2d0a9259d05e49<br />
d78a23dd26c60bfb a04fd346e5146469<br />
a8c3f010a627be81 c58ded1caaef2363<br />
635a45f97ca0d895 cc92ace1d09a99d6<br />
beb6b0dc0829535c 857a419e834db128<br />
64cd6ee8a843563b 0240520ff0195735<br />
cd9d316842d5d3f8 ef7209a0bb4b54ad<br />
7374d73e79be2c39 75632de562c59647<br />
0bb27bad79c3e2fc ddf194e1666cb9fc<br />
<br />
== SRP key-agreement ==<br />
<br />
u:<br />
b284aa1064e87751 50da6b5e2147b47c<br />
a7df505bed94a6f4 bb2ad873332ad732<br />
<br />
S:<br />
0092aaf0f527906a a5e8601f5d707907<br />
a03137e1b601e04b 5a1deb02a981f4be<br />
037b39829a27dba5 0f1b27545ff2e287<br />
29c2b79dcbdd32c9 d6b20d340affab91<br />
a626a8075806c26f e39df91d0ad979f9<br />
b2ee8aad1bc783e7 097407b63bfe58d9<br />
118b9b0b2a7c5c4c debaf8e9a460f4bf<br />
6247b0da34b760a5 9fac891757ddedca<br />
f08eed823b090586 c63009b2d740cc9f<br />
5397be89a2c32cdc fe6d6251ce11e44e<br />
6ecbdd9b6d93f30e 90896d2527564c7e<br />
b9ff70aa91acc0ba c1740a11cd184ffb<br />
989554ab58117c21 96b353d70c356160<br />
100ef5f4c28d19f6 e59ea2508e8e8aac<br />
6001497c27f362ed bafb25e0f045bfdf<br />
9fb02db9c908f103 40a639fe84c31b27<br />
<br />
M1:<br />
27949ec1e0f16256<br />
33436865edb037e2<br />
3eb6bf5cb91873f2<br />
a2729373c2039008<br />
<br />
srpK:<br />
e68fd0112bfa31dc<br />
ffc8e9c96a1cbadb<br />
4c3145978ff35c73<br />
e5bf8d30bbc7499a<br />
<br />
== /auth ==<br />
<br />
srpK:<br />
e68fd0112bfa31dc<br />
ffc8e9c96a1cbadb<br />
4c3145978ff35c73<br />
e5bf8d30bbc7499a<br />
<br />
respHMACkey:<br />
6584613597ef012f<br />
f1752b7869f01d03<br />
c72547a7b7199681<br />
531d9df1991edf23<br />
<br />
respXORkey:<br />
455835926ae37a1b<br />
627bd16affbeeab6<br />
27ecc737121826ca<br />
4a2bac2c100bf417<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
plaintext:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
ciphertext:<br />
253957f10e861c7c<br />
0a12bb0193d384d9<br />
579db544666d50bd<br />
3252d6576c768a68<br />
<br />
MAC:<br />
a98c87f5769ab4cc<br />
ca3df863faeb217e<br />
b16ddc29d712b301<br />
12b446324ee806d6<br />
<br />
response:<br />
253957f10e861c7c<br />
0a12bb0193d384d9<br />
579db544666d50bd<br />
3252d6576c768a68<br />
a98c87f5769ab4cc<br />
ca3df863faeb217e<br />
b16ddc29d712b301<br />
12b446324ee806d6<br />
<br />
== authtoken ==<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
tokenID (authToken):<br />
9a39818e3bbe6132<br />
38c9d7ff013a1841<br />
1ed2c66c3565c3c4<br />
de03feefecb7d212<br />
<br />
reqHMACkey:<br />
4a17cbdd54ee17db<br />
426fcd7baddff587<br />
231d7eadb408c091<br />
ce19ca915b715985<br />
<br />
requestKey:<br />
9d93978e662bfc6e<br />
8cc203fa4628ef5a<br />
7bf1ddfd7ee54e97<br />
ec5c033257b4fca9<br />
<br />
== /session ==<br />
<br />
requestKey:<br />
9d93978e662bfc6e<br />
8cc203fa4628ef5a<br />
7bf1ddfd7ee54e97<br />
ec5c033257b4fca9<br />
<br />
respHMACkey:<br />
cd3f50403d060b21<br />
76d32f71ca105bd8<br />
7c9b6c4e10e3ebf9<br />
3f5077bec2db24fa<br />
<br />
respXORkey:<br />
8422c53143dea9c6<br />
044afbe95228f291<br />
74996b830b1794a3<br />
eff132da53174d43<br />
92eeb87ccf8ad7a8<br />
0c432894e066de6b<br />
0ff70658dfbf2f07<br />
b9c7704045edcd54<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
sessionToken:<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
plaintext:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
ciphertext:<br />
04a347b2c75b2f41<br />
8cc37162dea57c1e<br />
e408f9109f820234<br />
7768a841cf8ad3dc<br />
324f1adf6b2f710f<br />
a4ea823f4ccb70c4<br />
bf46b4eb6b0a99b0<br />
017ecafbf95073eb<br />
<br />
MAC:<br />
7973ddbb184b601a<br />
c4df09704028ebfc<br />
754dd50e7d8eebfa<br />
52ce3fd868c69852<br />
<br />
response:<br />
04a347b2c75b2f41<br />
8cc37162dea57c1e<br />
e408f9109f820234<br />
7768a841cf8ad3dc<br />
324f1adf6b2f710f<br />
a4ea823f4ccb70c4<br />
bf46b4eb6b0a99b0<br />
017ecafbf95073eb<br />
7973ddbb184b601a<br />
c4df09704028ebfc<br />
754dd50e7d8eebfa<br />
52ce3fd868c69852<br />
<br />
== /account/keys ==<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
tokenID (keyFetchToken):<br />
3d0a7c02a15a62a2<br />
882f76e39b6494b5<br />
00c022a8816e0486<br />
25a495718998ba60<br />
<br />
reqHMACkey:<br />
87b8937f61d38d0e<br />
29cd2d5600b3f4da<br />
0aa48ac41de36a0e<br />
fe84bb4a9872ceb7<br />
<br />
keyRequestKey:<br />
14f338a9e8c6324d<br />
9e102d4e6ee83b20<br />
9796d5c74bb734a4<br />
10e729e014a4a546<br />
<br />
respHMACkey:<br />
f824d2953aab9faf<br />
51a1cb65ba9e7f9e<br />
5bf91c8d8fd1ac1c<br />
8c2d31853a8a1210<br />
<br />
respXORkey:<br />
ce7d7aa77859b235<br />
9932970bbe2101f2<br />
e80d01faf9191bd5<br />
ee52181d2f0b7809<br />
8281ba8cff392543<br />
3a89f7c3095e0c89<br />
900a469d60790c83<br />
3281c4df1a11c763<br />
<br />
kA:<br />
2021222324252627<br />
28292a2b2c2d2e2f<br />
3031323334353637<br />
38393a3b3c3d3e3f<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
plaintext:<br />
2021222324252627<br />
28292a2b2c2d2e2f<br />
3031323334353637<br />
38393a3b3c3d3e3f<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
ciphertext:<br />
ee5c58845c7c9412<br />
b11bbd20920c2fdd<br />
d83c33c9cd2c2de2<br />
d66b222613364636<br />
c2c0f8cfbb7c6304<br />
72c0bd88451342c6<br />
c05b14ce342c5ad4<br />
6ad89e84464c993c<br />
<br />
MAC:<br />
3927d30230157d08<br />
17a077eef4b20d97<br />
6f7a97363faf3f06<br />
4c003ada7d01aa70<br />
<br />
response:<br />
ee5c58845c7c9412<br />
b11bbd20920c2fdd<br />
d83c33c9cd2c2de2<br />
d66b222613364636<br />
c2c0f8cfbb7c6304<br />
72c0bd88451342c6<br />
c05b14ce342c5ad4<br />
6ad89e84464c993c<br />
3927d30230157d08<br />
17a077eef4b20d97<br />
6f7a97363faf3f06<br />
4c003ada7d01aa70<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
unwrapBKey:<br />
6ea660be9c89ec35<br />
5397f89afb282ea0<br />
bf21095760c8c500<br />
9bbcc894155bbe2a<br />
<br />
kB:<br />
2ee722fdd8ccaa72<br />
1bdeb2d1b76560ef<br />
ef705b04349d9357<br />
c3e592cf4906e075<br />
<br />
== use session (certificate/sign, etc) ==<br />
<br />
sessionToken:<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
tokenID (sessionToken):<br />
c0a29dcf46174973<br />
da1378696e4c82ae<br />
10f723cf4f4d9f75<br />
e39f4ae3851595ab<br />
<br />
reqHMACkey:<br />
9d8f22998ee7f579<br />
8b887042466b72d5<br />
3e56ab0c094388bf<br />
65831f702d2febc0<br />
<br />
== /password/change ==<br />
<br />
requestKey:<br />
9d93978e662bfc6e<br />
8cc203fa4628ef5a<br />
7bf1ddfd7ee54e97<br />
ec5c033257b4fca9<br />
<br />
respHMACkey:<br />
60d01e3d1da53b10<br />
93124a30c26889d7<br />
b2e067e7a09fde14<br />
6f935e3c653614f9<br />
<br />
respXORkey:<br />
3de5bd5e80faf84a<br />
dfca5396148123ef<br />
8184cd4bc10a7c8a<br />
db1688495affee67<br />
e07f80d914c5105f<br />
c86d6af24c4be1b1<br />
ef6c9c661422ac43<br />
181b3d29624a0cc2<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
accountResetToken:<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
plaintext:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
ciphertext:<br />
bd643fdd047f7ecd<br />
5743d91d980cad60<br />
11155fd8559fea1d<br />
438f12d2c66270f8<br />
20be421ad000d698<br />
00a4a03980862f7e<br />
3fbd4eb5c0f77a94<br />
c0c2e7f2be97d21d<br />
<br />
MAC:<br />
804fc4bc30923cc0<br />
d6c07ffea954848e<br />
0076b94f7deee71f<br />
a34db5c106d91980<br />
<br />
response:<br />
bd643fdd047f7ecd<br />
5743d91d980cad60<br />
11155fd8559fea1d<br />
438f12d2c66270f8<br />
20be421ad000d698<br />
00a4a03980862f7e<br />
3fbd4eb5c0f77a94<br />
c0c2e7f2be97d21d<br />
804fc4bc30923cc0<br />
d6c07ffea954848e<br />
0076b94f7deee71f<br />
a34db5c106d91980<br />
<br />
== /account/reset ==<br />
<br />
accountResetToken:<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
tokenID (accountResetToken):<br />
46ec557e56e531a0<br />
58620e9344ca9c75<br />
afac0d0bcbdd6f8c<br />
3c2f36055d9540cf<br />
<br />
reqHMACkey (for HAWK):<br />
716ebc28f5122ef4<br />
8670a48209190a16<br />
05263c3188dfe452<br />
56265929d1c45e48<br />
<br />
requestKey:<br />
aa5906d2318c6e54<br />
ecebfa52f10df4c0<br />
36165c230cc78ee8<br />
59f546c66ea3c126<br />
<br />
reqHMACkey (for ciphertext):<br />
a0d894a6232f2e78<br />
66a51dda3f84e01e<br />
ae5adb812564f391<br />
6c0d3cb16bdb743c<br />
<br />
reqXORkey:<br />
9cbde8fc9df31455 837b881e6c0d7e3c<br />
ca13589bc868c527 95fc00e51f2048ab<br />
d56de37629cda0b0 3f580a9e6c433724<br />
b5df12a735ccf2a1 e232d4f5fef84f86<br />
a1b4fdc47f8d1f73 12a6a230a8742d5b<br />
c144ee9abce25b57 9670b81085064cfb<br />
dcab862d9d57abcc 2142dcdde6682281<br />
d378c89b0dce06ae cd1c1ff68ad6db9a<br />
9cab0b02e160805b 59bb8712c8233056<br />
1b3ded75c430e23c 22338833b6f2ba39<br />
f5015ca7a905d6ee 6ec5b1e3ae5204ba<br />
6f3630ebf30ebbac 1f47329e8fe22770<br />
2a3d61f593328dd4 f0a96b628aa8ffec<br />
181e93d2af8d87ff 2d90d67caaf7f7c9<br />
af024c93cfc79e94 67ba70b3076c20cc<br />
141aa254ff159b25 3125a304441cecf3<br />
4fc1845ce96ee598 21fde83cd24e3209<br />
4d304477bfa2c8ed df236e512560694e<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
newSRPv:<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
<br />
plaintext:<br />
4041424344454647 48494a4b4c4d4e4f<br />
5051525354555657 58595a5b5c5d5e5f<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
<br />
ciphertext:<br />
dcfcaabfd9b65212 cb32c25520403073<br />
9a420ac89c3d9370 cda55abe437d16f4<br />
c47cf26738dcb1a1 2e491b8f7d522635<br />
a4ce03b624dde3b0 f323c5e4efe95e97<br />
b0a5ecd56e9c0e62 03b7b321b9653c4a<br />
d055ff8badf34a46 8761a90194175dea<br />
cdba973c8c46badd 3053cdccf7793390<br />
c269d98a1cdf17bf dc0d0ee79bc7ca8b<br />
8dba1a13f071914a 48aa9603d9322147<br />
0a2cfc64d521f32d 33229922a7e3ab28<br />
e4104db6b814c7ff 7fd4a0f2bf4315ab<br />
7e2721fae21faabd 0e56238f9ef33661<br />
3b2c70e482239cc5 e1b87a739bb9eefd<br />
090f82c3be9c96ee 3c81c76dbbe6e6d8<br />
be135d82ded68f85 76ab61a2167d31dd<br />
050bb345ee048a34 2034b215550dfde2<br />
5ed0954df87ff489 30ecf92dc35f2318<br />
5c215566aeb3d9fc ce327f403471785f<br />
<br />
MAC:<br />
1d3572fe0b4bdf66<br />
f2b2657cb2ee56fc<br />
80f7a82708cafd82<br />
1952e1f01761cb29<br />
<br />
response:<br />
dcfcaabfd9b65212 cb32c25520403073<br />
9a420ac89c3d9370 cda55abe437d16f4<br />
c47cf26738dcb1a1 2e491b8f7d522635<br />
a4ce03b624dde3b0 f323c5e4efe95e97<br />
b0a5ecd56e9c0e62 03b7b321b9653c4a<br />
d055ff8badf34a46 8761a90194175dea<br />
cdba973c8c46badd 3053cdccf7793390<br />
c269d98a1cdf17bf dc0d0ee79bc7ca8b<br />
8dba1a13f071914a 48aa9603d9322147<br />
0a2cfc64d521f32d 33229922a7e3ab28<br />
e4104db6b814c7ff 7fd4a0f2bf4315ab<br />
7e2721fae21faabd 0e56238f9ef33661<br />
3b2c70e482239cc5 e1b87a739bb9eefd<br />
090f82c3be9c96ee 3c81c76dbbe6e6d8<br />
be135d82ded68f85 76ab61a2167d31dd<br />
050bb345ee048a34 2034b215550dfde2<br />
5ed0954df87ff489 30ecf92dc35f2318<br />
5c215566aeb3d9fc ce327f403471785f<br />
1d3572fe0b4bdf66 f2b2657cb2ee56fc<br />
80f7a82708cafd82 1952e1f01761cb29<br />
<br />
== /account/destroy ==<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
tokenID (authToken):<br />
9a39818e3bbe6132<br />
38c9d7ff013a1841<br />
1ed2c66c3565c3c4<br />
de03feefecb7d212<br />
<br />
reqHMACkey:<br />
4a17cbdd54ee17db<br />
426fcd7baddff587<br />
231d7eadb408c091<br />
ce19ca915b715985<br />
<br />
= Keyserver Protocol Summary =<br />
<br />
* POST /account/create (email,srpV,srpSalt) -> ok (server sends verification email)<br />
** creates a user account<br />
* GET /account/devices [sessionToken] () -> list of devices<br />
* GET /account/keys [keyFetchToken,needs-verf] () -> kA/wrap(kB)<br />
** single-use, only if email is verified, encrypted results<br />
* POST /account/reset [authed+encrypted by accountResetToken] (wrap(kB),srpV,srpSalt) -> ok<br />
** single-use, does not require email to be verified, revoke all tokens for account, send notification email to user<br />
* POST /account/delete [authToken] () -> ok, account deleted<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authToken] () -> keyFetchToken, sessionToken<br />
* POST /session/destroy [sessionToken] () -> ok<br />
** for detaching a device, destroy all tokens<br />
* POST /recovery_email/status [sessionToken] () -> "verified" status of email<br />
** use "Accept: text/event-stream" header for server-sent-events; server will send "update" event with the new content of the resource any time it changes.<br />
* POST /recovery_email/resend_code [sessionToken] () -> re-send verification email<br />
* POST /recovery_email/verify_code (code) -> set "verified" flag<br />
** this code will come from a clickable link and is an unauthenticated endpoint<br />
** this could maybe take the recovery method if that would be helpful<br />
** sets verified flag on recovery method<br />
* POST /certificate/sign [sessionToken,needs-verf] (pubkey) -> cert<br />
** only if recovery email is verified<br />
* POST /password/change/start [authToken,needs-verf] () -> accountResetToken, keyFetchToken<br />
* POST /password/forgot/send_code () -> forgotPasswordToken<br />
** sends code to recovery method (email for now, maybe SMS later)<br />
** this is a short code, not a clickable link<br />
* POST /password/forgot/resend_code (forgotPasswordToken) -> re-sends code<br />
* POST /password/forgot/verify_code (forgotPasswordToken, code) -> accountResetToken<br />
** sets verified flag on recovery method<br />
* POST /get_random_bytes<br />
<br />
<br />
== Typical Client Flows ==<br />
<br />
Create account<br />
<br />
* POST /account/create (email,srpV,srpSalt) -> ok (server sends verification email)<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authed with authToken]() -> keyFetchToken, sessionToken<br />
* GET /recovery_email/status [sessionToken] () -> "verified" status<br />
** (optional, only if user requests resend) POST /recovery_email/resend_code [sessionToken]() -> ok<br />
** POST /recovery_email/verify_code (code) -> ok<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
* POST /certificate/sign [sessionToken] (pubkey) -> cert<br />
<br />
<br />
Attach to new device<br />
<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authToken] () -> keyFetchToken, sessionToken<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
** (if unverified-error, do waitUntilEmailVerified, then try again)<br />
* POST /certificate/sign [sessionToken] (pubkey) -> cert<br />
<br />
<br />
Forgot password<br />
<br />
* POST /password/forgot/send_code (email) -> forgotPasswordToken<br />
* POST /password/forgot/verify_code (forgotPasswordToken, code) -> accountResetToken<br />
* POST /account/reset [authed+encrypted by accountResetToken] (0000,srpV,srpSalt) -> ok<br />
* GOTO "Attach to new device"<br />
<br />
<br />
Change Password<br />
<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /password/change/start [authToken] () -> accountResetToken, keyFetchToken<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
* POST /account/reset [authed+encrypted by accountResetToken] (wrap(kB),srpV,srpSalt) -> ok<br />
* GOTO "Attach to new device"</div>Warnerhttps://wiki.mozilla.org/index.php?title=Identity/AttachedServices/KeyServerProtocol&diff=761824Identity/AttachedServices/KeyServerProtocol2013-11-15T00:49:41Z<p>Warner: /* Test Vectors */</p>
<hr />
<div>= PiCL Key Server / IdP Protocol =<br />
<br />
NOTE: This specification is slowly converging on stability (31-Jul-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.<br />
<br />
The server is being developed in https://github.com/mozilla/picl-idp . This repo currently includes a demonstration client (node.js CLI).<br />
<br />
Note that all messages are delivered over an HTTPS connection. The client browser may also implement cert-pinning to improve on the certificate validation process. The protections described below are in addition to those provided by TLS.<br />
<br />
Remaining TODO items:<br />
<br />
* decide on client-side key-stretching parameters: http://keywrapping.appspot.com can help<br />
* finalize SRP questions (definition of M1, generation of a/b)<br />
* finalize proof-of-work/DoS-prevention details<br />
* decide how to rate-limit account-creation calls<br />
* confirm this is actually implementable inside Firefox (especially w.r.t. NSS and Android/Java crypto)<br />
<br />
= Creating The Account =<br />
<br />
The first act performed by a user is to create the account. They enter email+password into their browser, which then does the following steps:<br />
<br />
* decide upon stretching parameters (perhaps consulting the keyserver for recommendations, but imposing a minimum strength requirement). For now we used a fixed {firstPBKDF:20000, scrypt: {N:65536, r:8, p:1}, secondPBKDF:20000}.<br />
* randomly choose a 32-byte mainSalt (this should be unique, but is not secret)<br />
* choose the SRP group parameters (fixed: use the 2048-bit group described below)<br />
* perform key-stretching (as described below), derive stretchedPW and srpPW<br />
* randomly choose a 32-byte srpSalt (unique, but not secret)<br />
* create srpVerifier from srpPW and srpSalt (as described below)<br />
* deliver (email, stretchParams, mainSalt, srpParams, srpSalt) to the keyserver's "POST /account/create" API<br />
<br />
The server, when creating a new account, creates both kA and wrap(kB) as randomly-generated 256-bit (32-byte) strings. It stores these, along with all the remaining values, indexed by email, in the account table where they can be retrieved by getToken later.<br />
<br />
== Email Verification ==<br />
<br />
To prevent fixation attacks, we require new accounts to verify their configured recovery email address before letting them learn the generated keys or obtain a signed certificate. Nevertheless, we wish clients to forget the user's password while they wait for email verification to complete. To achieve this, clients can obtain a sessionToken before verification, but most APIs that require it will raise errors until verification is finished.<br />
<br />
The server will send email with a URL that contains a long random "verification code" in the "fragment" hash. This URL points to a static page with some javascript that submits the code to the "POST /account/recovery_methods/verify_code" API. The URL can be clicked by any browser (it is not bound to anything), and when the API is hit, the account is marked as verified.<br />
<br />
After the client submits /account/create, it performs the "/session/auth" login sequence below to obtain a sessionToken. It then polls the "GET /account/recovery_methods" (which requires a sessionToken but not account verification) until the user clicks the email link and the API reports verification is complete. Then the client uses "GET /account/keys" and "POST /certificate/sign", described below, to obtain kA, kB, and a signed certificate to talk to the storage server.<br />
<br />
= Login: Obtaining the authToken =<br />
<br />
To connect a browser to an existing account, we use the following login protocol to transform an email+password pair into a single-use authToken. We will use this in the next section to create a session, from which we can obtain encryption keys and signed certificates.<br />
<br />
This protocol starts by using key-stretching to transform the email+password into a "stretchedPW", then feeds this into an SRP protocol to get the authToken.<br />
<br />
[[File:PICL-IdPAuth-auth-start.png|IdP Auth Protocol]]<br />
<br />
This authToken can be used (once) to do one of the following:<br />
<br />
* /session/create: obtain a sessionToken (and keyFetchToken), which enables storage server access<br />
* /password/change: obtain an accountResetToken, to safely reset the account password<br />
* /account/delete: to delete the entire account<br />
<br />
The protocol is designed to enable parallelism between key-stretching and the initial network messages, to reduce the time it takes to connect a browser to the account. In total, the browser requires five messages in four roundtrips (1: /auth/start, 2: /auth/finish, 3: /session/create, 4: /account/keys and /certificate/sign in parallel) before it is ready to talk to the storage server.<br />
<br />
== /auth/start ==<br />
<br />
As soon as the user finishes typing in the email address, the client should send it in the "/auth/start" 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 loginSRPToken that is used to associate this request with the subsequent /auth/finish 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.<br />
<br />
To mitigate DoS abuse, /auth/start may also require a proof-of-work string, described below.<br />
<br />
== Client-Side Key Stretching ==<br />
<br />
"Key Stretching" is the practice of running a password through a computationally-expensive one-way function before using it for encryption or authentication. The goal is to make brute-force dictionary attacks more expensive, by raising the cost of testing each guess.<br />
<br />
To protect the user's class-B data against compromises of our keyserver, we perform this key stretching on the client, rather than on the server. We use the memory-hard "scrypt" function (pronounced "ess-crypt") for this purpose, as motivated by the attacker-cost studies in [[Identity/CryptoIdeas/01-PBKDF-scrypt]]. Slower (low-RAM) devices can still participate by outsourcing the scrypt step to an "scrypt helper". To provide at least minimal protection against a compromise of this helper, we sandwich the scrypt step between two PBKDF2 steps. The complete protocol looks like this:<br />
<br />
[[File:PICL-IdPAuth-stretch-KDF.png|Stretching KDF]]<br />
<br />
Our initial parameter choices for this stretching are to use 20000 iterations of PBKDF2 on each side (40k in all), and to run scrypt with N/r/p set to 64k/8/1. This requires roughly 60MB of RAM for about 1.3 seconds on a 1.8GHz Intel Atom linux box (it will run faster, but use the same memory, on more modern CPUs). We are still studying performance on various platforms to decide what parameters to use in V1: lowering them speeds up the login process, but reduces security (by reducing the cost of an dictionary attack).<br />
<br />
Since the stretching is expected to take a second or two, the client can optimistically start this process (using default parameters) before receiving the auth/start 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 "mainSalt" is added *after* the stretching, to enable this parallelism (at a tiny cost in security).<br />
<br />
After "stretchedPW" is derived, a second HKDF call is used to derive "srpPW" and "unwrapBKey" which will be used later.<br />
<br />
[[File:PICL-IdPAuth-main-KDF.png|masterKey KDF]]<br />
<br />
== SRP Protocol Details ==<br />
<br />
The PiCL client uses the SRP protocol (http://srp.stanford.edu/) to prove that it knows the (stretched) account password without revealing the actual password (or information enabling a brute-force attack) to the server or any eavesdroppers. <br />
<br />
SRP is somewhat underspecified. We use SRP-6a, with SHA256 as the hash, and the 2048-bit modulus defined in RFC 5053 Appendix A. We consistently zero-pad all string values to 256 bytes (2048 bits), and use H(A+B+S) as the key-confirmation message "M1". These details, plus the SRP design papers and RFCs 2945 and 5054, should be enough to build a compatible implementation. The diagrams below and the test vectors at the end of this page can be used to verify compatibility.<br />
<br />
The server should use Jed's SRP module from https://github.com/jedp/node-srp . The client might use SJCL (http://crypto.stanford.edu/sjcl/) or native code (NSS).<br />
<br />
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).<br />
<br />
Note that SRP-6a uses a "k" value which basically encodes the group being used ("N" and "g"). Since all PICL accounts use the same 2048-bit group, they will all use the same "k" value (not to be confused with the per-session shared-secret "K" key that emerges from the protocol). This group's "k" integer is (as a base-10 number):<br />
2590038599070950300691544216303772122846747035652616593381637186118123578112<br />
<br />
=== SRP Verifier Calculation ===<br />
<br />
When the client first creates the account, it must combine the account email address, the stretched password (srpPW), and a randomly-generated srpSalt, to compute the srpVerifier. The server will use this verifier later, to check whether or not the client really knows the password.<br />
<br />
If the server is compromised and an attacker learns the srpVerifier for a given account, they cannot use this to directly log in (the verifier is not "password-equivalent"), but it does allow them to perform an offline brute-force attack against the user's password. In this respect, it is similar to a traditional hashed password. We make these attacks somewhat more expensive by performing the client-side stretching described above, instead of using the raw user password in the SRP calculation.<br />
<br />
[[File:PICL-IdPAuth-SRP-Verifier.png|client-side SRP Verifier calculation]]<br />
<br />
Test vectors are provided at the end of this page. The protocol requires that several values ("a", "b", "srpSalt") are chosen randomly, but for illustrative purposes, the test vectors use carefully-crafted non-random values. For the user's sake, please ensure implementations use proper random values instead of simply copying the test vectors.<br />
<br />
The client sends srpSalt and srpVerifier to the server when it creates the account. It will also re-compute the 'x' value (as an integer) during sign-in. The server will convert the srpVerifier string back into an integer ('v') for use during its own sign-in calculations.<br />
<br />
=== SRP Server-side Sign-In Flow ===<br />
<br />
When the user connects a new device to their account, they use the /auth/start API to start the SRP protocol. This sends the account email address to the server. The server looks up the stored srpVerifier for this account, creates a random 'b' integer, performs some math to compute the "B" number, then converts B into a string known as "srpB". "srpB" is returned to the client, along with srpSalt and the key-stretching parameters. "b" and "srpB" are retained for the subsequent /auth/finish call. '''Note that it is critical that the "b" integer remain secret on the server.''' The server also allocates a random loginSRPToken to connect the two calls.<br />
<br />
[[File:PICL-IdPAuth-SRP-Server.png|server-side SRP]]<br />
<br />
Later, /auth/start will be called with the client's srpA string and its M1 key-confirmation string. srpA is combined with srpB to calculate the "u" integer. srpA is also turned into an integer and used to compute the shared-secret "S" integer. S is then used to compute the shared key "srpK", which is the output of the SRP process.<br />
<br />
=== SRP Client Calculation ===<br />
<br />
While the client is waiting for the response to /auth/start, it begins its key-stretching calculations. Everything else must wait until the response to /auth/start arrives, which includes the key-stretching parameters (which are retroactively confirmed), srpSalt, and the server's generated srpB value.<br />
<br />
[[File:PICL-IdPAuth-SRP-Client.png|client-side SRP]]<br />
<br />
Once the client knows srpSalt, it computes the same "x" integer as it did in the middle of the srpVerifier calculation. It also converts srpB into an integer named "B". Then it creates a random "a" integer, uses it to compute the string "srpA", then combines srpA with the server's srpB to compute the "u" integer. It then combines the static "k", the password-derived "x", the combined "u", and the server's "B", together with some magic math, to derive the "S" integer. If everything went well, the client will compute the same "S" value as the server did. If not (the password was wrong, or the client is talking to a fake server that doesn't really know srpVerifier), then the two "S" values will not match.<br />
<br />
('''Again, it is critical that the client keep its "a" and "x" integers secret, both during and after the protocol run.''')<br />
<br />
To safely tell if the "S" values match, both client and server combine srpA, srpB, and their (independently) generated "S" strings to form a string named "M1". The client sends M1 (along with srpA) in the /session/auth/finish message. The server compares the client's copy of M1 against its own. If they match, the client knew the password and the server can safely respond with the encrypted account data. If they do not match, the client (or a man-in-the-middle attacker) did not know the password, and the client should increment a counter that can trigger defenses against online guessing attacks. The server must then return an error to the client, and '''not''' use or reveal srpK (or the correct M1) in any way.<br />
<br />
Both client and server also hash "S" into "srpK". This is the shared session key, from which specific message encryption and MAC keys are derived (as described below).<br />
<br />
=== SRP Notes ===<br />
<br />
The SRP "g" (generator) and "N" (prime modulus) should use the 2048-bit value from RFC 5054 Appendix A. 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.<br />
<br />
There are several places in SRP where integers (usually in the range 1..N-1) are converted into bytestrings, either for transmission over a wire, or to be passed into a hash function. The SRP spec is somewhat ambiguous about padding here: if the integer happens to be less than 2^2040, the simplest toString() approach will yield a '''255''' byte string, not a 256 byte string. PiCL consistently uses padding, so compatible implementations must prepend one or more NUL bytes to these short strings before transmission or hashing. The test vectors below were brute-forced to ensure that "srpVerifier", "srpA", "srpB", and "S" all wind up with leading zeros, to exercise the padding code in compatible implementations. If you are having problems getting your code to match these results, add some assertions to test that the stringified integers being put into hashes are exactly 256 bytes long.<br />
<br />
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 (/auth/finish).<br />
<br />
The server receives "A" in /auth/finish, 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 keyFetchToken+sessionToken as described below, returning the encrypted/MACed bundle in the response to /auth/finish.<br />
<br />
On the server, it is critical to reject an "A" value that is 0, or some other multiple of N. If the server does not check this, anybody can trivially sign in to any account without knowing the password. Likewise, it is critical for the client to reject a "B" value where B%N==0. If the client does not check this, the server (or an attacker pretending to be the server) will get a value that can be used in an offline brute-force search for the user's password.<br />
<br />
Outstanding crypto questions:<br />
<br />
* How exactly should the "a" and "b" integers be generated? The issue is of how much bias SRP can tolerate. Ideally these integers are uniformly distributed from 1 to N-1 (inclusive). The only way to obtain a purely uniform distribution from a source of random bytes is try-try-again: pick a (integral-number-of-bytes) number, compare it to the desired range, try again if it falls outside the range. If you get unlucky, this can take a lot of guesses, depending upon how close the range is to a power of two. ECDSA implementations tend to pick a number twice as long as the modulus and then modulo it down (rand(2^4096) % N), which yields a tiny fraction of a bit of bias. The cheaper approach is to do the same with a number of equal length (rand(2^2048) % N), which imposes more bias. Some SRP implementations appear to be satisfied by rand(2^256). We need more review here.<br />
* The original SRP papers defined M1=H(A+B+S) as we use here, but other implementation (in particular RFC2945) uses a curious construct that includes the username, the salt, and an odd XOR combination of N and g. We need to decide what to use for M1.<br />
<br />
== /auth/finish ==<br />
<br />
The client-side SRP calculation results in two values that are sent to the server in the /auth/finish 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.<br />
<br />
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.<br />
<br />
The server then allocates a single-use 32-byte random token named "authToken". It encrypts the token with the session key, and returns a success message with the encrypted bundle.<br />
<br />
All tokens have an associated tokenID, described below. The server needs to maintain a table that maps the tokenID to the token itself, so it can derive other values from the token later. The tokens are also associated with a specific account, so later API requests do not specify an email address or account ID.<br />
<br />
== Decrypting the /auth/finish Response ==<br />
<br />
The SRP session key ("srpK") is used to derive two other keys: respHMACkey and respXORkey.<br />
<br />
[[File:PICL-IdPAuth-encrypt-authToken.png|Decrypting the authToken]]<br />
<br />
The respXORkey is used to encrypt the authToken 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.<br />
<br />
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 returns the authToken value.<br />
<br />
= Creating a Session =<br />
<br />
For login, the single-use authToken is spent on a call to /session/create .<br />
This allocates two new (random 32-byte) tokens: a long-lived "sessionToken", and a single-use "keyFetchToken". The /session/create call returns an encrypted bundle containing the two tokens.<br />
<br />
[[File:PICL-IdPAuth-encrypt-sessionToken.png|Decrypting the sessionToken and keyFetchToken]]<br />
<br />
For calls which accept an authToken, the client uses authToken to derive three values:<br />
<br />
* tokenID<br />
* reqHMACkey<br />
* requestKey<br />
<br />
<br />
The client uses tokenID and reqHMACkey for a HAWK (https://github.com/hueniverse/hawk/) request to the "POST /session/create" API, using tokenID as "credentials.id" and reqHMACkey as "credentials.key". The server uses tokenID to look up the corresponding token, then derives reqHMACkey to validate the request.<br />
<br />
Each authToken-using call then derives additional API-specific values from requestKey. /session/create uses two derived values:<br />
<br />
* respHMACkey<br />
* respXORkey<br />
<br />
<br />
When the server receives a valid /session/create request, it allocates sessionToken and keyFetchToken, concatenates them, encrypts the pair by XORing it with the derived respXORkey, and attaches a MAC generated with respHMACkey. The encrypted MACed bundle is returned to the client.<br />
<br />
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 keyFetchToken and sessionToken values.<br />
<br />
Each authToken is single-use: once a successful request has been made with it, the authToken and its corresponding ID is removed from the server's memory, and subsequent attempts to use it will return a "no such token" error. The token is consumed even if the request fails (e.g. the MAC did not match).<br />
<br />
The authToken can be used (once) by multiple APIs. The server only needs to maintain one table mapping tokenID to authToken, because all these APIs share the same method of deriving a tokenID and reqHMACkey from the authToken.<br />
<br />
When a HAWK request appears, it should look up the tokenID in this table, retrieve the authToken, recompute reqHMACkey and requestKey, validate the request, delete the table entry, then hand the HTTP request and requestKey to the specific API handler. That handler should derive the API-specific values (respHMACkey, respXORkey, etc) to process the request or construct the response.<br />
<br />
The server can support multiple sessions per account (typically one per client device, plus perhaps others for account-management portals). There can also be multiple outstanding keyFetchTokens. The sessionToken lasts forever (until revoked by a password change or explicit revocation command), and can be used an unlimited number of times. The keyFetchToken expires after 60 seconds, and is single-use.<br />
<br />
= Obtaining keys kA and kB =<br />
<br />
Clients which have exchanged an authToken for either a sessionToken or an accountResetToken will also receive a keyFetchToken. This single-use token allows the client to retrieve kA and wrap(kB), which enables the client to encrypt and decrypt browser data (bookmarks, open-tabs, etc) correctly. As above, the keyFetchToken is used to derive tokenID, reqHMACkey, respHMACkey, and respXORkey, which are used in a HAWK request to the "GET /account/keys" API.<br />
<br />
The server pulls kA and wrap(kB) from the account table, concatenates them, encrypts the pair by XORing it with the derived respXORkey, and attaches a MAC generated with respHMACkey.<br />
<br />
[[File:PICL-IdPAuth-keys-server.png|keyFetchToken: server encrypts keys]]<br />
<br />
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 and wrap(kB) values.<br />
<br />
[[File:PICL-IdPAuth-keys-client.png|keyFetchToken: client decrypts keys]]<br />
<br />
Finally, the server-provided wrap(kB) value is simply XORed with the password-derived unwrapBKey (both are 32-byte strings) to obtain kB. There is no MAC on wrap(kB).<br />
<br />
[[File:PICL-IdPAuth-key-unwrap.png|unwrapping kB]]<br />
<br />
"kA" and "kB" enable the browser to encrypt/decrypt synchronized data records. They will be used to derive separate encryption and HMAC keys for each data collection (bookmarks, form-fill data, saved-password, open-tabs, etc). This will allow the user to share some data, but not everything, with a third party. The client may intentionally forget kA and kB (only retaining the derived keys) to reduce the power available to someone who steals their device.<br />
<br />
Note that /account/keys will not succeed until the account's email address has been verified. Also note that each keyFetchToken is single-use and short-lived. The token is consumed even if the request fails (e.g. the MAC does not match).<br />
<br />
= Signing Certificates =<br />
<br />
The sessionToken is used to derive two values:<br />
<br />
* tokenID<br />
* request HMAC key<br />
<br />
<br />
[[File:PICL-IdPAuth-use-session.png|Using the sessionToken, signing certificates]]<br />
<br />
The requestHMACkey is used in a HAWK request to provide integrity over many APIs, including /certificate/sign. requestHMACkey 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.<br />
<br />
For /certificate/sign, it is critical to enable payload verification by setting options.payload=true (on both client and server). Otherwise a man-in-the-middle could submit their own public key, get it signed, and then delete the user's data on the storage servers.<br />
<br />
The following keyserver APIs require a HAWK-protected request that uses the sessionToken. In addition, some require that the account be in the "verified" state:<br />
<br />
* GET /account/devices<br />
* POST /session/destroy<br />
* GET /recovery_email/status<br />
* POST /recovery_email/resend_code<br />
* POST /certificate/sign (requires "verified" account)<br />
<br />
= Resetting The Account =<br />
<br />
The account may be reset in two circumstances: when the user changes their password, and when the user forgets their password. In both cases, the client first obtains an "accountResetToken". This token is then used to change the SRP Verifier and either reset or replace the wrap(kB) value.<br />
<br />
== Changing the Password ==<br />
<br />
When the user wishes to change their password (i.e. they still know the old password), they first use the /auth/start+/auth/finish SRP protocol safely obtain an "authToken". They they use the "/password/change/start" API to exchange the authToken for an accountResetToken and a keyFetchToken.<br />
<br />
[[File:PICL-IdPAuth-encrypt-passwordChange.png|Server encrypts passwordChange response]]<br />
<br />
The accountResetToken will be used below to set the new password safely. The keyFetchToken should be used first, to obtain kB, so the subsequent account reset can replace wrap(kB) with a new value. This allows the password-changing client to retain their class-B data.<br />
<br />
Requiring an authToken proves that the user has provided the correct account password recently. When the account is reset, all active sessions and tokens will be cancelled (disconnecting all devices from the account). The client should immediately establish a new session as described above.<br />
<br />
This API is only used when the user knows their old password: if they have forgotten the password, use the "/password/forgot" APIs below.<br />
<br />
== Handling a Forgotten Password ==<br />
<br />
When the user has forgotten their password, they can use one of their "recovery methods" to obtain an accountResetToken. For now, this means we send a random code to the email address associated with their account. The user must copy this code from the email into their client, whereupon the client will get an accountResetToken that can be delivered to the API below.<br />
<br />
Note that, since the forgotten-password client never learns kB, any class-B data will be lost. This is necessary to protect class-B data from attackers who can read the users's email but do not know the account password (including those who compromise the IdP and the keyserver itself). When using /account/reset below, they will set wrap(kB) to a string of all zeros, which means the server should generate a new random wrap(kB) (just as it does during account creation).<br />
<br />
The /password/forgot/send_code API is used to ask the server to send a recovery code. This takes a recovery method, which for now is just an email address. This API is unauthenticated (after all, the user who has forgotten their password knows nothing but their email address). The server marks the corresponding account as "pending recovery", allocates a random forgotPasswordToken for the account, creates a recovery code, and sends the code (with instructions) via email. The API returns forgotPasswordToken to the client.<br />
<br />
The user must copy the recovery code into the same browser where they started the process. The client then submits the code to /password/forgot/verify_code along with the forgotPasswordToken they received. If they match, the server allocates a accountResetToken and returns it to the client. If they do not match, the server increments a counter (which is used to decide if an online guessing attack is happening).<br />
<br />
forgotPasswordToken can be used three times before it is exhausted. If the user guesses incorrectly this often, the client must call send_code again to get a new token and code. Each account has at most one token+code active at a time.<br />
<br />
The recovery code is initially a random 8-digit decimal number. If an attacker tries to sign in as someone else, hits the "forgot my password" button, then submits a guess to /password/forgot/verify_code, they will have a 1-in-100-million chance of success. If the server detects too many wrong guesses, it should increase the length of new codes. Another defensive technique is to require that users click an email link before being given the code: the server is told when the link is clicked, so the code will not be enabled until the email has been read. It remains to be seen whether this will be sufficient.<br />
<br />
The exact thresholds are TBD, but a nominal goal is to keep the chances of any attack succeeding to below 1-in-a-million per year. To achieve this, we can tolerate 100 verify_code failures in a single year before we must increase the length of the code.<br />
<br />
== Using accountResetToken ==<br />
<br />
An accountResetToken, obtained through either of the methods above, is used to invoke the /account/reset API. If the request is accepted, the server replaces the account password with a new one, updates the wrap(kB) value, cancels all active sessions and tokens (disconnecting all devices from the account), and sends a "your password has been changed" email to the user.<br />
<br />
If the client knew the old password, it can supply a new wrap(kB) value that will yield the same kB key as before, so no data will be lost. If the client did *not* know the old password, then it will supply a wrap(kB) of all-zeros, which tells the server to generate a new random wrap(kB) (just like during account creation), which means the class-B data will be lost.<br />
<br />
The client puts their new password through the same stretching procedure as described in the new-account section above, resulting in a new srpVerifier and unwrapBKey. If they knew the old kB, they XOR it with the new unwrapBKey to obtain a new wrap(kB).<br />
<br />
/account/reset needs request confidentiality, since the arguments include the newly wrapped kB value and the new SRP verifier, both of which enable a brute-force attack against the password. HAWK provides request integrity. The response is a single "ok" or "fail", conveyed by the HTTP headers, so we do not require response confidentiality, and can live without response integrity.<br />
<br />
So the single-use resetToken is used to derive three values:<br />
<br />
* tokenID<br />
* request HMAC key<br />
* request XOR key<br />
<br />
<br />
[[File:PICL-IdPAuth-encrypt-resetAccount.png|Client encrypts resetAccount request]]<br />
<br />
The request data will contain wrap(kB) and the new SRP verifier, concatenated together. Since we always pad the SRP verifier to the full (256-byte) group length, both pieces are fixed-length. We generate enough reqXORkey bytes to cover both values.<br />
<br />
The request data is XORed with requestXORkey, then delivered in the body of a HAWK request that uses tokenID as credentials.id and requestHMACkey as credentials.key . Note: it is critical to include the request body in the HAWK integrity check (options.payload=true, on both client and server), otherwise a man-in-the-middle could substitute their own SRP verifier, giving them control over the account (access to the user's class-A data, and a brute-force attack on their password).<br />
<br />
The client submits other values in the same request:<br />
<br />
* stretchParams<br />
* mainKDFSalt<br />
* srpSalt<br />
<br />
<br />
These values do not require confidentiality, so are not included in the encrypted bundle. They are still protected by the HAWK integrity check. Note that the server should assert that both salts are different than the previously stored values.<br />
<br />
After using /account/reset, clients should immediately perform the login protocol from above. If the old password was forgotten, this is necessary to fetch kA. In either case, a new sessionToken is required, since old sessions and tokens are revoked by /account/reset. Clients should retain the new srpPassword value during this process to avoid needing to run the lengthy key-stretching routine a second time.<br />
<br />
= Deleting The Account =<br />
<br />
When the user wishes to completely delete their account, the browser needs to perform two actions:<br />
<br />
* contact the storage servers and delete all records and collections<br />
* contact the keyserver and delete the account information<br />
<br />
The user should be prompted for their password as confirmation (i.e. a browser in the normal attached-and-synchronizing state should not be able to erase the account information: it must acquire a new authToken first).<br />
<br />
The device then obtains an authToken as described above, then spends it on a HAWK-protected request to the /account/destroy endpoint. This request contains no body and returns only a success code.<br />
<br />
[[File:PICL-IdPAuth-deleteAccount.png|Deleting the Account]]<br />
<br />
= Crypto Notes =<br />
<br />
Strong entropy is needed in the following places:<br />
<br />
* (client) creation of private "a" value inside SRP<br />
* (server) initial creation of kA and wrap(kB)<br />
* (server) creation of private "B" value inside SRP<br />
* (server) creation of signToken and resetToken<br />
<br />
<br />
On the server, code should get entropy from /dev/urandom via a function that uses it, like "crypto.randomBytes()" in node.js or "os.urandom()" in python. On the client, code should combine local entropy with some fetched from the keyserver via getEntropy(), to guard against failures in the local entropy pool. Something like HKDF(IKM=localEntropy+remoteEntropy, salt="", info=KW("mergeEntropy")).<br />
<br />
An HKDF-based stream cipher is used to protect the response for getToken2(), and the request for resetAccount(). HKDF is used to create a number of random bytes equal to the length of the message, then these are XORed with the plaintext to produce the ciphertext. An HMAC is then computed from the ciphertext, to protect the integrity of the message.<br />
<br />
HKDF, like any KDF, is defined to produce output that is indistinguishable from random data ("The HKDF Scheme", http://eprint.iacr.org/2010/264.pdf , by Hugo Krawczyk, section 3). XORing a plaintext with a random keystream to produce ciphertext is a simple and secure approach to data encryption, epitomized by AES-CTR or a stream cipher (http://cr.yp.to/snuffle/design.pdf). HKDF is not the fastest way to generate such a keystream, but it is safe, easy to specify, and easy to implement (just HMAC and XOR).<br />
<br />
Each keystream must be unique. SRP is defined to produce a random session key for each run (as long as at least one of the sides provides a random ephemeral key). We define resetToken to be a single-use randomly-generated value. Hence our two HKDF-XOR keystreams will be unique.<br />
<br />
A slightly more-traditional alternative would be to use AES-CTR (with the same HMAC-SHA256 used here), with a randomly-generated IV. This is equally secure, but requires implementors to obtain an AES library (with CTR mode, which does not seem to be universal). An even more traditional technique would be AES-CBC, which introduces the need for padding and a way to specify the length of the plaintext. The additional specification complexity, plus the library load, leads me to prefer HKDF+XOR.<br />
<br />
kB is equal to the XOR of wrapKey (which is a deterministic function of the user's email address, password, mainSalt, and the stretching parameters) and the server's randomly-generated wrap(kB) value, making kB a random value too. Using XOR as a wrapping function allows us to avoid sending kB or wrap(kB) in the initial createAccount arguments, which are not protected by SRP, and thus would enable an eavesdropper to mount a dictionary attack on the password (using wrap(kB) as their oracle).<br />
<br />
Likewise, allowing the server to generate kA avoids exposure during createAccount. The only point of vulnerability is a forgotten-password resetAccount() message, if an eavesdropper can learn the resetToken. In this case, the attacker might either use the resetToken themselves (then use signToken to learn kA and wrap(kB)), or passively decrypt the resetAccount() arguments to retrieve just wrap(kB). Given wrap(kB) and some encrypted browser data, the attacker can guess passwords (and derive kB, etc) until the data decrypts properly.<br />
<br />
To make this technique safe, any time kB or the password is changed, the mainSalt should be changed too. Otherwise knowledge of both wrap(old-kB) and old-kB would reveal wrapKey, making it easy to deduce the new kB. Changing mainSalt causes wrapKey to change too, preventing this.<br />
<br />
mainSalt is incorporated at the end of the stretching process to allow it to proceed in parallel with the getToken1() call that retrieves the salt. The inputs to the lengthy stretch come entirely from the user (email and password) or are optimistically (stretching parameters). This speedup seems more important than the minor security benefit of including the salt at the beginning of the stretch.<br />
<br />
There is no MAC on wrap(kB). If the keyserver chooses to deliver a bogus wrap(kB) or kA, the client will discover the problem a moment later when it talks to a storage server and attempts to retrieve data from an unrecognized collection-ID (since we intend to derive collection-IDs from the key used to encrypt their data, which will be derived from kA or kB as appropriate). It might be useful to add a checksum to kA and wrap(kB) to detect accidental corruption (e.g. store and deliver kA+SHA256(kA)), but this doesn't protect against intentional changes. We omit this checksum for now, assuming that disks will be reliable enough to let us never experience such failures.<br />
<br />
We use scrypt without a per-user salt. This is safe because the "password" input to scrypt is already diversified by the user's email address. The intention is to allow the scrypt-helper to run on anonymous data, so that an attacker who compromises this helper cannot easily learn the email addresses of the partially-stretched K1 values that it is receiving, confounding their attack.<br />
<br />
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. <br />
For /certificate/sign, we do not need request confidentiality or response confidentiality, since the client's pubkey and the resulting certificate will both be exposed over a similar SSL connection to the storage server later. And it is sufficient to rely on the response integrity provided by SSL, since the client can verify the returned certificate for itself. For the other keyserver APIs protected by HAWK, these properties are either unnecessary, or are provided by additional mechanisms.<br />
<br />
= Proof-Of-Work =<br />
<br />
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(). The server can control how much work is required.<br />
<br />
The getToken1() call looks for a "X-PiCL-PoW:" HTTP header. Most of the time, clients don't supply this header. But if the server responds to the getToken1() call with an error that indicates PoW is required, clients must create a valid PoW string and include it as the value of an "X-PiCL-PoW:" header in their next call to getToken1().<br />
<br />
The server's error message includes two parameters. The first is a "prefix string": the client's PoW string is required to begin with this prefix. The second is a "threshold hash". SHA256(PoWString) is required to be lexicographically earlier than the thresholdHashString (i.e. the numerical value of its hash must be closer to zero than the threshold). The client is expected to concatenate the prefix with a counter, then repeatedly increment the counter and hash the result until they meet the threshold, then re-submit their getToken1() request with the combined prefix+counter string in the header. If the client has spent more than e.g. 10 seconds doing this, the client should probably help the user cancel the operation and try again.<br />
<br />
When a server is under a DoS attack (either via some manual configuration tool or sensed automatically), it should start requiring valid unique X-PiCL-PoW headers. The server should initially require very little work, by using a threshold hash with just a few leading zero bits. If this is insufficient to reduce the attack volume, the threshold should be lowered, requiring even more work (from both the attacker and legitimate clients).<br />
<br />
The server should create a prefix string that contains a parseable timestamp and a random nonce (e.g. "%d-%d-" % (int(time.time()), b32encode(os.urandom(8)))). The server should also decide on a cutoff time (perhaps ten minutes ago). Each server must then maintain a table of "old PoW strings" to prevent replay attacks (these do not need to be shared among all servers: an in-RAM cache is fine).<br />
<br />
When the server receives a proposed PoW string, it first splits off the leading timestamp, and if the timestamp is older than the cutoff time, it rejects the string (either by dropping the connection, or returning a new "PoW required" error if it's feeling nice). Then it hashes the whole string and compares it against the threshold, rejecting those which fail to meet the threshold. Finally, for strings that pass the hash threshold, it checks the "old strings" table, and rejects any that appear on that list.<br />
<br />
If the PoW string makes it past all these checks, the server should add the string to the "old strings" table, then accept the request (i.e. compute an srpB value and add a session-id table entry for the request).<br />
<br />
The old-strings table check should be optimized to reject present strings quickly (i.e. if we are under attack, we should expect to see lots of duplicates of the same string, and must minimize the work we do when this occurs).<br />
<br />
The server can remove values from the old-strings table that have timestamps older than the cutoff time. The server can also discard values at other times (to avoid consuming too much memory), without losing anything but protection against resource consumption.<br />
<br />
Other notes:<br />
<br />
The server-side code for this can be deferred until we care to have a response to a DoS attack. However the client-side code for this must be present from day one, otherwise we won't be able to turn on the defense later without fear of disabling legitimate old clients.<br />
<br />
The server should perform as little work as possible before rejecting a token. Every extra CPU cycle it spends in this path is increasing the DoS attack amplification factor.<br />
<br />
The nonce in the prefix string exists to make sure that two successive clients get different prefixes, and thus do not come up with the same counter value (and inadvertently create identical strings, looking like a replay attack). If this proved annoying or expensive, we could instead obligate clients to produce their own nonce.<br />
<br />
TBD: Is this worth it? Should the PoW string go into an HTTP header? (I want it to be cheap to extract, and not clutter logs). Should the error response be a distinctive HTTP error code so our monitoring tools can easily count them? We can also use this feature to slow down online guessing attacks (i.e. trigger it either when getToken1 is called too much or when getToken2 produces too many errors). Since getToken1() includes an email address, we could also requires PoWs for some addresses (e.g. those we know to be under attack) but not others.<br />
<br />
= Glossary =<br />
<br />
This defines some of the jargon we've developed for this protocol.<br />
<br />
* data classes: each type of browser data (bookmarks, passwords, history, etc) can be assigned, by the user, to either class-A or class-B<br />
* class-A: data assigned to this class can be recovered, even if the user forgets their password, by proving control over an email address and resetting the account. It can also be read by Mozilla (since it runs the keyserver and knows kA), or by the user's IdP (by resetting the account without the user's permission).<br />
* class-B: data in this class cannot be recovered if the password is forgotten. It cannot be read by the IdP. Mozilla (via the keyserver) cannot read this data, but can attempt a brute-force dictionary attack against the password.<br />
* kA: the master key for data stored as "class-A", a 32-byte binary string. Individual encryption keys for different datatypes are derived from kA.<br />
* kB: the master key for data stored as "class-B", a 32-byte binary string.<br />
* wrap(kB): an encrypted copy of kB. The keyserver stores wrap(kB) and never sees kB itself. The client (browser) uses a key derived from the user's password to decrypt wrap(kB), obtaining the real kB.<br />
* sessionToken: a long-lived per-device token which allows the device to obtained signed BrowserID certificates for the account's identity (GUID@picl-something.org). This token remains valid until the user revokes it (either by changing their password, or triggering some kind of "revoke a specific device" or "revoke all devices" function).<br />
<br />
= Test Vectors =<br />
<br />
The following example uses a non-ASCII email address of "andré@example.org" (with an accented "e", UTF8 encoding is 616e6472c3a9406578616d706c652e6f7267) and a non-ascii password of "pässwörd" (with accents on "a" and "o", UTF8 encoding is 70c3a4737377c3b67264).<br />
<br />
These test vectors were produced by the python code in https://github.com/warner/picl-spec-crypto (revision aa441c6). The diagrams above may lag behind the latest version of that code.<br />
<br />
== stretch-KDF ==<br />
<br />
email:<br />
616e6472c3a94065<br />
78616d706c652e6f<br />
7267<br />
<br />
password:<br />
70c3a4737377c3b6<br />
7264<br />
<br />
K1 (scrypt input):<br />
f84913e3d8e6d624<br />
689d0a3e9678ac8d<br />
cc79d2c2f3d96414<br />
88cd9d6ef6cd83dd<br />
<br />
K2 (scrypt output):<br />
5b82f146a6412692<br />
3e4167a0350bb181<br />
feba61f63cb17140<br />
12b19cb0be0119c5<br />
<br />
stretchedPW:<br />
c16d46c31bee242c<br />
b31f916e9e38d60b<br />
76431d3f5304549c<br />
c75ae4bc20c7108c<br />
<br />
== main-KDF ==<br />
<br />
mainSalt (normally random):<br />
00f0000000000000<br />
0000000000000000<br />
0000000000000000<br />
000000000000034d<br />
<br />
srpPW:<br />
00f9b71800ab5337<br />
d51177d8fbc682a3<br />
653fa6dae5b87628<br />
eeec43a18af59a9d<br />
<br />
unwrapBKey:<br />
6ea660be9c89ec35<br />
5397f89afb282ea0<br />
bf21095760c8c500<br />
9bbcc894155bbe2a<br />
<br />
internal x (base 10):<br />
8192518690918<br />
99580124814080709381476194749939<br />
03899664126296984459627523279550<br />
<br />
internal x (hex):<br />
b5200337cc3f3f92<br />
6cdddae0b2d31029<br />
c069936a844aff58<br />
779a545be89d0abe<br />
<br />
v (verifier as number) (base 10):<br />
114649<br />
57230405843056840989945621595830<br />
71784395917725741221739574165799<br />
54316134303691657140298181419198<br />
87853709633756255809680435884948<br />
69849281177012209169281795507853<br />
57610332070005048463659745521969<br />
83218225819721112680718485091921<br />
64608360806562626442477160609654<br />
43167308814558974899899506977051<br />
96721477608178869100211706638584<br />
53875100985456239693728258285562<br />
04889672594983678412848291529879<br />
88548996842770025110751388952323<br />
22170663943486107183421205517476<br />
84831590615660554713667726412525<br />
73641352721966728239512914666806<br />
49625530438034148797508015907639<br />
67594925530663571631035463732161<br />
30193328802116982288883318596822<br />
<br />
== SRP Verifier ==<br />
<br />
k (base 10):<br />
259003859907<br />
09503006915442163037721228467470<br />
35652616593381637186118123578112<br />
<br />
srpSalt (normally random):<br />
00f1000000000000<br />
0000000000000000<br />
0000000000000000<br />
0000000000000179<br />
<br />
srpVerifier:<br />
00173ffa0263e63c cfd6791b8ee2a40f<br />
048ec94cd95aa8a3 125726f9805e0c82<br />
83c658dc0b607fbb 25db68e68e93f265<br />
8483049c68af7e82 14c49fde2712a775<br />
b63e545160d64b00 189a86708c69657d<br />
a7a1678eda0cd79f 86b8560ebdb1ffc2<br />
21db360eab901d64 3a75bf1205070a57<br />
91230ae56466b8c3 c1eb656e19b794f1<br />
ea0d2a077b3a7553 50208ea0118fec8c<br />
4b2ec344a05c66ae 1449b32609ca7189<br />
451c259d65bd15b3 4d8729afdb5faff8<br />
af1f3437bbdc0c3d 0b069a8ab2a959c9<br />
0c5a43d42082c774 90f3afcc10ef5648<br />
625c0605cdaace6c 6fdc9e9a7e6635d6<br />
19f50af773452247 0502cab26a52a198<br />
f5b00a2798589165 07b0b4e9ef9524d6<br />
<br />
== SRP B ==<br />
<br />
private b (normally random) (base 10):<br />
1198277<br />
66042000957856349411550091527197<br />
08125226960544768993643095227800<br />
29105361555030352774505625606029<br />
71778328003253459733139844872578<br />
33596964143417216389157582554932<br />
02841499373672188315534280693274<br />
23189198736863575142046053414928<br />
39548731879043135718309539706489<br />
29157321423483527294767988835942<br />
53343430842313006332606344714480<br />
99439808861069316482621424231409<br />
08830704769167700098392968117727<br />
43420990997238759832829219109897<br />
32876428831985487823417312772399<br />
92628295469389578458363237146486<br />
38545526799188280210660508721582<br />
00403102624831815596140094933216<br />
29832845626116777080504444704039<br />
04739431335617585333671378812943<br />
<br />
private b (hex):<br />
00f3000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 000000000000000f<br />
<br />
transmitted srpB:<br />
0022ce5a7b9d8127 7172caa20b0f1efb<br />
4643b3becc535664 73959b07b790d3c3<br />
f08650d5531c19ad 30ebb67bdb481d1d<br />
9cf61bf272f84398 48fdda58a4e6abc5<br />
abb2ac496da5098d 5cbf90e29b4b110e<br />
4e2c033c70af7392 5fa37457ee13ea3e<br />
8fde4ab516dff1c2 ae8e57a6b264fb9d<br />
b637eeeae9b5e43d faba9b329d3b8770<br />
ce89888709e02627 0e474eef822436e6<br />
397562f284778673 a1a7bc12b6883d1c<br />
21fbc27ffb3dbeb8 5efda279a69a1941<br />
4969113f10451603 065f0a0126666456<br />
51dde44a52f4d8de 113e2131321df1bf<br />
4369d2585364f9e5 36c39a4dce33221b<br />
e57d50ddccb4384e 3612bbfd03a268a3<br />
6e4f7e01de651401 e108cc247db50392<br />
<br />
== SRP A ==<br />
<br />
private a (normally random) (base 10):<br />
1193346<br />
47663227291363113405741243413916<br />
43482736314616601220006703889414<br />
28162541137108417166380088052095<br />
43910927476491099816542561560345<br />
50331133015255005622124012256352<br />
06121987030570656676375703406470<br />
63422988042473190059156975005813<br />
46381864669664357382020200036915<br />
26156674010218162984912976536206<br />
14440782978764393137821956464627<br />
16314542157937343986808167341567<br />
89864323268060014089757606109012<br />
50649711198896213496068605039486<br />
22864591676298304745954690086093<br />
75374681084741884719851454277570<br />
80362211874088739962880012800917<br />
05751238004976540634839106888223<br />
63866455314898189520502368799907<br />
19946264951520393624479315579863<br />
<br />
private a (hex):<br />
00f2000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 000000000000d3d7<br />
<br />
transmitted srpA:<br />
007da76cb7e77af5 ab61f334dbd5a958<br />
513afcdf0f47ab99 271fc5f7860fe213<br />
2e5802ca79d2e5c0 64bb80a38ee08771<br />
c98a937696698d87 8d78571568c98a1c<br />
40cc6e7cb101988a 2f9ba3d65679027d<br />
4d9068cb8aad6ebf f0101bab6d52b5fd<br />
fa81d2ed48bba119 d4ecdb7f3f478bd2<br />
36d5749f2275e948 4f2d0a9259d05e49<br />
d78a23dd26c60bfb a04fd346e5146469<br />
a8c3f010a627be81 c58ded1caaef2363<br />
635a45f97ca0d895 cc92ace1d09a99d6<br />
beb6b0dc0829535c 857a419e834db128<br />
64cd6ee8a843563b 0240520ff0195735<br />
cd9d316842d5d3f8 ef7209a0bb4b54ad<br />
7374d73e79be2c39 75632de562c59647<br />
0bb27bad79c3e2fc ddf194e1666cb9fc<br />
<br />
== SRP key-agreement ==<br />
<br />
u:<br />
b284aa1064e87751 50da6b5e2147b47c<br />
a7df505bed94a6f4 bb2ad873332ad732<br />
<br />
S:<br />
0092aaf0f527906a a5e8601f5d707907<br />
a03137e1b601e04b 5a1deb02a981f4be<br />
037b39829a27dba5 0f1b27545ff2e287<br />
29c2b79dcbdd32c9 d6b20d340affab91<br />
a626a8075806c26f e39df91d0ad979f9<br />
b2ee8aad1bc783e7 097407b63bfe58d9<br />
118b9b0b2a7c5c4c debaf8e9a460f4bf<br />
6247b0da34b760a5 9fac891757ddedca<br />
f08eed823b090586 c63009b2d740cc9f<br />
5397be89a2c32cdc fe6d6251ce11e44e<br />
6ecbdd9b6d93f30e 90896d2527564c7e<br />
b9ff70aa91acc0ba c1740a11cd184ffb<br />
989554ab58117c21 96b353d70c356160<br />
100ef5f4c28d19f6 e59ea2508e8e8aac<br />
6001497c27f362ed bafb25e0f045bfdf<br />
9fb02db9c908f103 40a639fe84c31b27<br />
<br />
M1:<br />
27949ec1e0f16256<br />
33436865edb037e2<br />
3eb6bf5cb91873f2<br />
a2729373c2039008<br />
<br />
srpK:<br />
e68fd0112bfa31dc<br />
ffc8e9c96a1cbadb<br />
4c3145978ff35c73<br />
e5bf8d30bbc7499a<br />
<br />
== /auth ==<br />
<br />
srpK:<br />
e68fd0112bfa31dc<br />
ffc8e9c96a1cbadb<br />
4c3145978ff35c73<br />
e5bf8d30bbc7499a<br />
<br />
respHMACkey:<br />
6584613597ef012f<br />
f1752b7869f01d03<br />
c72547a7b7199681<br />
531d9df1991edf23<br />
<br />
respXORkey:<br />
455835926ae37a1b<br />
627bd16affbeeab6<br />
27ecc737121826ca<br />
4a2bac2c100bf417<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
plaintext:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
ciphertext:<br />
253957f10e861c7c<br />
0a12bb0193d384d9<br />
579db544666d50bd<br />
3252d6576c768a68<br />
<br />
MAC:<br />
a98c87f5769ab4cc<br />
ca3df863faeb217e<br />
b16ddc29d712b301<br />
12b446324ee806d6<br />
<br />
response:<br />
253957f10e861c7c<br />
0a12bb0193d384d9<br />
579db544666d50bd<br />
3252d6576c768a68<br />
a98c87f5769ab4cc<br />
ca3df863faeb217e<br />
b16ddc29d712b301<br />
12b446324ee806d6<br />
<br />
== authtoken ==<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
tokenID (authToken):<br />
9a39818e3bbe6132<br />
38c9d7ff013a1841<br />
1ed2c66c3565c3c4<br />
de03feefecb7d212<br />
<br />
reqHMACkey:<br />
4a17cbdd54ee17db<br />
426fcd7baddff587<br />
231d7eadb408c091<br />
ce19ca915b715985<br />
<br />
requestKey:<br />
9d93978e662bfc6e<br />
8cc203fa4628ef5a<br />
7bf1ddfd7ee54e97<br />
ec5c033257b4fca9<br />
<br />
== /session ==<br />
<br />
requestKey:<br />
9d93978e662bfc6e<br />
8cc203fa4628ef5a<br />
7bf1ddfd7ee54e97<br />
ec5c033257b4fca9<br />
<br />
respHMACkey:<br />
cd3f50403d060b21<br />
76d32f71ca105bd8<br />
7c9b6c4e10e3ebf9<br />
3f5077bec2db24fa<br />
<br />
respXORkey:<br />
8422c53143dea9c6<br />
044afbe95228f291<br />
74996b830b1794a3<br />
eff132da53174d43<br />
92eeb87ccf8ad7a8<br />
0c432894e066de6b<br />
0ff70658dfbf2f07<br />
b9c7704045edcd54<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
sessionToken:<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
plaintext:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
ciphertext:<br />
04a347b2c75b2f41<br />
8cc37162dea57c1e<br />
e408f9109f820234<br />
7768a841cf8ad3dc<br />
324f1adf6b2f710f<br />
a4ea823f4ccb70c4<br />
bf46b4eb6b0a99b0<br />
017ecafbf95073eb<br />
<br />
MAC:<br />
7973ddbb184b601a<br />
c4df09704028ebfc<br />
754dd50e7d8eebfa<br />
52ce3fd868c69852<br />
<br />
response:<br />
04a347b2c75b2f41<br />
8cc37162dea57c1e<br />
e408f9109f820234<br />
7768a841cf8ad3dc<br />
324f1adf6b2f710f<br />
a4ea823f4ccb70c4<br />
bf46b4eb6b0a99b0<br />
017ecafbf95073eb<br />
7973ddbb184b601a<br />
c4df09704028ebfc<br />
754dd50e7d8eebfa<br />
52ce3fd868c69852<br />
<br />
== /account/keys ==<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
tokenID (keyFetchToken):<br />
3d0a7c02a15a62a2<br />
882f76e39b6494b5<br />
00c022a8816e0486<br />
25a495718998ba60<br />
<br />
reqHMACkey:<br />
87b8937f61d38d0e<br />
29cd2d5600b3f4da<br />
0aa48ac41de36a0e<br />
fe84bb4a9872ceb7<br />
<br />
keyRequestKey:<br />
14f338a9e8c6324d<br />
9e102d4e6ee83b20<br />
9796d5c74bb734a4<br />
10e729e014a4a546<br />
<br />
respHMACkey:<br />
f824d2953aab9faf<br />
51a1cb65ba9e7f9e<br />
5bf91c8d8fd1ac1c<br />
8c2d31853a8a1210<br />
<br />
respXORkey:<br />
ce7d7aa77859b235<br />
9932970bbe2101f2<br />
e80d01faf9191bd5<br />
ee52181d2f0b7809<br />
8281ba8cff392543<br />
3a89f7c3095e0c89<br />
900a469d60790c83<br />
3281c4df1a11c763<br />
<br />
kA:<br />
2021222324252627<br />
28292a2b2c2d2e2f<br />
3031323334353637<br />
38393a3b3c3d3e3f<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
plaintext:<br />
2021222324252627<br />
28292a2b2c2d2e2f<br />
3031323334353637<br />
38393a3b3c3d3e3f<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
ciphertext:<br />
ee5c58845c7c9412<br />
b11bbd20920c2fdd<br />
d83c33c9cd2c2de2<br />
d66b222613364636<br />
c2c0f8cfbb7c6304<br />
72c0bd88451342c6<br />
c05b14ce342c5ad4<br />
6ad89e84464c993c<br />
<br />
MAC:<br />
3927d30230157d08<br />
17a077eef4b20d97<br />
6f7a97363faf3f06<br />
4c003ada7d01aa70<br />
<br />
response:<br />
ee5c58845c7c9412<br />
b11bbd20920c2fdd<br />
d83c33c9cd2c2de2<br />
d66b222613364636<br />
c2c0f8cfbb7c6304<br />
72c0bd88451342c6<br />
c05b14ce342c5ad4<br />
6ad89e84464c993c<br />
3927d30230157d08<br />
17a077eef4b20d97<br />
6f7a97363faf3f06<br />
4c003ada7d01aa70<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
unwrapBKey:<br />
6ea660be9c89ec35<br />
5397f89afb282ea0<br />
bf21095760c8c500<br />
9bbcc894155bbe2a<br />
<br />
kB:<br />
2ee722fdd8ccaa72<br />
1bdeb2d1b76560ef<br />
ef705b04349d9357<br />
c3e592cf4906e075<br />
<br />
== use session (certificate/sign, etc) ==<br />
<br />
sessionToken:<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
tokenID (sessionToken):<br />
c0a29dcf46174973<br />
da1378696e4c82ae<br />
10f723cf4f4d9f75<br />
e39f4ae3851595ab<br />
<br />
reqHMACkey:<br />
9d8f22998ee7f579<br />
8b887042466b72d5<br />
3e56ab0c094388bf<br />
65831f702d2febc0<br />
<br />
== /password/change ==<br />
<br />
requestKey:<br />
9d93978e662bfc6e<br />
8cc203fa4628ef5a<br />
7bf1ddfd7ee54e97<br />
ec5c033257b4fca9<br />
<br />
respHMACkey:<br />
60d01e3d1da53b10<br />
93124a30c26889d7<br />
b2e067e7a09fde14<br />
6f935e3c653614f9<br />
<br />
respXORkey:<br />
3de5bd5e80faf84a<br />
dfca5396148123ef<br />
8184cd4bc10a7c8a<br />
db1688495affee67<br />
e07f80d914c5105f<br />
c86d6af24c4be1b1<br />
ef6c9c661422ac43<br />
181b3d29624a0cc2<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
accountResetToken:<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
plaintext:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
ciphertext:<br />
bd643fdd047f7ecd<br />
5743d91d980cad60<br />
11155fd8559fea1d<br />
438f12d2c66270f8<br />
20be421ad000d698<br />
00a4a03980862f7e<br />
3fbd4eb5c0f77a94<br />
c0c2e7f2be97d21d<br />
<br />
MAC:<br />
804fc4bc30923cc0<br />
d6c07ffea954848e<br />
0076b94f7deee71f<br />
a34db5c106d91980<br />
<br />
response:<br />
bd643fdd047f7ecd<br />
5743d91d980cad60<br />
11155fd8559fea1d<br />
438f12d2c66270f8<br />
20be421ad000d698<br />
00a4a03980862f7e<br />
3fbd4eb5c0f77a94<br />
c0c2e7f2be97d21d<br />
804fc4bc30923cc0<br />
d6c07ffea954848e<br />
0076b94f7deee71f<br />
a34db5c106d91980<br />
<br />
== /account/reset ==<br />
<br />
accountResetToken:<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
tokenID (accountResetToken):<br />
46ec557e56e531a0<br />
58620e9344ca9c75<br />
afac0d0bcbdd6f8c<br />
3c2f36055d9540cf<br />
<br />
reqHMACkey (for HAWK):<br />
716ebc28f5122ef4<br />
8670a48209190a16<br />
05263c3188dfe452<br />
56265929d1c45e48<br />
<br />
requestKey:<br />
aa5906d2318c6e54<br />
ecebfa52f10df4c0<br />
36165c230cc78ee8<br />
59f546c66ea3c126<br />
<br />
reqHMACkey (for ciphertext):<br />
a0d894a6232f2e78<br />
66a51dda3f84e01e<br />
ae5adb812564f391<br />
6c0d3cb16bdb743c<br />
<br />
reqXORkey:<br />
9cbde8fc9df31455 837b881e6c0d7e3c<br />
ca13589bc868c527 95fc00e51f2048ab<br />
d56de37629cda0b0 3f580a9e6c433724<br />
b5df12a735ccf2a1 e232d4f5fef84f86<br />
a1b4fdc47f8d1f73 12a6a230a8742d5b<br />
c144ee9abce25b57 9670b81085064cfb<br />
dcab862d9d57abcc 2142dcdde6682281<br />
d378c89b0dce06ae cd1c1ff68ad6db9a<br />
9cab0b02e160805b 59bb8712c8233056<br />
1b3ded75c430e23c 22338833b6f2ba39<br />
f5015ca7a905d6ee 6ec5b1e3ae5204ba<br />
6f3630ebf30ebbac 1f47329e8fe22770<br />
2a3d61f593328dd4 f0a96b628aa8ffec<br />
181e93d2af8d87ff 2d90d67caaf7f7c9<br />
af024c93cfc79e94 67ba70b3076c20cc<br />
141aa254ff159b25 3125a304441cecf3<br />
4fc1845ce96ee598 21fde83cd24e3209<br />
4d304477bfa2c8ed df236e512560694e<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
newSRPv:<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
<br />
plaintext:<br />
4041424344454647 48494a4b4c4d4e4f<br />
5051525354555657 58595a5b5c5d5e5f<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
<br />
ciphertext:<br />
dcfcaabfd9b65212 cb32c25520403073<br />
9a420ac89c3d9370 cda55abe437d16f4<br />
c47cf26738dcb1a1 2e491b8f7d522635<br />
a4ce03b624dde3b0 f323c5e4efe95e97<br />
b0a5ecd56e9c0e62 03b7b321b9653c4a<br />
d055ff8badf34a46 8761a90194175dea<br />
cdba973c8c46badd 3053cdccf7793390<br />
c269d98a1cdf17bf dc0d0ee79bc7ca8b<br />
8dba1a13f071914a 48aa9603d9322147<br />
0a2cfc64d521f32d 33229922a7e3ab28<br />
e4104db6b814c7ff 7fd4a0f2bf4315ab<br />
7e2721fae21faabd 0e56238f9ef33661<br />
3b2c70e482239cc5 e1b87a739bb9eefd<br />
090f82c3be9c96ee 3c81c76dbbe6e6d8<br />
be135d82ded68f85 76ab61a2167d31dd<br />
050bb345ee048a34 2034b215550dfde2<br />
5ed0954df87ff489 30ecf92dc35f2318<br />
5c215566aeb3d9fc ce327f403471785f<br />
<br />
MAC:<br />
1d3572fe0b4bdf66<br />
f2b2657cb2ee56fc<br />
80f7a82708cafd82<br />
1952e1f01761cb29<br />
<br />
response:<br />
dcfcaabfd9b65212 cb32c25520403073<br />
9a420ac89c3d9370 cda55abe437d16f4<br />
c47cf26738dcb1a1 2e491b8f7d522635<br />
a4ce03b624dde3b0 f323c5e4efe95e97<br />
b0a5ecd56e9c0e62 03b7b321b9653c4a<br />
d055ff8badf34a46 8761a90194175dea<br />
cdba973c8c46badd 3053cdccf7793390<br />
c269d98a1cdf17bf dc0d0ee79bc7ca8b<br />
8dba1a13f071914a 48aa9603d9322147<br />
0a2cfc64d521f32d 33229922a7e3ab28<br />
e4104db6b814c7ff 7fd4a0f2bf4315ab<br />
7e2721fae21faabd 0e56238f9ef33661<br />
3b2c70e482239cc5 e1b87a739bb9eefd<br />
090f82c3be9c96ee 3c81c76dbbe6e6d8<br />
be135d82ded68f85 76ab61a2167d31dd<br />
050bb345ee048a34 2034b215550dfde2<br />
5ed0954df87ff489 30ecf92dc35f2318<br />
5c215566aeb3d9fc ce327f403471785f<br />
1d3572fe0b4bdf66 f2b2657cb2ee56fc<br />
80f7a82708cafd82 1952e1f01761cb29<br />
<br />
== /account/destroy ==<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
tokenID (authToken):<br />
9a39818e3bbe6132<br />
38c9d7ff013a1841<br />
1ed2c66c3565c3c4<br />
de03feefecb7d212<br />
<br />
reqHMACkey:<br />
4a17cbdd54ee17db<br />
426fcd7baddff587<br />
231d7eadb408c091<br />
ce19ca915b715985<br />
<br />
= Keyserver Protocol Summary =<br />
<br />
* POST /account/create (email,srpV,srpSalt) -> ok (server sends verification email)<br />
** creates a user account<br />
* GET /account/devices [sessionToken] () -> list of devices<br />
* GET /account/keys [keyFetchToken,needs-verf] () -> kA/wrap(kB)<br />
** single-use, only if email is verified, encrypted results<br />
* POST /account/reset [authed+encrypted by accountResetToken] (wrap(kB),srpV,srpSalt) -> ok<br />
** single-use, does not require email to be verified, revoke all tokens for account, send notification email to user<br />
* POST /account/delete [authToken] () -> ok, account deleted<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authToken] () -> keyFetchToken, sessionToken<br />
* POST /session/destroy [sessionToken] () -> ok<br />
** for detaching a device, destroy all tokens<br />
* POST /recovery_email/status [sessionToken] () -> "verified" status of email<br />
** use "Accept: text/event-stream" header for server-sent-events; server will send "update" event with the new content of the resource any time it changes.<br />
* POST /recovery_email/resend_code [sessionToken] () -> re-send verification email<br />
* POST /recovery_email/verify_code (code) -> set "verified" flag<br />
** this code will come from a clickable link and is an unauthenticated endpoint<br />
** this could maybe take the recovery method if that would be helpful<br />
** sets verified flag on recovery method<br />
* POST /certificate/sign [sessionToken,needs-verf] (pubkey) -> cert<br />
** only if recovery email is verified<br />
* POST /password/change/start [authToken,needs-verf] () -> accountResetToken, keyFetchToken<br />
* POST /password/forgot/send_code () -> forgotPasswordToken<br />
** sends code to recovery method (email for now, maybe SMS later)<br />
** this is a short code, not a clickable link<br />
* POST /password/forgot/resend_code (forgotPasswordToken) -> re-sends code<br />
* POST /password/forgot/verify_code (forgotPasswordToken, code) -> accountResetToken<br />
** sets verified flag on recovery method<br />
* POST /get_random_bytes<br />
<br />
<br />
== Typical Client Flows ==<br />
<br />
Create account<br />
<br />
* POST /account/create (email,srpV,srpSalt) -> ok (server sends verification email)<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authed with authToken]() -> keyFetchToken, sessionToken<br />
* GET /recovery_email/status [sessionToken] () -> "verified" status<br />
** (optional, only if user requests resend) POST /recovery_email/resend_code [sessionToken]() -> ok<br />
** POST /recovery_email/verify_code (code) -> ok<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
* POST /certificate/sign [sessionToken] (pubkey) -> cert<br />
<br />
<br />
Attach to new device<br />
<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authToken] () -> keyFetchToken, sessionToken<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
** (if unverified-error, do waitUntilEmailVerified, then try again)<br />
* POST /certificate/sign [sessionToken] (pubkey) -> cert<br />
<br />
<br />
Forgot password<br />
<br />
* POST /password/forgot/send_code (email) -> forgotPasswordToken<br />
* POST /password/forgot/verify_code (forgotPasswordToken, code) -> accountResetToken<br />
* POST /account/reset [authed+encrypted by accountResetToken] (0000,srpV,srpSalt) -> ok<br />
* GOTO "Attach to new device"<br />
<br />
<br />
Change Password<br />
<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /password/change/start [authToken] () -> accountResetToken, keyFetchToken<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
* POST /account/reset [authed+encrypted by accountResetToken] (wrap(kB),srpV,srpSalt) -> ok<br />
* GOTO "Attach to new device"</div>Warnerhttps://wiki.mozilla.org/index.php?title=File:PICL-IdPAuth-encrypt-resetAccount.png&diff=755081File:PICL-IdPAuth-encrypt-resetAccount.png2013-11-07T23:55:14Z<p>Warner: Warner uploaded a new version of &quot;File:PICL-IdPAuth-encrypt-resetAccount.png&quot;</p>
<hr />
<div></div>Warnerhttps://wiki.mozilla.org/index.php?title=Identity/WeeklyMeeting/2013-10-28&diff=739997Identity/WeeklyMeeting/2013-10-282013-10-28T20:14:12Z<p>Warner: </p>
<hr />
<div><br />
=AGENDA=<br />
* TEF work week update<br />
** Last week's effort produced an architecture and nearly complete implementation of Firefox Accounts on FirefoxOS, with the following features:<br />
*** Sign in on First-Time Use<br />
*** Sign in and account management in Settings<br />
*** An Inter-App Communication API for certified apps to interact with Firefox Accounts<br />
*** A DOM API for any app to request Firefox Account sign-in<br />
*** Moz activities interface and user-approval UI for third-party app sign-in request<br />
*** Cryptographic support for certificate and assertion bundling<br />
*** Native library for interacting with Firefox Accounts web service<br />
*** Joy all around (a new nsISupports interface)<br />
*** Latest architecture approach: https://id.etherpad.mozilla.org/fxa-on-fxos-architecture<br />
* Reminder: Lloyd is traveling this week attending Brendan's off-site<br />
* Why isn't our work represented here: https://wiki.mozilla.org/B2G/Roadmap<br />
** short answer: not sure<br />
*** Tauni has been working with Candice to change that<br />
* Firefox Accounts demos:<br />
** Android: https://vimeo.com/77667079<br />
** Desktop: https://vimeo.com/77717494<br />
** NEW wiki: https://wiki.mozilla.org/Identity/Firefox-Accounts<br />
<br />
General Reminders:<br />
* Have you signed up for note taking?<br />
** See: https://id.etherpad.mozilla.org/weekly-notetakers<br />
* Status report archive: https://wiki.mozilla.org/Identity/Department_Status<br />
= Meeting Notes =<br />
[warner is this week's notetaker]<br />
<br />
* lloyd is travelling, mark mail as URGENT if needed<br />
* check out the demo videos<br />
* madrid crew (FxOS) has been rocking: have a basically functioning system, working on building a screencast this week. Looking to share code on the gecko side (HAWK, crypto stuff). Still trying to figure out what the FxOS "identifier" should be: email? phone number? recycled identifiers are scary. Hadn't received clear directions on FxA-on-FxOS until a few weeks ago, deadline for 1.3 is december, so it's somewhat at-risk. hard to push updates on people who are paying for their bandwidth, carriers may or may not be willing to pay for free ones, how can we keep users safely updated? still searching. overall mood is amazing and energized. Some carriers interested in bootstrapping-on-top-of or replacing FxA with their own schemes, but currently most are happy with FxA, not so happy with email-as-identifier (many markets don't have email), most want username+password. Using phone number is problematic (both reassignment and multi-SIM phones), SMS isn't reliable.<br />
* https://bugzilla.mozilla.org/showdependencytree.cgi?id=920135&maxdepth=4&hide_resolved=1<br />
* when phone boots up for the first time ("FTU": First-Time Use): prompted to sign up-for/in-to FxA, or can do later from the setup app<br />
* "certified" (pre-installed) apps can use that FxA immediately. 3rd-party apps can request a signin. 1.3 apps are just Marketplace and WheresMyFox (lost-phone locator). Can remove some Persona cruft once Marketplace moves to FxA.<br />
* unanswered questions: how can you sign-out, what does sign-out actually mean (per-app? phone-wide?), how does sign-out look given the new Goldilocks API (which removes onLogout)?<br />
* new firefox accounts wiki: https://wiki.mozilla.org/Identity/Firefox-Accounts : add questions.<br />
* QA update: jrgm is out sick, checking email. karl is covering trains, peter covering picl.<br />
<br />
<br />
= Team Status =<br />
== Native (B2G) ==<br />
== Signin to the Web ==<br />
Goldilocks landed; pivoting to focus on "finishing" the first cut of the BrowserID spec.<br />
== Profile in the Cloud (PiCL) ==<br />
= Libraries =<br />
= UX =<br />
* All UX talks, mocks, etc for Identity has a home and has been recently updated with the latest flows for FxOS, Fx Android and Fx Desktop.<br />
** https://wiki.mozilla.org/Identity/UX<br />
= Docs & Engagement =<br />
* [Francois] JSConf.asia and APIdays.io talks: canceled<br />
= QA =<br />
* jbonacci:<br />
** Continuing my work on FxA for the 3 platforms<br />
** Starting to pull together UX/UI stuff for the Test Plan<br />
** Going to be adding information to several wiki pages to help the entire QA team get up to speed on FxA and Next Sync.<br />
<br />
= Blog Schedule =<br />
* We are working on launching a new blog series. Stay tuned for more information soon<br />
= PTO/OOO (kept in the Identity calendar for everyone's reference) =<br />
* Shane: 28 Oct - 1 Nov : PTO<br />
* Francois: 4 - 8 Nov: working from UK<br />
* Francois: 11 - 18 Nov: working from SF<br />
* Francois: 13 - 14 Nov: TRIBE<br />
* Francois: 19 - 22 Nov: OOO conference<br />
* Brian: 8 - 11 Nov : OOO conference<br />
* John G: 27 Nov : PTO<br />
* Katie: 23 Dec - 2 Jan: PTO<br />
* Katie: 27-28 March: PTO</div>Warnerhttps://wiki.mozilla.org/index.php?title=File:PICL-IdPAuth-use-session.png&diff=731538File:PICL-IdPAuth-use-session.png2013-10-18T00:21:56Z<p>Warner: Warner uploaded a new version of &quot;File:PICL-IdPAuth-use-session.png&quot;</p>
<hr />
<div></div>Warnerhttps://wiki.mozilla.org/index.php?title=Identity/AttachedServices/KeyServerProtocol&diff=709478Identity/AttachedServices/KeyServerProtocol2013-09-17T00:16:43Z<p>Warner: /* Test Vectors */ update to use keyRequestKey in account/keys</p>
<hr />
<div>= PiCL Key Server / IdP Protocol =<br />
<br />
NOTE: This specification is slowly converging on stability (31-Jul-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.<br />
<br />
The server is being developed in https://github.com/mozilla/picl-idp . This repo currently includes a demonstration client (node.js CLI).<br />
<br />
Note that all messages are delivered over an HTTPS connection. The client browser may also implement cert-pinning to improve on the certificate validation process. The protections described below are in addition to those provided by TLS.<br />
<br />
Remaining TODO items:<br />
<br />
* decide on client-side key-stretching parameters: http://keywrapping.appspot.com can help<br />
* finalize SRP questions (definition of M1, generation of a/b)<br />
* finalize proof-of-work/DoS-prevention details<br />
* decide how to rate-limit account-creation calls<br />
* confirm this is actually implementable inside Firefox (especially w.r.t. NSS and Android/Java crypto)<br />
<br />
= Creating The Account =<br />
<br />
The first act performed by a user is to create the account. They enter email+password into their browser, which then does the following steps:<br />
<br />
* decide upon stretching parameters (perhaps consulting the keyserver for recommendations, but imposing a minimum strength requirement). For now we used a fixed {firstPBKDF:20000, scrypt: {N:65536, r:8, p:1}, secondPBKDF:20000}.<br />
* randomly choose a 32-byte mainSalt (this should be unique, but is not secret)<br />
* choose the SRP group parameters (fixed: use the 2048-bit group described below)<br />
* perform key-stretching (as described below), derive stretchedPW and srpPW<br />
* randomly choose a 32-byte srpSalt (unique, but not secret)<br />
* create srpVerifier from srpPW and srpSalt (as described below)<br />
* deliver (email, stretchParams, mainSalt, srpParams, srpSalt) to the keyserver's "POST /account/create" API<br />
<br />
<br />
To limit abuse, the createAccount() should also require a fresh "createToken". This should be created by some other API, outside the scope of this document, that perhaps requires a CAPTCHA or something. createAccount() might also require a proof-of-work token, as described below.<br />
<br />
The server, when creating a new account, creates both kA and wrap(kB) as randomly-generated 256-bit (32-byte) strings. It stores these, along with all the remaining values, indexed by email, in the account table where they can be retrieved by getToken later.<br />
<br />
== Email Verification ==<br />
<br />
To prevent fixation attacks, we require new accounts to verify their configured recovery email address before letting them learn the generated keys or obtain a signed certificate. Nevertheless, we wish clients to forget the user's password while they wait for email verification to complete. To achieve this, clients can obtain a sessionToken before verification, but most APIs that require it will raise errors until verification is finished.<br />
<br />
The server will send email with a URL that contains a long random "verification code" in the "fragment" hash. This URL points to a static page with some javascript that submits the code to the "POST /account/recovery_methods/verify_code" API. The URL can be clicked by any browser (it is not bound to anything), and when the API is hit, the account is marked as verified.<br />
<br />
After the client submits /account/create, it performs the "/session/auth" login sequence below to obtain a sessionToken. It then polls the "GET /account/recovery_methods" (which requires a sessionToken but not account verification) until the user clicks the email link and the API reports verification is complete. Then the client uses "GET /account/keys" and "POST /certificate/sign", described below, to obtain kA, kB, and a signed certificate to talk to the storage server.<br />
<br />
= Login: Obtaining the authToken =<br />
<br />
To connect a browser to an existing account, we use the following login protocol to transform an email+password pair into a single-use authToken. We will use this in the next section to create a session, from which we can obtain encryption keys and signed certificates.<br />
<br />
This protocol starts by using key-stretching to transform the email+password into a "stretchedPW", then feeds this into an SRP protocol to get the authToken.<br />
<br />
[[File:PICL-IdPAuth-auth-start.png|IdP Auth Protocol]]<br />
<br />
This authToken can be used (once) to do one of the following:<br />
<br />
* /session/create: obtain a sessionToken (and keyFetchToken), which enables storage server access<br />
* /password/change: obtain an accountResetToken, to safely reset the account password<br />
* /account/delete: to delete the entire account<br />
<br />
The protocol is designed to enable parallelism between key-stretching and the initial network messages, to reduce the time it takes to connect a browser to the account. In total, the browser requires five messages in four roundtrips (1: /auth/start, 2: /auth/finish, 3: /session/create, 4: /account/keys and /certificate/sign in parallel) before it is ready to talk to the storage server.<br />
<br />
== /auth/start ==<br />
<br />
As soon as the user finishes typing in the email address, the client should send it in the "/auth/start" 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 loginSRPToken that is used to associate this request with the subsequent /auth/finish 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.<br />
<br />
To mitigate DoS abuse, /auth/start may also require a proof-of-work string, described below.<br />
<br />
== Client-Side Key Stretching ==<br />
<br />
"Key Stretching" is the practice of running a password through a computationally-expensive one-way function before using it for encryption or authentication. The goal is to make brute-force dictionary attacks more expensive, by raising the cost of testing each guess.<br />
<br />
To protect the user's class-B data against compromises of our keyserver, we perform this key stretching on the client, rather than on the server. We use the memory-hard "scrypt" function (pronounced "ess-crypt") for this purpose, as motivated by the attacker-cost studies in [[Identity/CryptoIdeas/01-PBKDF-scrypt]]. Slower (low-RAM) devices can still participate by outsourcing the scrypt step to an "scrypt helper". To provide at least minimal protection against a compromise of this helper, we sandwich the scrypt step between two PBKDF2 steps. The complete protocol looks like this:<br />
<br />
[[File:PICL-IdPAuth-stretch-KDF.png|Stretching KDF]]<br />
<br />
Our initial parameter choices for this stretching are to use 20000 iterations of PBKDF2 on each side (40k in all), and to run scrypt with N/r/p set to 64k/8/1. This requires roughly 60MB of RAM for about 1.3 seconds on a 1.8GHz Intel Atom linux box (it will run faster, but use the same memory, on more modern CPUs). We are still studying performance on various platforms to decide what parameters to use in V1: lowering them speeds up the login process, but reduces security (by reducing the cost of an dictionary attack).<br />
<br />
Since the stretching is expected to take a second or two, the client can optimistically start this process (using default parameters) before receiving the auth/start 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 "mainSalt" is added *after* the stretching, to enable this parallelism (at a tiny cost in security).<br />
<br />
After "stretchedPW" is derived, a second HKDF call is used to derive "srpPW" and "unwrapBKey" which will be used later.<br />
<br />
[[File:PICL-IdPAuth-main-KDF.png|masterKey KDF]]<br />
<br />
== SRP Protocol Details ==<br />
<br />
The PiCL client uses the SRP protocol (http://srp.stanford.edu/) to prove that it knows the (stretched) account password without revealing the actual password (or information enabling a brute-force attack) to the server or any eavesdroppers. <br />
<br />
SRP is somewhat underspecified. We use SRP-6a, with SHA256 as the hash, and the 2048-bit modulus defined in RFC 5053 Appendix A. We consistently zero-pad all string values to 256 bytes (2048 bits), and use H(A+B+S) as the key-confirmation message "M1". These details, plus the SRP design papers and RFCs 2945 and 5054, should be enough to build a compatible implementation. The diagrams below and the test vectors at the end of this page can be used to verify compatibility.<br />
<br />
The server should use Jed's SRP module from https://github.com/jedp/node-srp . The client might use SJCL (http://crypto.stanford.edu/sjcl/) or native code (NSS).<br />
<br />
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).<br />
<br />
Note that SRP-6a uses a "k" value which basically encodes the group being used ("N" and "g"). Since all PICL accounts use the same 2048-bit group, they will all use the same "k" value (not to be confused with the per-session shared-secret "K" key that emerges from the protocol). This group's "k" integer is (as a base-10 number):<br />
2590038599070950300691544216303772122846747035652616593381637186118123578112<br />
<br />
=== SRP Verifier Calculation ===<br />
<br />
When the client first creates the account, it must combine the account email address, the stretched password (srpPW), and a randomly-generated srpSalt, to compute the srpVerifier. The server will use this verifier later, to check whether or not the client really knows the password.<br />
<br />
If the server is compromised and an attacker learns the srpVerifier for a given account, they cannot use this to directly log in (the verifier is not "password-equivalent"), but it does allow them to perform an offline brute-force attack against the user's password. In this respect, it is similar to a traditional hashed password. We make these attacks somewhat more expensive by performing the client-side stretching described above, instead of using the raw user password in the SRP calculation.<br />
<br />
[[File:PICL-IdPAuth-SRP-Verifier.png|client-side SRP Verifier calculation]]<br />
<br />
Test vectors are provided at the end of this page. The protocol requires that several values ("a", "b", "srpSalt") are chosen randomly, but for illustrative purposes, the test vectors use carefully-crafted non-random values. For the user's sake, please ensure implementations use proper random values instead of simply copying the test vectors.<br />
<br />
The client sends srpSalt and srpVerifier to the server when it creates the account. It will also re-compute the 'x' value (as an integer) during sign-in. The server will convert the srpVerifier string back into an integer ('v') for use during its own sign-in calculations.<br />
<br />
=== SRP Server-side Sign-In Flow ===<br />
<br />
When the user connects a new device to their account, they use the /auth/start API to start the SRP protocol. This sends the account email address to the server. The server looks up the stored srpVerifier for this account, creates a random 'b' integer, performs some math to compute the "B" number, then converts B into a string known as "srpB". "srpB" is returned to the client, along with srpSalt and the key-stretching parameters. "b" and "srpB" are retained for the subsequent /auth/finish call. '''Note that it is critical that the "b" integer remain secret on the server.''' The server also allocates a random loginSRPToken to connect the two calls.<br />
<br />
[[File:PICL-IdPAuth-SRP-Server.png|server-side SRP]]<br />
<br />
Later, /auth/start will be called with the client's srpA string and its M1 key-confirmation string. srpA is combined with srpB to calculate the "u" integer. srpA is also turned into an integer and used to compute the shared-secret "S" integer. S is then used to compute the shared key "srpK", which is the output of the SRP process.<br />
<br />
=== SRP Client Calculation ===<br />
<br />
While the client is waiting for the response to /auth/start, it begins its key-stretching calculations. Everything else must wait until the response to /auth/start arrives, which includes the key-stretching parameters (which are retroactively confirmed), srpSalt, and the server's generated srpB value.<br />
<br />
[[File:PICL-IdPAuth-SRP-Client.png|client-side SRP]]<br />
<br />
Once the client knows srpSalt, it computes the same "x" integer as it did in the middle of the srpVerifier calculation. It also converts srpB into an integer named "B". Then it creates a random "a" integer, uses it to compute the string "srpA", then combines srpA with the server's srpB to compute the "u" integer. It then combines the static "k", the password-derived "x", the combined "u", and the server's "B", together with some magic math, to derive the "S" integer. If everything went well, the client will compute the same "S" value as the server did. If not (the password was wrong, or the client is talking to a fake server that doesn't really know srpVerifier), then the two "S" values will not match.<br />
<br />
('''Again, it is critical that the client keep its "a" and "x" integers secret, both during and after the protocol run.''')<br />
<br />
To safely tell if the "S" values match, both client and server combine srpA, srpB, and their (independently) generated "S" strings to form a string named "M1". The client sends M1 (along with srpA) in the /session/auth/finish message. The server compares the client's copy of M1 against its own. If they match, the client knew the password and the server can safely respond with the encrypted account data. If they do not match, the client (or a man-in-the-middle attacker) did not know the password, and the client should increment a counter that can trigger defenses against online guessing attacks. The server must then return an error to the client, and '''not''' use or reveal srpK (or the correct M1) in any way.<br />
<br />
Both client and server also hash "S" into "srpK". This is the shared session key, from which specific message encryption and MAC keys are derived (as described below).<br />
<br />
=== SRP Notes ===<br />
<br />
The SRP "g" (generator) and "N" (prime modulus) should use the 2048-bit value from RFC 5054 Appendix A. 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.<br />
<br />
There are several places in SRP where integers (usually in the range 1..N-1) are converted into bytestrings, either for transmission over a wire, or to be passed into a hash function. The SRP spec is somewhat ambiguous about padding here: if the integer happens to be less than 2^2040, the simplest toString() approach will yield a '''255''' byte string, not a 256 byte string. PiCL consistently uses padding, so compatible implementations must prepend one or more NUL bytes to these short strings before transmission or hashing. The test vectors below were brute-forced to ensure that "srpVerifier", "srpA", "srpB", and "S" all wind up with leading zeros, to exercise the padding code in compatible implementations. If you are having problems getting your code to match these results, add some assertions to test that the stringified integers being put into hashes are exactly 256 bytes long.<br />
<br />
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 (/auth/finish).<br />
<br />
The server receives "A" in /auth/finish, 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 keyFetchToken+sessionToken as described below, returning the encrypted/MACed bundle in the response to /auth/finish.<br />
<br />
On the server, it is critical to reject an "A" value that is 0, or some other multiple of N. If the server does not check this, anybody can trivially sign in to any account without knowing the password. Likewise, it is critical for the client to reject a "B" value where B%N==0. If the client does not check this, the server (or an attacker pretending to be the server) will get a value that can be used in an offline brute-force search for the user's password.<br />
<br />
Outstanding crypto questions:<br />
<br />
* How exactly should the "a" and "b" integers be generated? The issue is of how much bias SRP can tolerate. Ideally these integers are uniformly distributed from 1 to N-1 (inclusive). The only way to obtain a purely uniform distribution from a source of random bytes is try-try-again: pick a (integral-number-of-bytes) number, compare it to the desired range, try again if it falls outside the range. If you get unlucky, this can take a lot of guesses, depending upon how close the range is to a power of two. ECDSA implementations tend to pick a number twice as long as the modulus and then modulo it down (rand(2^4096) % N), which yields a tiny fraction of a bit of bias. The cheaper approach is to do the same with a number of equal length (rand(2^2048) % N), which imposes more bias. Some SRP implementations appear to be satisfied by rand(2^256). We need more review here.<br />
* The original SRP papers defined M1=H(A+B+S) as we use here, but other implementation (in particular RFC2945) uses a curious construct that includes the username, the salt, and an odd XOR combination of N and g. We need to decide what to use for M1.<br />
<br />
== /auth/finish ==<br />
<br />
The client-side SRP calculation results in two values that are sent to the server in the /auth/finish 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.<br />
<br />
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.<br />
<br />
The server then allocates a single-use 32-byte random token named "authToken". It encrypts the token with the session key, and returns a success message with the encrypted bundle.<br />
<br />
All tokens have an associated tokenID, described below. The server needs to maintain a table that maps the tokenID to the token itself, so it can derive other values from the token later. The tokens are also associated with a specific account, so later API requests do not specify an email address or account ID.<br />
<br />
== Decrypting the /auth/finish Response ==<br />
<br />
The SRP session key ("srpK") is used to derive two other keys: respHMACkey and respXORkey.<br />
<br />
[[File:PICL-IdPAuth-encrypt-authToken.png|Decrypting the authToken]]<br />
<br />
The respXORkey is used to encrypt the authToken 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.<br />
<br />
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 returns the authToken value.<br />
<br />
= Creating a Session =<br />
<br />
For login, the single-use authToken is spent on a call to /session/create .<br />
This allocates two new (random 32-byte) tokens: a long-lived "sessionToken", and a single-use "keyFetchToken". The /session/create call returns an encrypted bundle containing the two tokens.<br />
<br />
[[File:PICL-IdPAuth-encrypt-sessionToken.png|Decrypting the sessionToken and keyFetchToken]]<br />
<br />
For calls which accept an authToken, the client uses authToken to derive three values:<br />
<br />
* tokenID<br />
* reqHMACkey<br />
* requestKey<br />
<br />
<br />
The client uses tokenID and reqHMACkey for a HAWK (https://github.com/hueniverse/hawk/) request to the "POST /session/create" API, using tokenID as "credentials.id" and reqHMACkey as "credentials.key". The server uses tokenID to look up the corresponding token, then derives reqHMACkey to validate the request.<br />
<br />
Each authToken-using call then derives additional API-specific values from requestKey. /session/create uses two derived values:<br />
<br />
* respHMACkey<br />
* respXORkey<br />
<br />
<br />
When the server receives a valid /session/create request, it allocates sessionToken and keyFetchToken, concatenates them, encrypts the pair by XORing it with the derived respXORkey, and attaches a MAC generated with respHMACkey. The encrypted MACed bundle is returned to the client.<br />
<br />
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 keyFetchToken and sessionToken values.<br />
<br />
Each authToken is single-use: once a successful request has been made with it, the authToken and its corresponding ID is removed from the server's memory, and subsequent attempts to use it will return a "no such token" error. The token is consumed even if the request fails (e.g. the MAC did not match).<br />
<br />
The authToken can be used (once) by multiple APIs. The server only needs to maintain one table mapping tokenID to authToken, because all these APIs share the same method of deriving a tokenID and reqHMACkey from the authToken.<br />
<br />
When a HAWK request appears, it should look up the tokenID in this table, retrieve the authToken, recompute reqHMACkey and requestKey, validate the request, delete the table entry, then hand the HTTP request and requestKey to the specific API handler. That handler should derive the API-specific values (respHMACkey, respXORkey, etc) to process the request or construct the response.<br />
<br />
The server can support multiple sessions per account (typically one per client device, plus perhaps others for account-management portals). There can also be multiple outstanding keyFetchTokens. The sessionToken lasts forever (until revoked by a password change or explicit revocation command), and can be used an unlimited number of times. The keyFetchToken expires after 60 seconds, and is single-use.<br />
<br />
= Obtaining keys kA and kB =<br />
<br />
Clients which have exchanged an authToken for either a sessionToken or an accountResetToken will also receive a keyFetchToken. This single-use token allows the client to retrieve kA and wrap(kB), which enables the client to encrypt and decrypt browser data (bookmarks, open-tabs, etc) correctly. As above, the keyFetchToken is used to derive tokenID, reqHMACkey, respHMACkey, and respXORkey, which are used in a HAWK request to the "GET /account/keys" API.<br />
<br />
The server pulls kA and wrap(kB) from the account table, concatenates them, encrypts the pair by XORing it with the derived respXORkey, and attaches a MAC generated with respHMACkey.<br />
<br />
[[File:PICL-IdPAuth-keys-server.png|keyFetchToken: server encrypts keys]]<br />
<br />
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 and wrap(kB) values.<br />
<br />
[[File:PICL-IdPAuth-keys-client.png|keyFetchToken: client decrypts keys]]<br />
<br />
Finally, the server-provided wrap(kB) value is simply XORed with the password-derived unwrapBKey (both are 32-byte strings) to obtain kB. There is no MAC on wrap(kB).<br />
<br />
[[File:PICL-IdPAuth-key-unwrap.png|unwrapping kB]]<br />
<br />
"kA" and "kB" enable the browser to encrypt/decrypt synchronized data records. They will be used to derive separate encryption and HMAC keys for each data collection (bookmarks, form-fill data, saved-password, open-tabs, etc). This will allow the user to share some data, but not everything, with a third party. The client may intentionally forget kA and kB (only retaining the derived keys) to reduce the power available to someone who steals their device.<br />
<br />
Note that /account/keys will not succeed until the account's email address has been verified. Also note that each keyFetchToken is single-use and short-lived. The token is consumed even if the request fails (e.g. the MAC does not match).<br />
<br />
= Signing Certificates =<br />
<br />
The sessionToken is used to derive two values:<br />
<br />
* tokenID<br />
* request HMAC key<br />
<br />
<br />
[[File:PICL-IdPAuth-use-session.png|Using the sessionToken, signing certificates]]<br />
<br />
The requestHMACkey is used in a HAWK request to provide integrity over many APIs, including /certificate/sign. requestHMACkey 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.<br />
<br />
For /certificate/sign, it is critical to enable payload verification by setting options.payload=true (on both client and server). Otherwise a man-in-the-middle could submit their own public key, get it signed, and then delete the user's data on the storage servers.<br />
<br />
The following keyserver APIs require a HAWK-protected request that uses the sessionToken. In addition, some require that the account be in the "verified" state:<br />
<br />
* GET /account/devices<br />
* POST /session/destroy<br />
* GET /recovery_email/status<br />
* POST /recovery_email/resend_code<br />
* POST /certificate/sign (requires "verified" account)<br />
<br />
= Resetting The Account =<br />
<br />
The account may be reset in two circumstances: when the user changes their password, and when the user forgets their password. In both cases, the client first obtains an "accountResetToken". This token is then used to change the SRP Verifier and either reset or replace the wrap(kB) value.<br />
<br />
== Changing the Password ==<br />
<br />
When the user wishes to change their password (i.e. they still know the old password), they first use the /auth/start+/auth/finish SRP protocol safely obtain an "authToken". They they use the "/password/change/start" API to exchange the authToken for an accountResetToken and a keyFetchToken.<br />
<br />
[[File:PICL-IdPAuth-encrypt-passwordChange.png|Server encrypts passwordChange response]]<br />
<br />
The accountResetToken will be used below to set the new password safely. The keyFetchToken should be used first, to obtain kB, so the subsequent account reset can replace wrap(kB) with a new value. This allows the password-changing client to retain their class-B data.<br />
<br />
Requiring an authToken proves that the user has provided the correct account password recently. When the account is reset, all active sessions and tokens will be cancelled (disconnecting all devices from the account). The client should immediately establish a new session as described above.<br />
<br />
This API is only used when the user knows their old password: if they have forgotten the password, use the "/password/forgot" APIs below.<br />
<br />
== Handling a Forgotten Password ==<br />
<br />
When the user has forgotten their password, they can use one of their "recovery methods" to obtain an accountResetToken. For now, this means we send a random code to the email address associated with their account. The user must copy this code from the email into their client, whereupon the client will get an accountResetToken that can be delivered to the API below.<br />
<br />
Note that, since the forgotten-password client never learns kB, any class-B data will be lost. This is necessary to protect class-B data from attackers who can read the users's email but do not know the account password (including those who compromise the IdP and the keyserver itself). When using /account/reset below, they will set wrap(kB) to a string of all zeros, which means the server should generate a new random wrap(kB) (just as it does during account creation).<br />
<br />
The /password/forgot/send_code API is used to ask the server to send a recovery code. This takes a recovery method, which for now is just an email address. This API is unauthenticated (after all, the user who has forgotten their password knows nothing but their email address). The server marks the corresponding account as "pending recovery", allocates a random forgotPasswordToken for the account, creates a recovery code, and sends the code (with instructions) via email. The API returns forgotPasswordToken to the client.<br />
<br />
The user must copy the recovery code into the same browser where they started the process. The client then submits the code to /password/forgot/verify_code along with the forgotPasswordToken they received. If they match, the server allocates a accountResetToken and returns it to the client. If they do not match, the server increments a counter (which is used to decide if an online guessing attack is happening).<br />
<br />
forgotPasswordToken can be used three times before it is exhausted. If the user guesses incorrectly this often, the client must call send_code again to get a new token and code. Each account has at most one token+code active at a time.<br />
<br />
The recovery code is initially a random 8-digit decimal number. If an attacker tries to sign in as someone else, hits the "forgot my password" button, then submits a guess to /password/forgot/verify_code, they will have a 1-in-100-million chance of success. If the server detects too many wrong guesses, it should increase the length of new codes. Another defensive technique is to require that users click an email link before being given the code: the server is told when the link is clicked, so the code will not be enabled until the email has been read. It remains to be seen whether this will be sufficient.<br />
<br />
The exact thresholds are TBD, but a nominal goal is to keep the chances of any attack succeeding to below 1-in-a-million per year. To achieve this, we can tolerate 100 verify_code failures in a single year before we must increase the length of the code.<br />
<br />
== Using accountResetToken ==<br />
<br />
An accountResetToken, obtained through either of the methods above, is used to invoke the /account/reset API. If the request is accepted, the server replaces the account password with a new one, updates the wrap(kB) value, cancels all active sessions and tokens (disconnecting all devices from the account), and sends a "your password has been changed" email to the user.<br />
<br />
If the client knew the old password, it can supply a new wrap(kB) value that will yield the same kB key as before, so no data will be lost. If the client did *not* know the old password, then it will supply a wrap(kB) of all-zeros, which tells the server to generate a new random wrap(kB) (just like during account creation), which means the class-B data will be lost.<br />
<br />
The client puts their new password through the same stretching procedure as described in the new-account section above, resulting in a new srpVerifier and unwrapBKey. If they knew the old kB, they XOR it with the new unwrapBKey to obtain a new wrap(kB).<br />
<br />
/account/reset needs request confidentiality, since the arguments include the newly wrapped kB value and the new SRP verifier, both of which enable a brute-force attack against the password. HAWK provides request integrity. The response is a single "ok" or "fail", conveyed by the HTTP headers, so we do not require response confidentiality, and can live without response integrity.<br />
<br />
So the single-use resetToken is used to derive three values:<br />
<br />
* tokenID<br />
* request HMAC key<br />
* request XOR key<br />
<br />
<br />
[[File:PICL-IdPAuth-encrypt-resetAccount.png|Client encrypts resetAccount request]]<br />
<br />
The request data will contain wrap(kB) and the new SRP verifier, concatenated together. Since we always pad the SRP verifier to the full (256-byte) group length, both pieces are fixed-length. We generate enough reqXORkey bytes to cover both values.<br />
<br />
The request data is XORed with requestXORkey, then delivered in the body of a HAWK request that uses tokenID as credentials.id and requestHMACkey as credentials.key . Note: it is critical to include the request body in the HAWK integrity check (options.payload=true, on both client and server), otherwise a man-in-the-middle could substitute their own SRP verifier, giving them control over the account (access to the user's class-A data, and a brute-force attack on their password).<br />
<br />
The client submits other values in the same request:<br />
<br />
* stretchParams<br />
* mainKDFSalt<br />
* srpSalt<br />
<br />
<br />
These values do not require confidentiality, so are not included in the encrypted bundle. They are still protected by the HAWK integrity check. Note that the server should assert that both salts are different than the previously stored values.<br />
<br />
After using /account/reset, clients should immediately perform the login protocol from above. If the old password was forgotten, this is necessary to fetch kA. In either case, a new sessionToken is required, since old sessions and tokens are revoked by /account/reset. Clients should retain the new srpPassword value during this process to avoid needing to run the lengthy key-stretching routine a second time.<br />
<br />
= Deleting The Account =<br />
<br />
When the user wishes to completely delete their account, the browser needs to perform two actions:<br />
<br />
* contact the storage servers and delete all records and collections<br />
* contact the keyserver and delete the account information<br />
<br />
The user should be prompted for their password as confirmation (i.e. a browser in the normal attached-and-synchronizing state should not be able to erase the account information: it must acquire a new authToken first).<br />
<br />
The device then obtains an authToken as described above, then spends it on a HAWK-protected request to the /account/destroy endpoint. This request contains no body and returns only a success code.<br />
<br />
[[File:PICL-IdPAuth-deleteAccount.png|Deleting the Account]]<br />
<br />
= Crypto Notes =<br />
<br />
Strong entropy is needed in the following places:<br />
<br />
* (client) creation of private "a" value inside SRP<br />
* (server) initial creation of kA and wrap(kB)<br />
* (server) creation of private "B" value inside SRP<br />
* (server) creation of signToken and resetToken<br />
<br />
<br />
On the server, code should get entropy from /dev/urandom via a function that uses it, like "crypto.randomBytes()" in node.js or "os.urandom()" in python. On the client, code should combine local entropy with some fetched from the keyserver via getEntropy(), to guard against failures in the local entropy pool. Something like HKDF(IKM=localEntropy+remoteEntropy, salt="", info=KW("mergeEntropy")).<br />
<br />
An HKDF-based stream cipher is used to protect the response for getToken2(), and the request for resetAccount(). HKDF is used to create a number of random bytes equal to the length of the message, then these are XORed with the plaintext to produce the ciphertext. An HMAC is then computed from the ciphertext, to protect the integrity of the message.<br />
<br />
HKDF, like any KDF, is defined to produce output that is indistinguishable from random data ("The HKDF Scheme", http://eprint.iacr.org/2010/264.pdf , by Hugo Krawczyk, section 3). XORing a plaintext with a random keystream to produce ciphertext is a simple and secure approach to data encryption, epitomized by AES-CTR or a stream cipher (http://cr.yp.to/snuffle/design.pdf). HKDF is not the fastest way to generate such a keystream, but it is safe, easy to specify, and easy to implement (just HMAC and XOR).<br />
<br />
Each keystream must be unique. SRP is defined to produce a random session key for each run (as long as at least one of the sides provides a random ephemeral key). We define resetToken to be a single-use randomly-generated value. Hence our two HKDF-XOR keystreams will be unique.<br />
<br />
A slightly more-traditional alternative would be to use AES-CTR (with the same HMAC-SHA256 used here), with a randomly-generated IV. This is equally secure, but requires implementors to obtain an AES library (with CTR mode, which does not seem to be universal). An even more traditional technique would be AES-CBC, which introduces the need for padding and a way to specify the length of the plaintext. The additional specification complexity, plus the library load, leads me to prefer HKDF+XOR.<br />
<br />
kB is equal to the XOR of wrapKey (which is a deterministic function of the user's email address, password, mainSalt, and the stretching parameters) and the server's randomly-generated wrap(kB) value, making kB a random value too. Using XOR as a wrapping function allows us to avoid sending kB or wrap(kB) in the initial createAccount arguments, which are not protected by SRP, and thus would enable an eavesdropper to mount a dictionary attack on the password (using wrap(kB) as their oracle).<br />
<br />
Likewise, allowing the server to generate kA avoids exposure during createAccount. The only point of vulnerability is a forgotten-password resetAccount() message, if an eavesdropper can learn the resetToken. In this case, the attacker might either use the resetToken themselves (then use signToken to learn kA and wrap(kB)), or passively decrypt the resetAccount() arguments to retrieve just wrap(kB). Given wrap(kB) and some encrypted browser data, the attacker can guess passwords (and derive kB, etc) until the data decrypts properly.<br />
<br />
To make this technique safe, any time kB or the password is changed, the mainSalt should be changed too. Otherwise knowledge of both wrap(old-kB) and old-kB would reveal wrapKey, making it easy to deduce the new kB. Changing mainSalt causes wrapKey to change too, preventing this.<br />
<br />
mainSalt is incorporated at the end of the stretching process to allow it to proceed in parallel with the getToken1() call that retrieves the salt. The inputs to the lengthy stretch come entirely from the user (email and password) or are optimistically (stretching parameters). This speedup seems more important than the minor security benefit of including the salt at the beginning of the stretch.<br />
<br />
There is no MAC on wrap(kB). If the keyserver chooses to deliver a bogus wrap(kB) or kA, the client will discover the problem a moment later when it talks to a storage server and attempts to retrieve data from an unrecognized collection-ID (since we intend to derive collection-IDs from the key used to encrypt their data, which will be derived from kA or kB as appropriate). It might be useful to add a checksum to kA and wrap(kB) to detect accidental corruption (e.g. store and deliver kA+SHA256(kA)), but this doesn't protect against intentional changes. We omit this checksum for now, assuming that disks will be reliable enough to let us never experience such failures.<br />
<br />
We use scrypt without a per-user salt. This is safe because the "password" input to scrypt is already diversified by the user's email address. The intention is to allow the scrypt-helper to run on anonymous data, so that an attacker who compromises this helper cannot easily learn the email addresses of the partially-stretched K1 values that it is receiving, confounding their attack.<br />
<br />
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. <br />
For /certificate/sign, we do not need request confidentiality or response confidentiality, since the client's pubkey and the resulting certificate will both be exposed over a similar SSL connection to the storage server later. And it is sufficient to rely on the response integrity provided by SSL, since the client can verify the returned certificate for itself. For the other keyserver APIs protected by HAWK, these properties are either unnecessary, or are provided by additional mechanisms.<br />
<br />
= Proof-Of-Work =<br />
<br />
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(). The server can control how much work is required.<br />
<br />
The getToken1() call looks for a "X-PiCL-PoW:" HTTP header. Most of the time, clients don't supply this header. But if the server responds to the getToken1() call with an error that indicates PoW is required, clients must create a valid PoW string and include it as the value of an "X-PiCL-PoW:" header in their next call to getToken1().<br />
<br />
The server's error message includes two parameters. The first is a "prefix string": the client's PoW string is required to begin with this prefix. The second is a "threshold hash". SHA256(PoWString) is required to be lexicographically earlier than the thresholdHashString (i.e. the numerical value of its hash must be closer to zero than the threshold). The client is expected to concatenate the prefix with a counter, then repeatedly increment the counter and hash the result until they meet the threshold, then re-submit their getToken1() request with the combined prefix+counter string in the header. If the client has spent more than e.g. 10 seconds doing this, the client should probably help the user cancel the operation and try again.<br />
<br />
When a server is under a DoS attack (either via some manual configuration tool or sensed automatically), it should start requiring valid unique X-PiCL-PoW headers. The server should initially require very little work, by using a threshold hash with just a few leading zero bits. If this is insufficient to reduce the attack volume, the threshold should be lowered, requiring even more work (from both the attacker and legitimate clients).<br />
<br />
The server should create a prefix string that contains a parseable timestamp and a random nonce (e.g. "%d-%d-" % (int(time.time()), b32encode(os.urandom(8)))). The server should also decide on a cutoff time (perhaps ten minutes ago). Each server must then maintain a table of "old PoW strings" to prevent replay attacks (these do not need to be shared among all servers: an in-RAM cache is fine).<br />
<br />
When the server receives a proposed PoW string, it first splits off the leading timestamp, and if the timestamp is older than the cutoff time, it rejects the string (either by dropping the connection, or returning a new "PoW required" error if it's feeling nice). Then it hashes the whole string and compares it against the threshold, rejecting those which fail to meet the threshold. Finally, for strings that pass the hash threshold, it checks the "old strings" table, and rejects any that appear on that list.<br />
<br />
If the PoW string makes it past all these checks, the server should add the string to the "old strings" table, then accept the request (i.e. compute an srpB value and add a session-id table entry for the request).<br />
<br />
The old-strings table check should be optimized to reject present strings quickly (i.e. if we are under attack, we should expect to see lots of duplicates of the same string, and must minimize the work we do when this occurs).<br />
<br />
The server can remove values from the old-strings table that have timestamps older than the cutoff time. The server can also discard values at other times (to avoid consuming too much memory), without losing anything but protection against resource consumption.<br />
<br />
Other notes:<br />
<br />
The server-side code for this can be deferred until we care to have a response to a DoS attack. However the client-side code for this must be present from day one, otherwise we won't be able to turn on the defense later without fear of disabling legitimate old clients.<br />
<br />
The server should perform as little work as possible before rejecting a token. Every extra CPU cycle it spends in this path is increasing the DoS attack amplification factor.<br />
<br />
The nonce in the prefix string exists to make sure that two successive clients get different prefixes, and thus do not come up with the same counter value (and inadvertently create identical strings, looking like a replay attack). If this proved annoying or expensive, we could instead obligate clients to produce their own nonce.<br />
<br />
TBD: Is this worth it? Should the PoW string go into an HTTP header? (I want it to be cheap to extract, and not clutter logs). Should the error response be a distinctive HTTP error code so our monitoring tools can easily count them? We can also use this feature to slow down online guessing attacks (i.e. trigger it either when getToken1 is called too much or when getToken2 produces too many errors). Since getToken1() includes an email address, we could also requires PoWs for some addresses (e.g. those we know to be under attack) but not others.<br />
<br />
= Glossary =<br />
<br />
This defines some of the jargon we've developed for this protocol.<br />
<br />
* data classes: each type of browser data (bookmarks, passwords, history, etc) can be assigned, by the user, to either class-A or class-B<br />
* class-A: data assigned to this class can be recovered, even if the user forgets their password, by proving control over an email address and resetting the account. It can also be read by Mozilla (since it runs the keyserver and knows kA), or by the user's IdP (by resetting the account without the user's permission).<br />
* class-B: data in this class cannot be recovered if the password is forgotten. It cannot be read by the IdP. Mozilla (via the keyserver) cannot read this data, but can attempt a brute-force dictionary attack against the password.<br />
* kA: the master key for data stored as "class-A", a 32-byte binary string. Individual encryption keys for different datatypes are derived from kA.<br />
* kB: the master key for data stored as "class-B", a 32-byte binary string.<br />
* wrap(kB): an encrypted copy of kB. The keyserver stores wrap(kB) and never sees kB itself. The client (browser) uses a key derived from the user's password to decrypt wrap(kB), obtaining the real kB.<br />
* sessionToken: a long-lived per-device token which allows the device to obtained signed BrowserID certificates for the account's identity (GUID@picl-something.org). This token remains valid until the user revokes it (either by changing their password, or triggering some kind of "revoke a specific device" or "revoke all devices" function).<br />
<br />
= Test Vectors =<br />
<br />
The following example uses a non-ASCII email address of "andré@example.org" (with an accented "e", UTF8 encoding is 616e6472c3a9406578616d706c652e6f7267) and a non-ascii password of "pässwörd" (with accents on "a" and "o", UTF8 encoding is 70c3a4737377c3b67264).<br />
<br />
These test vectors were produced by the python code in https://github.com/warner/picl-spec-crypto . The diagrams may lag behind the latest version of that code.<br />
<br />
== stretch-KDF ==<br />
<br />
email:<br />
616e6472c3a94065<br />
78616d706c652e6f<br />
7267<br />
<br />
password:<br />
70c3a4737377c3b6<br />
7264<br />
<br />
K1 (scrypt input):<br />
f84913e3d8e6d624<br />
689d0a3e9678ac8d<br />
cc79d2c2f3d96414<br />
88cd9d6ef6cd83dd<br />
<br />
K2 (scrypt output):<br />
5b82f146a6412692<br />
3e4167a0350bb181<br />
feba61f63cb17140<br />
12b19cb0be0119c5<br />
<br />
stretchedPW:<br />
c16d46c31bee242c<br />
b31f916e9e38d60b<br />
76431d3f5304549c<br />
c75ae4bc20c7108c<br />
<br />
== main-KDF ==<br />
<br />
mainSalt (normally random):<br />
00f0000000000000<br />
0000000000000000<br />
0000000000000000<br />
000000000000034d<br />
<br />
srpPW:<br />
00f9b71800ab5337<br />
d51177d8fbc682a3<br />
653fa6dae5b87628<br />
eeec43a18af59a9d<br />
<br />
unwrapBKey:<br />
6ea660be9c89ec35<br />
5397f89afb282ea0<br />
bf21095760c8c500<br />
9bbcc894155bbe2a<br />
<br />
internal x (base 10):<br />
8192518690918<br />
99580124814080709381476194749939<br />
03899664126296984459627523279550<br />
<br />
internal x (hex):<br />
b5200337cc3f3f92<br />
6cdddae0b2d31029<br />
c069936a844aff58<br />
779a545be89d0abe<br />
<br />
v (verifier as number) (base 10):<br />
114649<br />
57230405843056840989945621595830<br />
71784395917725741221739574165799<br />
54316134303691657140298181419198<br />
87853709633756255809680435884948<br />
69849281177012209169281795507853<br />
57610332070005048463659745521969<br />
83218225819721112680718485091921<br />
64608360806562626442477160609654<br />
43167308814558974899899506977051<br />
96721477608178869100211706638584<br />
53875100985456239693728258285562<br />
04889672594983678412848291529879<br />
88548996842770025110751388952323<br />
22170663943486107183421205517476<br />
84831590615660554713667726412525<br />
73641352721966728239512914666806<br />
49625530438034148797508015907639<br />
67594925530663571631035463732161<br />
30193328802116982288883318596822<br />
<br />
== SRP Verifier ==<br />
<br />
k (base 10):<br />
259003859907<br />
09503006915442163037721228467470<br />
35652616593381637186118123578112<br />
<br />
srpSalt (normally random):<br />
00f1000000000000<br />
0000000000000000<br />
0000000000000000<br />
0000000000000179<br />
<br />
srpVerifier:<br />
00173ffa0263e63c cfd6791b8ee2a40f<br />
048ec94cd95aa8a3 125726f9805e0c82<br />
83c658dc0b607fbb 25db68e68e93f265<br />
8483049c68af7e82 14c49fde2712a775<br />
b63e545160d64b00 189a86708c69657d<br />
a7a1678eda0cd79f 86b8560ebdb1ffc2<br />
21db360eab901d64 3a75bf1205070a57<br />
91230ae56466b8c3 c1eb656e19b794f1<br />
ea0d2a077b3a7553 50208ea0118fec8c<br />
4b2ec344a05c66ae 1449b32609ca7189<br />
451c259d65bd15b3 4d8729afdb5faff8<br />
af1f3437bbdc0c3d 0b069a8ab2a959c9<br />
0c5a43d42082c774 90f3afcc10ef5648<br />
625c0605cdaace6c 6fdc9e9a7e6635d6<br />
19f50af773452247 0502cab26a52a198<br />
f5b00a2798589165 07b0b4e9ef9524d6<br />
<br />
== SRP B ==<br />
<br />
private b (normally random) (base 10):<br />
1198277<br />
66042000957856349411550091527197<br />
08125226960544768993643095227800<br />
29105361555030352774505625606029<br />
71778328003253459733139844872578<br />
33596964143417216389157582554932<br />
02841499373672188315534280693274<br />
23189198736863575142046053414928<br />
39548731879043135718309539706489<br />
29157321423483527294767988835942<br />
53343430842313006332606344714480<br />
99439808861069316482621424231409<br />
08830704769167700098392968117727<br />
43420990997238759832829219109897<br />
32876428831985487823417312772399<br />
92628295469389578458363237146486<br />
38545526799188280210660508721582<br />
00403102624831815596140094933216<br />
29832845626116777080504444704039<br />
04739431335617585333671378812943<br />
<br />
private b (hex):<br />
00f3000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 000000000000000f<br />
<br />
transmitted srpB:<br />
0022ce5a7b9d8127 7172caa20b0f1efb<br />
4643b3becc535664 73959b07b790d3c3<br />
f08650d5531c19ad 30ebb67bdb481d1d<br />
9cf61bf272f84398 48fdda58a4e6abc5<br />
abb2ac496da5098d 5cbf90e29b4b110e<br />
4e2c033c70af7392 5fa37457ee13ea3e<br />
8fde4ab516dff1c2 ae8e57a6b264fb9d<br />
b637eeeae9b5e43d faba9b329d3b8770<br />
ce89888709e02627 0e474eef822436e6<br />
397562f284778673 a1a7bc12b6883d1c<br />
21fbc27ffb3dbeb8 5efda279a69a1941<br />
4969113f10451603 065f0a0126666456<br />
51dde44a52f4d8de 113e2131321df1bf<br />
4369d2585364f9e5 36c39a4dce33221b<br />
e57d50ddccb4384e 3612bbfd03a268a3<br />
6e4f7e01de651401 e108cc247db50392<br />
<br />
== SRP A ==<br />
<br />
private a (normally random) (base 10):<br />
1193346<br />
47663227291363113405741243413916<br />
43482736314616601220006703889414<br />
28162541137108417166380088052095<br />
43910927476491099816542561560345<br />
50331133015255005622124012256352<br />
06121987030570656676375703406470<br />
63422988042473190059156975005813<br />
46381864669664357382020200036915<br />
26156674010218162984912976536206<br />
14440782978764393137821956464627<br />
16314542157937343986808167341567<br />
89864323268060014089757606109012<br />
50649711198896213496068605039486<br />
22864591676298304745954690086093<br />
75374681084741884719851454277570<br />
80362211874088739962880012800917<br />
05751238004976540634839106888223<br />
63866455314898189520502368799907<br />
19946264951520393624479315579863<br />
<br />
private a (hex):<br />
00f2000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 000000000000d3d7<br />
<br />
transmitted srpA:<br />
007da76cb7e77af5 ab61f334dbd5a958<br />
513afcdf0f47ab99 271fc5f7860fe213<br />
2e5802ca79d2e5c0 64bb80a38ee08771<br />
c98a937696698d87 8d78571568c98a1c<br />
40cc6e7cb101988a 2f9ba3d65679027d<br />
4d9068cb8aad6ebf f0101bab6d52b5fd<br />
fa81d2ed48bba119 d4ecdb7f3f478bd2<br />
36d5749f2275e948 4f2d0a9259d05e49<br />
d78a23dd26c60bfb a04fd346e5146469<br />
a8c3f010a627be81 c58ded1caaef2363<br />
635a45f97ca0d895 cc92ace1d09a99d6<br />
beb6b0dc0829535c 857a419e834db128<br />
64cd6ee8a843563b 0240520ff0195735<br />
cd9d316842d5d3f8 ef7209a0bb4b54ad<br />
7374d73e79be2c39 75632de562c59647<br />
0bb27bad79c3e2fc ddf194e1666cb9fc<br />
<br />
== SRP key-agreement ==<br />
<br />
u:<br />
b284aa1064e87751 50da6b5e2147b47c<br />
a7df505bed94a6f4 bb2ad873332ad732<br />
<br />
S:<br />
0092aaf0f527906a a5e8601f5d707907<br />
a03137e1b601e04b 5a1deb02a981f4be<br />
037b39829a27dba5 0f1b27545ff2e287<br />
29c2b79dcbdd32c9 d6b20d340affab91<br />
a626a8075806c26f e39df91d0ad979f9<br />
b2ee8aad1bc783e7 097407b63bfe58d9<br />
118b9b0b2a7c5c4c debaf8e9a460f4bf<br />
6247b0da34b760a5 9fac891757ddedca<br />
f08eed823b090586 c63009b2d740cc9f<br />
5397be89a2c32cdc fe6d6251ce11e44e<br />
6ecbdd9b6d93f30e 90896d2527564c7e<br />
b9ff70aa91acc0ba c1740a11cd184ffb<br />
989554ab58117c21 96b353d70c356160<br />
100ef5f4c28d19f6 e59ea2508e8e8aac<br />
6001497c27f362ed bafb25e0f045bfdf<br />
9fb02db9c908f103 40a639fe84c31b27<br />
<br />
M1:<br />
27949ec1e0f16256<br />
33436865edb037e2<br />
3eb6bf5cb91873f2<br />
a2729373c2039008<br />
<br />
srpK:<br />
e68fd0112bfa31dc<br />
ffc8e9c96a1cbadb<br />
4c3145978ff35c73<br />
e5bf8d30bbc7499a<br />
<br />
== /auth ==<br />
<br />
srpK:<br />
e68fd0112bfa31dc<br />
ffc8e9c96a1cbadb<br />
4c3145978ff35c73<br />
e5bf8d30bbc7499a<br />
<br />
respHMACkey:<br />
6584613597ef012f<br />
f1752b7869f01d03<br />
c72547a7b7199681<br />
531d9df1991edf23<br />
<br />
respXORkey:<br />
455835926ae37a1b<br />
627bd16affbeeab6<br />
27ecc737121826ca<br />
4a2bac2c100bf417<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
plaintext:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
ciphertext:<br />
253957f10e861c7c<br />
0a12bb0193d384d9<br />
579db544666d50bd<br />
3252d6576c768a68<br />
<br />
MAC:<br />
a98c87f5769ab4cc<br />
ca3df863faeb217e<br />
b16ddc29d712b301<br />
12b446324ee806d6<br />
<br />
response:<br />
253957f10e861c7c<br />
0a12bb0193d384d9<br />
579db544666d50bd<br />
3252d6576c768a68<br />
a98c87f5769ab4cc<br />
ca3df863faeb217e<br />
b16ddc29d712b301<br />
12b446324ee806d6<br />
<br />
== authtoken ==<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
tokenID (authToken):<br />
9a39818e3bbe6132<br />
38c9d7ff013a1841<br />
1ed2c66c3565c3c4<br />
de03feefecb7d212<br />
<br />
reqHMACkey:<br />
4a17cbdd54ee17db<br />
426fcd7baddff587<br />
231d7eadb408c091<br />
ce19ca915b715985<br />
<br />
requestKey:<br />
9d93978e662bfc6e<br />
8cc203fa4628ef5a<br />
7bf1ddfd7ee54e97<br />
ec5c033257b4fca9<br />
<br />
== /session ==<br />
<br />
requestKey:<br />
9d93978e662bfc6e<br />
8cc203fa4628ef5a<br />
7bf1ddfd7ee54e97<br />
ec5c033257b4fca9<br />
<br />
respHMACkey:<br />
cd3f50403d060b21<br />
76d32f71ca105bd8<br />
7c9b6c4e10e3ebf9<br />
3f5077bec2db24fa<br />
<br />
respXORkey:<br />
8422c53143dea9c6<br />
044afbe95228f291<br />
74996b830b1794a3<br />
eff132da53174d43<br />
92eeb87ccf8ad7a8<br />
0c432894e066de6b<br />
0ff70658dfbf2f07<br />
b9c7704045edcd54<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
sessionToken:<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
plaintext:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
ciphertext:<br />
04a347b2c75b2f41<br />
8cc37162dea57c1e<br />
e408f9109f820234<br />
7768a841cf8ad3dc<br />
324f1adf6b2f710f<br />
a4ea823f4ccb70c4<br />
bf46b4eb6b0a99b0<br />
017ecafbf95073eb<br />
<br />
MAC:<br />
7973ddbb184b601a<br />
c4df09704028ebfc<br />
754dd50e7d8eebfa<br />
52ce3fd868c69852<br />
<br />
response:<br />
04a347b2c75b2f41<br />
8cc37162dea57c1e<br />
e408f9109f820234<br />
7768a841cf8ad3dc<br />
324f1adf6b2f710f<br />
a4ea823f4ccb70c4<br />
bf46b4eb6b0a99b0<br />
017ecafbf95073eb<br />
7973ddbb184b601a<br />
c4df09704028ebfc<br />
754dd50e7d8eebfa<br />
52ce3fd868c69852<br />
<br />
== /account/keys ==<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
tokenID (keyFetchToken):<br />
3d0a7c02a15a62a2<br />
882f76e39b6494b5<br />
00c022a8816e0486<br />
25a495718998ba60<br />
<br />
reqHMACkey:<br />
87b8937f61d38d0e<br />
29cd2d5600b3f4da<br />
0aa48ac41de36a0e<br />
fe84bb4a9872ceb7<br />
<br />
keyRequestKey:<br />
14f338a9e8c6324d<br />
9e102d4e6ee83b20<br />
9796d5c74bb734a4<br />
10e729e014a4a546<br />
<br />
respHMACkey:<br />
f824d2953aab9faf<br />
51a1cb65ba9e7f9e<br />
5bf91c8d8fd1ac1c<br />
8c2d31853a8a1210<br />
<br />
respXORkey:<br />
ce7d7aa77859b235<br />
9932970bbe2101f2<br />
e80d01faf9191bd5<br />
ee52181d2f0b7809<br />
8281ba8cff392543<br />
3a89f7c3095e0c89<br />
900a469d60790c83<br />
3281c4df1a11c763<br />
<br />
kA:<br />
2021222324252627<br />
28292a2b2c2d2e2f<br />
3031323334353637<br />
38393a3b3c3d3e3f<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
plaintext:<br />
2021222324252627<br />
28292a2b2c2d2e2f<br />
3031323334353637<br />
38393a3b3c3d3e3f<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
ciphertext:<br />
ee5c58845c7c9412<br />
b11bbd20920c2fdd<br />
d83c33c9cd2c2de2<br />
d66b222613364636<br />
c2c0f8cfbb7c6304<br />
72c0bd88451342c6<br />
c05b14ce342c5ad4<br />
6ad89e84464c993c<br />
<br />
MAC:<br />
3927d30230157d08<br />
17a077eef4b20d97<br />
6f7a97363faf3f06<br />
4c003ada7d01aa70<br />
<br />
response:<br />
ee5c58845c7c9412<br />
b11bbd20920c2fdd<br />
d83c33c9cd2c2de2<br />
d66b222613364636<br />
c2c0f8cfbb7c6304<br />
72c0bd88451342c6<br />
c05b14ce342c5ad4<br />
6ad89e84464c993c<br />
3927d30230157d08<br />
17a077eef4b20d97<br />
6f7a97363faf3f06<br />
4c003ada7d01aa70<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
unwrapBKey:<br />
6ea660be9c89ec35<br />
5397f89afb282ea0<br />
bf21095760c8c500<br />
9bbcc894155bbe2a<br />
<br />
kB:<br />
2ee722fdd8ccaa72<br />
1bdeb2d1b76560ef<br />
ef705b04349d9357<br />
c3e592cf4906e075<br />
<br />
== use session (certificate/sign, etc) ==<br />
<br />
sessionToken:<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
tokenID (sessionToken):<br />
639503a218ffbb62<br />
983e9628be5cd64a<br />
0438d0ae81b2b9da<br />
deb900a83470bc6b<br />
<br />
reqHMACkey:<br />
3a0188943837ab22<br />
8fe74e759566d0e4<br />
837cbcc7494157aa<br />
c4da82025b2811b2<br />
<br />
== /password/change ==<br />
<br />
requestKey:<br />
9d93978e662bfc6e<br />
8cc203fa4628ef5a<br />
7bf1ddfd7ee54e97<br />
ec5c033257b4fca9<br />
<br />
respHMACkey:<br />
60d01e3d1da53b10<br />
93124a30c26889d7<br />
b2e067e7a09fde14<br />
6f935e3c653614f9<br />
<br />
respXORkey:<br />
3de5bd5e80faf84a<br />
dfca5396148123ef<br />
8184cd4bc10a7c8a<br />
db1688495affee67<br />
e07f80d914c5105f<br />
c86d6af24c4be1b1<br />
ef6c9c661422ac43<br />
181b3d29624a0cc2<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
accountResetToken:<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
plaintext:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
ciphertext:<br />
bd643fdd047f7ecd<br />
5743d91d980cad60<br />
11155fd8559fea1d<br />
438f12d2c66270f8<br />
20be421ad000d698<br />
00a4a03980862f7e<br />
3fbd4eb5c0f77a94<br />
c0c2e7f2be97d21d<br />
<br />
MAC:<br />
804fc4bc30923cc0<br />
d6c07ffea954848e<br />
0076b94f7deee71f<br />
a34db5c106d91980<br />
<br />
response:<br />
bd643fdd047f7ecd<br />
5743d91d980cad60<br />
11155fd8559fea1d<br />
438f12d2c66270f8<br />
20be421ad000d698<br />
00a4a03980862f7e<br />
3fbd4eb5c0f77a94<br />
c0c2e7f2be97d21d<br />
804fc4bc30923cc0<br />
d6c07ffea954848e<br />
0076b94f7deee71f<br />
a34db5c106d91980<br />
<br />
== /account/reset ==<br />
<br />
accountResetToken:<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
tokenID (accountResetToken):<br />
a6857e5d53d35073<br />
d50ef2ce2c4dd747<br />
32bb2eae1af5bf79<br />
618ed945e1310792<br />
<br />
reqHMACkey:<br />
47fab27352ee6b48<br />
33938d76519bbdb8<br />
ac7293f8b5e74335<br />
6fdd1d5edf39f52d<br />
<br />
reqXORkey:<br />
82ed612313a11673 95108d7d379b2029<br />
7a539ce9d3861e95 1bf5a9b9cdbfb332<br />
bd6aba056ce0c568 2c5a93963446b1b4<br />
7397c8c24f3a1d67 2a0ddc856474f5b1<br />
33ab884ce33335c1 5578a1a7302933cb<br />
458fbee0a5e52414 c914beb97568a30c<br />
28364dc8fb03ae7c 76a2f324a9a1cee6<br />
71b74aa8906d0e03 39fb52a1bf2b1ef5<br />
ab5d883295db62af 20701cb3af42a09e<br />
c76cda585ab5644b 7250ef7b780537e5<br />
b3e784d37a118bd6 57a0fe29ec6e5cd3<br />
325be8e1d8a3dd71 b360ea266757e463<br />
ada6b0a7a85a8ac0 eed618d9f6ee91ab<br />
1d2f714f224d67db 46843c4e3339de15<br />
efe0297a45f9fe0d 6d768b5c589a290f<br />
11f03237192cc0a3 a02645a810d83bb1<br />
84d582bfb15d2393 3fa4805374da62c6<br />
a2c887b157285c6a 79b47156c9abe02e<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
newSRPv:<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
<br />
plaintext:<br />
4041424344454647 48494a4b4c4d4e4f<br />
5051525354555657 58595a5b5c5d5e5f<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
<br />
ciphertext:<br />
c2ac236057e45034 dd59c7367bd66e66<br />
2a02ceba87d348c2 43acf3e291e2ed6d<br />
ac7bab147df1d479 3d4b82872557a0a5<br />
6286d9d35e2b0c76 3b1ccd947565e4a0<br />
22ba995df22224d0 4469b0b6213822da<br />
549eaff1b4f43505 d805afa86479b21d<br />
39275cd9ea12bf6d 67b3e235b8b0dff7<br />
60a65bb9817c1f12 28ea43b0ae3a0fe4<br />
ba4c992384ca73be 31610da2be53b18f<br />
d67dcb494ba4755a 6341fe6a691426f4<br />
a2f695c26b009ac7 46b1ef38fd7f4dc2<br />
234af9f0c9b2cc60 a271fb377646f572<br />
bcb7a1b6b94b9bd1 ffc709c8e7ff80ba<br />
0c3e605e335c76ca 57952d5f2228cf04<br />
fef1386b54e8ef1c 7c679a4d498b381e<br />
00e12326083dd1b2 b13754b901c92aa0<br />
95c493aea04c3282 2eb5914265cb73d7<br />
b3d996a046394d7b 68a56047d8baf13f<br />
<br />
== /account/destroy ==<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
tokenID (authToken):<br />
9a39818e3bbe6132<br />
38c9d7ff013a1841<br />
1ed2c66c3565c3c4<br />
de03feefecb7d212<br />
<br />
reqHMACkey:<br />
4a17cbdd54ee17db<br />
426fcd7baddff587<br />
231d7eadb408c091<br />
ce19ca915b715985<br />
<br />
= Keyserver Protocol Summary =<br />
<br />
* POST /account/create (email,srpV,srpSalt) -> ok (server sends verification email)<br />
** creates a user account<br />
* GET /account/devices [sessionToken] () -> list of devices<br />
* GET /account/keys [keyFetchToken,needs-verf] () -> kA/wrap(kB)<br />
** single-use, only if email is verified, encrypted results<br />
* POST /account/reset [authed+encrypted by accountResetToken] (wrap(kB),srpV,srpSalt) -> ok<br />
** single-use, does not require email to be verified, revoke all tokens for account, send notification email to user<br />
* POST /account/delete [authToken] () -> ok, account deleted<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authToken] () -> keyFetchToken, sessionToken<br />
* POST /session/destroy [sessionToken] () -> ok<br />
** for detaching a device, destroy all tokens<br />
* POST /recovery_email/status [sessionToken] () -> "verified" status of email<br />
** use "Accept: text/event-stream" header for server-sent-events; server will send "update" event with the new content of the resource any time it changes.<br />
* POST /recovery_email/resend_code [sessionToken] () -> re-send verification email<br />
* POST /recovery_email/verify_code (code) -> set "verified" flag<br />
** this code will come from a clickable link and is an unauthenticated endpoint<br />
** this could maybe take the recovery method if that would be helpful<br />
** sets verified flag on recovery method<br />
* POST /certificate/sign [sessionToken,needs-verf] (pubkey) -> cert<br />
** only if recovery email is verified<br />
* POST /password/change/start [authToken,needs-verf] () -> accountResetToken, keyFetchToken<br />
* POST /password/forgot/send_code () -> forgotPasswordToken<br />
** sends code to recovery method (email for now, maybe SMS later)<br />
** this is a short code, not a clickable link<br />
* POST /password/forgot/resend_code (forgotPasswordToken) -> re-sends code<br />
* POST /password/forgot/verify_code (forgotPasswordToken, code) -> accountResetToken<br />
** sets verified flag on recovery method<br />
* POST /get_random_bytes<br />
<br />
<br />
== Typical Client Flows ==<br />
<br />
Create account<br />
<br />
* POST /account/create (email,srpV,srpSalt) -> ok (server sends verification email)<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authed with authToken]() -> keyFetchToken, sessionToken<br />
* GET /recovery_email/status [sessionToken] () -> "verified" status<br />
** (optional, only if user requests resend) POST /recovery_email/resend_code [sessionToken]() -> ok<br />
** POST /recovery_email/verify_code (code) -> ok<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
* POST /certificate/sign [sessionToken] (pubkey) -> cert<br />
<br />
<br />
Attach to new device<br />
<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authToken] () -> keyFetchToken, sessionToken<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
** (if unverified-error, do waitUntilEmailVerified, then try again)<br />
* POST /certificate/sign [sessionToken] (pubkey) -> cert<br />
<br />
<br />
Forgot password<br />
<br />
* POST /password/forgot/send_code (email) -> forgotPasswordToken<br />
* POST /password/forgot/verify_code (forgotPasswordToken, code) -> accountResetToken<br />
* POST /account/reset [authed+encrypted by accountResetToken] (0000,srpV,srpSalt) -> ok<br />
* GOTO "Attach to new device"<br />
<br />
<br />
Change Password<br />
<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /password/change/start [authToken] () -> accountResetToken, keyFetchToken<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
* POST /account/reset [authed+encrypted by accountResetToken] (wrap(kB),srpV,srpSalt) -> ok<br />
* GOTO "Attach to new device"</div>Warnerhttps://wiki.mozilla.org/index.php?title=File:PICL-IdPAuth-keys-server.png&diff=709471File:PICL-IdPAuth-keys-server.png2013-09-17T00:10:13Z<p>Warner: Warner uploaded a new version of &quot;File:PICL-IdPAuth-keys-server.png&quot;</p>
<hr />
<div></div>Warnerhttps://wiki.mozilla.org/index.php?title=File:PICL-IdPAuth-keys-client.png&diff=709470File:PICL-IdPAuth-keys-client.png2013-09-17T00:09:53Z<p>Warner: Warner uploaded a new version of &quot;File:PICL-IdPAuth-keys-client.png&quot;</p>
<hr />
<div></div>Warnerhttps://wiki.mozilla.org/index.php?title=File:PICL-IdPAuth-keys-client.png&diff=709455File:PICL-IdPAuth-keys-client.png2013-09-16T23:57:34Z<p>Warner: Warner uploaded a new version of &quot;File:PICL-IdPAuth-keys-client.png&quot;</p>
<hr />
<div></div>Warnerhttps://wiki.mozilla.org/index.php?title=File:PICL-IdPAuth-keys-server.png&diff=709454File:PICL-IdPAuth-keys-server.png2013-09-16T23:57:10Z<p>Warner: Warner uploaded a new version of &quot;File:PICL-IdPAuth-keys-server.png&quot;</p>
<hr />
<div></div>Warnerhttps://wiki.mozilla.org/index.php?title=Identity/WeeklyMeeting/2013-09-16&diff=709357Identity/WeeklyMeeting/2013-09-162013-09-16T21:35:10Z<p>Warner: </p>
<hr />
<div>=AGENDA=<br />
* Re-org questions/concerns<br />
* Update on "the" PiCL (strawplan)! (see notes below)<br />
<br />
Great job everyone involved with the RP interviews & the Persona meetup<br />
* RP interviews took place last week - update from ozten<br />
* SF Persona Event last Thursday - update from didem, dan, zaach<br />
* Spread the word - Mountain View Persona Event : engineering support from seanmonstar/zaach<br />
** Date & Time: September 25, 5:30pm - 9:30pm Pacific Time<br />
** Location: Mozilla Mountain View Office. 650 Castro St, Mountain View<br />
** RSVP: http://persona-meetup-mv.eventbrite.com/<br />
* Status report archive: https://wiki.mozilla.org/Identity/Department_Status<br />
General Reminders:<br />
* Have you signed up for note taking?<br />
** See: https://id.etherpad.mozilla.org/weekly-notetakers<br />
* Monday meeting: <br />
** etherpad: https://id.etherpad.mozilla.org/weekly-moco-updates<br />
** Content deadline: 5 pm PDT every Thursday<br />
<br />
= Meeting Notes =<br />
* lots of folks are in Seattle this week. Seattle != Portland. warner is geographically challenged<br />
* reorg? where does UX fit in? UX and product are thrown in with engineering. We're all confused. We'll all work together. signin team will talk about priorities later this week.<br />
* gene asks "is this reorg different from the one lloyd told us about earlier?". no, new one. We're all trying to keep up. That one was identity reporting to mmayo. This one is product/UX now also reporting to mmayo. product/UX are now merged into product-focussed arms, ours is called Cloud Services (distinct from Firefox, FxOS, Platform, Marketplace). org-wide shifting, making multiple products look like multiple lines of business. rlord: what do jay's comments about focus/results mean for us? mmayo: may influence how we build things, frame how we go to market. Bringing something completely new to market is different than providing a well-needed mundane "table-stakes" feature.<br />
* mmayo: hard to know how to evaluate our projects: what do people really care about, how to influence/improve adoption. Do our products need wide adoption to succeed? To some extent, adoption has seemed like someone else's problem. Who is driving? Responsibility has been spread out, leads to stop energy. With reorg, at the end of the day, mmayo is the decision maker. If we're a service org, is our customer the rest of mozilla? or the end user? Sync might be like Platform: providing services to the rest of firefox/etc, but not a product in itself. Not easy questions. Geolocation: same question.<br />
** "Cloud" is a horrible word, but sets expectations reasonably well, was chosen consciously.<br />
* awesome RP interviews happened last week, Persona event in SF, another one next week. ozten will send some email summaries.<br />
* lloyd on strawplan (picl+persona=?): we defined MVP a couple of months ago, after listening to everyone, had a "fun" Design Review, found three challenges. 1: are we not leveraging/encouraging Persona enough. 2: are we not improving the signup flow (the browser has more info, could use it). 3: are we taking too large of a risk in this post-PRISM world by reducing security compared to Sync1.0/pairing.<br />
** how to address these, maintain momentum?<br />
** the plan: Firefox Accounts aren't going away, we need a DB, will have data, will keep pushing on this<br />
** Persona front+center in sync is interesting adoption approach. Would probably need it native, Persona would need to be out of beta (stable formats, load-capable servers)<br />
** plan: make minimal tweaks to current milestones, to leave room for adding persona. Prioritize getting native persona out of beta. Give us the room to defer the decision for a couple of months without losing momentum or visibility.<br />
** callahad: this gives signin team a good goal, helps focus<br />
** lloyd's goal is to circulate the plan wider this week<br />
* JGruen sez: RP Interview summary on its way!<br />
* RP interviews (ozten): john and didem have interviewed a variety of sites. Some use it for the privacy protection, some just add it to the NASCAR collection. Biggest win is simplicity of implementation, some called it "magic". Mozilla is well-trusted in the developer community, some RPs tried Persona just because they trust us. They have good channels to Persona devs. One A/B test: Persona beat google and twitter, lost slightly to facebook. Some users are confused about what Persona is (why am I being sent to a third-party site? what's all this mozilla branding?). Better adoption when Persona button is set apart from the NASCAR bundle, of course. RPs seem willing to work with us to do A/B testing. One implementation problem with a Pyramid (python) web framework, we should put some time into the RP frameworks. Also put some more work into buttons. No one wanted more features. Sites that use social-signon weren't doing anything with the extra user data thus obtained.<br />
* [action] Determine how much scrubbing do we need to do to make the RP interview write-up public once Johns has posted to staff<br />
* SF Persona event (callahad): notes are in toxborrow's biweekly email. 20-30 people showed up. Probably didn't hit the demographic we wanted: not a plurality of developers, but several people doing data science. Probably not going to drive RP adoption. Lots of folks doing devices or mobile apps or native apps, internet-of-things stuff, interested in using Persona to somehow connect/combine these things. Recording of the presentation is on airmozilla.<br />
* rfeeley's pixels: native persona, adding a persona button into the URL bar next to the lock icon or favicon. Wants to be in left-most chevron. Doesn't handle multiple emails so well, should encourage small-number-of-emails. Showed a very cool diagram of the PICL setup/signin/pref flow, some speculative ones, will try to get URLs to make those public.<br />
<br />
= Team Status =<br />
== Native (B2G) ==<br />
* <br />
== Signin to the Web ==<br />
* RP branded emails has LANDED<br />
* Realms going through security review<br />
* stomlinson going to work on metrics & a/b testing for 1.5-2 months<br />
* train-2013.09.11 branched<br />
== Profile in the Cloud (PiCL) ==<br />
* we're continuing to build out "login to browser" on Fx and Fennec<br />
* still figuring out our "how does this relate to Persona" redux<br />
== WebRTC ==<br />
* landing patches in nightly:<br />
** jedp has bigger fish to fry, rseys can help if he knows what to do<br />
* rfeeley wants to revive TinCan (rseys is onboard)<br />
** discuss what needs to happen first<br />
*** ssl cert on tincan.im (startssl?)<br />
*** beta.tincan.im?<br />
*** tincan auth addon advertised<br />
*** land persona changes to sign arbitrary payload (whoa nelly this might take a while)<br />
*** land changes in nightly from jedp & rseys patches (or use addon but prefer patches)<br />
= Libraries =<br />
* Fix for broswerid-verify for node (should check content-type, not just go on status code)<br />
* v0.46 of browserid-wordpress plugin released.<br />
= UX =<br />
* All UX talks, mocks, etc for Identity has a home and has been recently updated with the latest flows for FxOS, Fx Android and Fx Desktop.<br />
** https://wiki.mozilla.org/Identity/UX<br />
= Business Development =<br />
= Product Marketing =<br />
= Docs & Engagement =<br />
* Gave a talk at OWASP Day NZ: https://groups.google.com/d/topic/mozilla.dev.identity/GVfMI0zLbnE/discussion<br />
* Talking at JSFoo.in this week<br />
= Product =<br />
= QA =<br />
* jbonacci:<br />
** Continuing to test and investigate the unit tests/functional tests for IDP, FAB, SH, etc.<br />
** Digging into the IDP and SH load tests - several issues to investigate here...<br />
* Persona train-2013.08.28 went live last Thursday, next train-2013.09.11 in progress<br />
* tried out chilts aws/route53 branch and looks good to me.<br />
= Blog Schedule =<br />
* We are working on launching a new blog series. Stay tuned for more information soon<br />
= PTO/OOO (kept in the Identity calendar for everyone's reference) =<br />
* Francois: 18-23 Sep (JSFoo.in)<br />
* Francois: 25-27 (TRIBE and getting there)<br />
* Sam 27 Sept - PTO<br />
* Brian: 2 Oct - OOO fly to Brussels for summit<br />
* John G: 11 Oct - PTO<br />
* Brian: 7 Oct - 16-oct - PTO<br />
* Brian: 8 Nov - 11-nov - OOO conference</div>Warnerhttps://wiki.mozilla.org/index.php?title=Identity/AttachedServices/Key_Stretching_Performance_Tests&diff=698389Identity/AttachedServices/Key Stretching Performance Tests2013-08-22T22:00:48Z<p>Warner: convert to secs/op</p>
<hr />
<div>{| border="1" cellpadding="2"<br />
|+ Test Results (in seconds per operation)<br />
|-<br />
! Browser / Test !! Firefox 23 Desktop (MBP) !! Google Chrome 28 (MBP) !! Firefox Android Beta 24 (Nexus4) !! Firefox OS (Keon) !! Android WebView (Nexus4) !! Android + OpenSSL<br />
|-<br />
! [1] 20k PBKDF2-SHA256<br />
| 186ms (5.39 ops/sec ±4.20%, 30 runs sampled) || 123ms (8.12 ops/sec ±1.15%, 43 runs sampled) || 2.13s (0.47 ops/sec ±7.73%, 7 runs sampled) || 4.17ms (0.24 ops/sec ±7.06%, 6 runs sampled) || 1.52s (0.66 ops/sec ±4.35%, 8 runs sampled) || 351ms (2.85 ops/sec, **1 run sampled**)<br />
|-<br />
! [2] 20k*PBKDF + remote(scrypt(64k,8,1)) + 20k*PBKDF<br />
| 1.52s (0.66 ops/sec ±10.70%, 8 runs sampled) || 1.27s (0.79 ops/sec ±1.98%, 8 runs sampled) || 6.25s (0.16 ops/sec ±9.02%, 5 runs sampled) || 10s (0.10 ops/sec ±3.37%, 5 runs sampled) || 5.56s (0.18 ops/sec ±21.96%, 6 runs sampled) || --<br />
|-<br />
|-<br />
! [3] 20kPBKDF + local(scrypt(64k,8,1)) + 20kPBKDF<br />
| -- || -- || -- || -- || -- || 7.3s (0.1369 ops/sec, 10 runs sampled, extremes discarded -- 7362-7505msec)<br />
|-<br />
|-<br />
! [4] 20kPBKDF + emscripten(scrypt(64k,8,1)) + 20kPBKDF<br />
| 3.6s (0.28 ops/sec ±26.77%, 6 runs sampled) || 3.2s (0.31 ops/sec ±1.46%, 6 runs sampled) || 20s (0.05 ops/sec ±25.20%, 5 runs sampled) || <span style='font-size:43px'>&#9760;</span> || 33s (0.03 ops/sec ±2.86%, 5 runs sampled) || --<br />
|-<br />
|}<br />
<br />
<br />
'''Tests'''<br />
* [1] '''20k PBKDF2-SHA256''', pdbkdf2.derive(...) - Client pdbkdf2 derivation [https://wiki.mozilla.org/Identity/AttachedServices/KeyServerProtocol#stretch-KDF #stretch-KDF]<br />
* [2] '''20k*PBKDF+scrypt(64k,8,1)+20k*PBKDF''' keyStretch.derive(email, password) - Full Key Stretch with a remote scrypt helper at "http://scrypt.dev.lcip.org/" (EC2 m1.small, est scrypt(64k,8,1) time = 2.2s). [https://wiki.mozilla.org/Identity/AttachedServices/KeyServerProtocol#stretch-KDF #stretch-KDF + #main-KDF]<br />
* [3] '''20kPBKDF + local(scrypt(64k,8,1)) + 20kPBKDF'''<br />
* [4] '''20kPBKDF + emscripten(scrypt(64k,8,1)) + 20kPBKDF''' Using the [https://github.com/tonyg/js-scrypt Pure-Javascript Emscripten-compiled scrypt routine]. Modified 'TOTAL_MEMORY' to 'scrypt_module_factory(134217728);'<br />
<br />
Note that the m1.small instance runs scrypt in 2.2s, whereas the more cost-effective (given serious load) c1.medium or c1.xlarge instances will run it in 1.0s .<br />
<br />
'''Test Framework'''<br />
<br />
These performance tests use [http://benchmarkjs.com/ Benchmark.js]<br />
<br />
* "ops/sec" stands for operations per second. That is how many times a test is projected to execute in a second.<br />
* A test is repeatedly executed until it reaches the minimum time needed to get a percentage uncertainty for the measurement of less than or equal to 1%. The number of iterations will vary depending on the resolution of the environment’s timer and how many times a test can execute in the minimum run time. We collect completed test runs for 5 seconds (configurable), or at least 5 runs (also configurable), and then perform statistical analysis on the sample. So, a test may be repeated 100,000 times in 50 ms (the minimum run time for most environments), and then repeated 100 times more (5 seconds). A larger sample size (in this example, 100), leads to a smaller margin of error.<br />
* benchmark.js returns ops/sec, rounded to hundredths. This number was inverted to obtain secs/op, losing precision in the process.<br />
<br />
'''Hardware Used'''<br />
<br />
* Firefox Desktop, Google Chrome - MacBook Pro 2.7 GHz i7, 16 GB 1600MHz DDR3<br />
* Firefox OS - Keon (Snapdragon S1 7225AB 1Ghz, 512 RAM)<br />
* Firefox Android, Android Webview - Nexus 4 (1.512 GHz quad-core, 2GB RAM)<br />
<br />
Test Suite Page: [http://v14d.com/picl/benchmark.html http://v14d.com/picl/benchmark.html]</div>Warnerhttps://wiki.mozilla.org/index.php?title=Identity/AttachedServices/Key_Stretching_Performance_Tests&diff=697971Identity/AttachedServices/Key Stretching Performance Tests2013-08-21T21:36:38Z<p>Warner: oops, s not ms</p>
<hr />
<div>{| border="1" cellpadding="2"<br />
|+ Test Results (in seconds per operation)<br />
|-<br />
! Browser / Test !! Firefox 23 Desktop !! Google Chrome 28 !! Firefox Android Beta 24 !! Firefox OS !! Android WebView !! Android + OpenSSL<br />
|-<br />
! [1] 20k PBKDF2-SHA256<br />
| 186ms (5.39 ops/sec ±4.20%, 30 runs sampled) || 123ms (8.12 ops/sec ±1.15%, 43 runs sampled) || 2.13s (0.47 ops/sec ±7.73%, 7 runs sampled) || 4.17ms (0.24 ops/sec ±7.06%, 6 runs sampled) || 1.52s (0.66 ops/sec ±4.35%, 8 runs sampled) || 351ms (2.85 ops/sec, **1 run sampled**)<br />
|-<br />
! [2] 20k*PBKDF + remote(scrypt(64k,8,1)) + 20k*PBKDF<br />
| 1.52s (0.66 ops/sec ±10.70%, 8 runs sampled) || 1.27s (0.79 ops/sec ±1.98%, 8 runs sampled) || 6.25s (0.16 ops/sec ±9.02%, 5 runs sampled) || 10s (0.10 ops/sec ±3.37%, 5 runs sampled) || 5.56s (0.18 ops/sec ±21.96%, 6 runs sampled) || --<br />
|-<br />
|-<br />
! [3] 20kPBKDF + local(scrypt(64k,8,1)) + 20kPBKDF<br />
| -- || -- || -- || -- || -- || 7.3s (0.1369 ops/sec, 10 runs sampled, extremes discarded -- 7362-7505msec)<br />
|-<br />
|}<br />
<br />
<br />
'''Tests'''<br />
* [1] '''20k PBKDF2-SHA256''', pdbkdf2.derive(...) - Client pdbkdf2 derivation [https://wiki.mozilla.org/Identity/AttachedServices/KeyServerProtocol#stretch-KDF #stretch-KDF]<br />
* [2] '''20k*PBKDF+scrypt(64k,8,1)+20k*PBKDF''' keyStretch.derive(email, password) - Full Key Stretch with a remote scrypt helper at "http://scrypt.dev.lcip.org/" (EC2 m1.small, est scrypt(64k,8,1) time = 2.2s). [https://wiki.mozilla.org/Identity/AttachedServices/KeyServerProtocol#stretch-KDF #stretch-KDF + #main-KDF]<br />
* [3] '''20kPBKDF + local(scrypt(64k,8,1)) + 20kPBKDF'''<br />
<br />
Note that the m1.small instance runs scrypt in 2.2s, whereas the more cost-effective (given serious load) c1.medium or c1.xlarge instances will run it in 1.0s .<br />
<br />
'''Test Framework'''<br />
<br />
These performance tests use [http://benchmarkjs.com/ Benchmark.js]<br />
<br />
* "ops/sec" stands for operations per second. That is how many times a test is projected to execute in a second.<br />
* A test is repeatedly executed until it reaches the minimum time needed to get a percentage uncertainty for the measurement of less than or equal to 1%. The number of iterations will vary depending on the resolution of the environment’s timer and how many times a test can execute in the minimum run time. We collect completed test runs for 5 seconds (configurable), or at least 5 runs (also configurable), and then perform statistical analysis on the sample. So, a test may be repeated 100,000 times in 50 ms (the minimum run time for most environments), and then repeated 100 times more (5 seconds). A larger sample size (in this example, 100), leads to a smaller margin of error.<br />
<br />
Test Suite Page: [http://v14d.com/picl/benchmark.html http://v14d.com/picl/benchmark.html]</div>Warnerhttps://wiki.mozilla.org/index.php?title=Identity/AttachedServices/Key_Stretching_Performance_Tests&diff=697898Identity/AttachedServices/Key Stretching Performance Tests2013-08-21T18:04:20Z<p>Warner: rewrite results to secs/op, not ops/sec</p>
<hr />
<div>{| border="1" cellpadding="2"<br />
|+ Test Results (in seconds per operation)<br />
|-<br />
! Browser / Test !! Firefox 23 Desktop !! Google Chrome 28 !! Firefox Android Beta 24 !! Firefox OS !! Android WebView !! Android + OpenSSL<br />
|-<br />
! [1] 20k PBKDF2-SHA256<br />
| 186ms (5.39 ops/sec ±4.20%, 30 runs sampled) || 123ms (8.12 ops/sec ±1.15%, 43 runs sampled) || 2.13s (0.47 ops/sec ±7.73%, 7 runs sampled) || 4.17ms (0.24 ops/sec ±7.06%, 6 runs sampled) || 1.52s (0.66 ops/sec ±4.35%, 8 runs sampled) || 351ms (2.85 ops/sec, **1 run sampled**)<br />
|-<br />
! [2] 20k*PBKDF + remote(scrypt(64k,8,1)) + 20k*PBKDF<br />
| 1.52ms (0.66 ops/sec ±10.70%, 8 runs sampled) || 1.27ms (0.79 ops/sec ±1.98%, 8 runs sampled) || 6.25s (0.16 ops/sec ±9.02%, 5 runs sampled) || 10s (0.10 ops/sec ±3.37%, 5 runs sampled) || 5.56s (0.18 ops/sec ±21.96%, 6 runs sampled) || --<br />
|-<br />
|-<br />
! [3] 20kPBKDF + local(scrypt(64k,8,1)) + 20kPBKDF<br />
| -- || -- || -- || -- || -- || 7.3s (0.1369 ops/sec, 10 runs sampled, extremes discarded -- 7362-7505msec)<br />
|-<br />
|}<br />
<br />
<br />
'''Tests'''<br />
* [1] '''20k PBKDF2-SHA256''', pdbkdf2.derive(...) - Client pdbkdf2 derivation [https://wiki.mozilla.org/Identity/AttachedServices/KeyServerProtocol#stretch-KDF #stretch-KDF]<br />
* [2] '''20k*PBKDF+scrypt(64k,8,1)+20k*PBKDF''' keyStretch.derive(email, password) - Full Key Stretch with a remote scrypt helper at "http://scrypt.dev.lcip.org/" (EC2 m1.small, est scrypt(64k,8,1) time = 2.2s). [https://wiki.mozilla.org/Identity/AttachedServices/KeyServerProtocol#stretch-KDF #stretch-KDF + #main-KDF]<br />
* [3] '''20kPBKDF + local(scrypt(64k,8,1)) + 20kPBKDF'''<br />
<br />
Note that the m1.small instance runs scrypt in 2.2s, whereas the more cost-effective (given serious load) c1.medium or c1.xlarge instances will run it in 1.0s .<br />
<br />
'''Test Framework'''<br />
<br />
These performance tests use [http://benchmarkjs.com/ Benchmark.js]<br />
<br />
* "ops/sec" stands for operations per second. That is how many times a test is projected to execute in a second.<br />
* A test is repeatedly executed until it reaches the minimum time needed to get a percentage uncertainty for the measurement of less than or equal to 1%. The number of iterations will vary depending on the resolution of the environment’s timer and how many times a test can execute in the minimum run time. We collect completed test runs for 5 seconds (configurable), or at least 5 runs (also configurable), and then perform statistical analysis on the sample. So, a test may be repeated 100,000 times in 50 ms (the minimum run time for most environments), and then repeated 100 times more (5 seconds). A larger sample size (in this example, 100), leads to a smaller margin of error.<br />
<br />
Test Suite Page: [http://v14d.com/picl/benchmark.html http://v14d.com/picl/benchmark.html]</div>Warnerhttps://wiki.mozilla.org/index.php?title=Identity/AttachedServices/Key_Stretching_Performance_Tests&diff=697341Identity/AttachedServices/Key Stretching Performance Tests2013-08-20T18:21:05Z<p>Warner: record what the scrypt-helper hardware was</p>
<hr />
<div>{| border="1" cellpadding="2"<br />
|+ Test Results (in ops/sec)<br />
|-<br />
! Browser / Test !! Firefox 23 Desktop !! Google Chrome 28 !! Firefox Android Beta 24 !! Firefox OS !! Android WebView !! Android + OpenSSL<br />
|-<br />
! [1] 20k PBKDF2-SHA256<br />
| 5.39 ops/sec ±4.20% (30 runs sampled) || 8.12 ops/sec ±1.15% (43 runs sampled) || 0.47 ops/sec ±7.73% (7 runs sampled) || 0.24 ops/sec ±7.06% (6 runs sampled) || 0.66 ops/sec ±4.35% (8 runs sampled) || 2.85 ops/sec (**1 run sampled**)<br />
|-<br />
! [2] 20k*PBKDF + remote(scrypt(64k,8,1)) + 20k*PBKDF<br />
| 0.66 ops/sec ±10.70% (8 runs sampled) || 0.79 ops/sec ±1.98% (8 runs sampled) || 0.16 ops/sec ±9.02% (5 runs sampled) || 0.10 ops/sec ±3.37% (5 runs sampled) || 0.18 ops/sec ±21.96% (6 runs sampled) || --<br />
|-<br />
|-<br />
! [3] 20kPBKDF + local(scrypt(64k,8,1)) + 20kPBKDF<br />
| -- || -- || -- || -- || -- || 0.1369 ops/sec (10 runs sampled, extremes discarded -- 7362-7505msec)<br />
|-<br />
|}<br />
<br />
<br />
'''Tests'''<br />
* [1] '''20k PBKDF2-SHA256''', pdbkdf2.derive(...) - Client pdbkdf2 derivation [https://wiki.mozilla.org/Identity/AttachedServices/KeyServerProtocol#stretch-KDF #stretch-KDF]<br />
* [2] '''20k*PBKDF+scrypt(64k,8,1)+20k*PBKDF''' keyStretch.derive(email, password) - Full Key Stretch with a remote scrypt helper at "http://scrypt.dev.lcip.org/" (EC2 m1.small, est scrypt(64k,8,1) time = 2.2s). [https://wiki.mozilla.org/Identity/AttachedServices/KeyServerProtocol#stretch-KDF #stretch-KDF + #main-KDF]<br />
* [3] '''20kPBKDF + local(scrypt(64k,8,1)) + 20kPBKDF'''<br />
<br />
Note that the m1.small instance runs scrypt in 2.2s, whereas the more cost-effective (given serious load) c1.medium or c1.xlarge instances will run it in 1.0s .<br />
<br />
'''Test Framework'''<br />
<br />
These performance tests use [http://benchmarkjs.com/ Benchmark.js]<br />
<br />
* "ops/sec" stands for operations per second. That is how many times a test is projected to execute in a second.<br />
* A test is repeatedly executed until it reaches the minimum time needed to get a percentage uncertainty for the measurement of less than or equal to 1%. The number of iterations will vary depending on the resolution of the environment’s timer and how many times a test can execute in the minimum run time. We collect completed test runs for 5 seconds (configurable), or at least 5 runs (also configurable), and then perform statistical analysis on the sample. So, a test may be repeated 100,000 times in 50 ms (the minimum run time for most environments), and then repeated 100 times more (5 seconds). A larger sample size (in this example, 100), leads to a smaller margin of error.<br />
<br />
Test Suite Page: [http://v14d.com/picl/benchmark.html http://v14d.com/picl/benchmark.html]</div>Warnerhttps://wiki.mozilla.org/index.php?title=Identity/AttachedServices/Key_Stretching_Performance_Tests&diff=697310Identity/AttachedServices/Key Stretching Performance Tests2013-08-20T17:59:01Z<p>Warner: row 2 is remote-scrypt</p>
<hr />
<div>{| border="1" cellpadding="2"<br />
|+ Test Results (in ops/sec)<br />
|-<br />
! Browser / Test !! Firefox 23 Desktop !! Google Chrome 28 !! Firefox Android Beta 24 !! Firefox OS !! Android WebView !! Android + OpenSSL<br />
|-<br />
! [1] 20k PBKDF2-SHA256<br />
| 5.39 ops/sec ±4.20% (30 runs sampled) || 8.12 ops/sec ±1.15% (43 runs sampled) || 0.47 ops/sec ±7.73% (7 runs sampled) || 0.24 ops/sec ±7.06% (6 runs sampled) || 0.66 ops/sec ±4.35% (8 runs sampled) || 2.85 ops/sec (**1 run sampled**)<br />
|-<br />
! [2] 20k*PBKDF + remote(scrypt(64k,8,1)) + 20k*PBKDF<br />
| 0.66 ops/sec ±10.70% (8 runs sampled) || 0.79 ops/sec ±1.98% (8 runs sampled) || 0.16 ops/sec ±9.02% (5 runs sampled) || 0.10 ops/sec ±3.37% (5 runs sampled) || 0.18 ops/sec ±21.96% (6 runs sampled) || --<br />
|-<br />
|-<br />
! [3] 20kPBKDF + local(scrypt(64k,8,1)) + 20kPBKDF<br />
| -- || -- || -- || -- || -- || 0.1369 ops/sec (10 runs sampled, extremes discarded -- 7362-7505msec)<br />
|-<br />
|}<br />
<br />
<br />
'''Tests'''<br />
* [1] '''20k PBKDF2-SHA256''', pdbkdf2.derive(...) - Client pdbkdf2 derivation [https://wiki.mozilla.org/Identity/AttachedServices/KeyServerProtocol#stretch-KDF #stretch-KDF]<br />
* [2] '''20k*PBKDF+scrypt(64k,8,1)+20k*PBKDF''' keyStretch.derive(email, password) - Full Key Stretch with a remote helper at "http://scrypt.dev.lcip.org/". [https://wiki.mozilla.org/Identity/AttachedServices/KeyServerProtocol#stretch-KDF #stretch-KDF + #main-KDF]<br />
* [3] '''20kPBKDF + local(scrypt(64k,8,1)) + 20kPBKDF'''<br />
<br />
'''Test Framework'''<br />
<br />
These performance tests use [http://benchmarkjs.com/ Benchmark.js]<br />
<br />
* "ops/sec" stands for operations per second. That is how many times a test is projected to execute in a second.<br />
* A test is repeatedly executed until it reaches the minimum time needed to get a percentage uncertainty for the measurement of less than or equal to 1%. The number of iterations will vary depending on the resolution of the environment’s timer and how many times a test can execute in the minimum run time. We collect completed test runs for 5 seconds (configurable), or at least 5 runs (also configurable), and then perform statistical analysis on the sample. So, a test may be repeated 100,000 times in 50 ms (the minimum run time for most environments), and then repeated 100 times more (5 seconds). A larger sample size (in this example, 100), leads to a smaller margin of error.<br />
<br />
Test Suite Page: [http://v14d.com/picl/benchmark.html http://v14d.com/picl/benchmark.html]</div>Warnerhttps://wiki.mozilla.org/index.php?title=File:PICL-IdPAuth-main-KDF.png&diff=696689File:PICL-IdPAuth-main-KDF.png2013-08-19T22:01:41Z<p>Warner: Warner uploaded a new version of &quot;File:PICL-IdPAuth-main-KDF.png&quot;</p>
<hr />
<div></div>Warnerhttps://wiki.mozilla.org/index.php?title=Identity/AttachedServices/KeyServerProtocol&diff=692481Identity/AttachedServices/KeyServerProtocol2013-08-09T00:14:55Z<p>Warner: /* Test Vectors */ update vectors to add authToken HKDF (one authTokenID)</p>
<hr />
<div>= PiCL Key Server / IdP Protocol =<br />
<br />
NOTE: This specification is slowly converging on stability (31-Jul-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.<br />
<br />
The server is being developed in https://github.com/mozilla/picl-idp . This repo currently includes a demonstration client (node.js CLI).<br />
<br />
Note that all messages are delivered over an HTTPS connection. The client browser may also implement cert-pinning to improve on the certificate validation process. The protections described below are in addition to those provided by TLS.<br />
<br />
Remaining TODO items:<br />
<br />
* decide on client-side key-stretching parameters: http://keywrapping.appspot.com can help<br />
* finalize SRP questions (definition of M1, generation of a/b)<br />
* finalize proof-of-work/DoS-prevention details<br />
* decide how to rate-limit account-creation calls<br />
* confirm this is actually implementable inside Firefox (especially w.r.t. NSS and Android/Java crypto)<br />
<br />
= Creating The Account =<br />
<br />
The first act performed by a user is to create the account. They enter email+password into their browser, which then does the following steps:<br />
<br />
* decide upon stretching parameters (perhaps consulting the keyserver for recommendations, but imposing a minimum strength requirement). For now we used a fixed {firstPBKDF:20000, scrypt: {N:65536, r:8, p:1}, secondPBKDF:20000}.<br />
* randomly choose a 32-byte mainSalt (this should be unique, but is not secret)<br />
* choose the SRP group parameters (fixed: use the 2048-bit group described below)<br />
* perform key-stretching (as described below), derive stretchedPW and srpPW<br />
* randomly choose a 32-byte srpSalt (unique, but not secret)<br />
* create srpVerifier from srpPW and srpSalt (as described below)<br />
* deliver (email, stretchParams, mainSalt, srpParams, srpSalt) to the keyserver's "POST /account/create" API<br />
<br />
<br />
To limit abuse, the createAccount() should also require a fresh "createToken". This should be created by some other API, outside the scope of this document, that perhaps requires a CAPTCHA or something. createAccount() might also require a proof-of-work token, as described below.<br />
<br />
The server, when creating a new account, creates both kA and wrap(kB) as randomly-generated 256-bit (32-byte) strings. It stores these, along with all the remaining values, indexed by email, in the account table where they can be retrieved by getToken later.<br />
<br />
== Email Verification ==<br />
<br />
To prevent fixation attacks, we require new accounts to verify their configured recovery email address before letting them learn the generated keys or obtain a signed certificate. Nevertheless, we wish clients to forget the user's password while they wait for email verification to complete. To achieve this, clients can obtain a sessionToken before verification, but most APIs that require it will raise errors until verification is finished.<br />
<br />
The server will send email with a URL that contains a long random "verification code" in the "fragment" hash. This URL points to a static page with some javascript that submits the code to the "POST /account/recovery_methods/verify_code" API. The URL can be clicked by any browser (it is not bound to anything), and when the API is hit, the account is marked as verified.<br />
<br />
After the client submits /account/create, it performs the "/session/auth" login sequence below to obtain a sessionToken. It then polls the "GET /account/recovery_methods" (which requires a sessionToken but not account verification) until the user clicks the email link and the API reports verification is complete. Then the client uses "GET /account/keys" and "POST /certificate/sign", described below, to obtain kA, kB, and a signed certificate to talk to the storage server.<br />
<br />
= Login: Obtaining the authToken =<br />
<br />
To connect a browser to an existing account, we use the following login protocol to transform an email+password pair into a single-use authToken. We will use this in the next section to create a session, from which we can obtain encryption keys and signed certificates.<br />
<br />
This protocol starts by using key-stretching to transform the email+password into a "stretchedPW", then feeds this into an SRP protocol to get the authToken.<br />
<br />
[[File:PICL-IdPAuth-auth-start.png|IdP Auth Protocol]]<br />
<br />
This authToken can be used (once) to do one of the following:<br />
<br />
* /session/create: obtain a sessionToken (and keyFetchToken), which enables storage server access<br />
* /password/change: obtain an accountResetToken, to safely reset the account password<br />
* /account/delete: to delete the entire account<br />
<br />
The protocol is designed to enable parallelism between key-stretching and the initial network messages, to reduce the time it takes to connect a browser to the account. In total, the browser requires five messages in four roundtrips (1: /auth/start, 2: /auth/finish, 3: /session/create, 4: /account/keys and /certificate/sign in parallel) before it is ready to talk to the storage server.<br />
<br />
== /auth/start ==<br />
<br />
As soon as the user finishes typing in the email address, the client should send it in the "/auth/start" 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 loginSRPToken that is used to associate this request with the subsequent /auth/finish 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.<br />
<br />
To mitigate DoS abuse, /auth/start may also require a proof-of-work string, described below.<br />
<br />
== Client-Side Key Stretching ==<br />
<br />
"Key Stretching" is the practice of running a password through a computationally-expensive one-way function before using it for encryption or authentication. The goal is to make brute-force dictionary attacks more expensive, by raising the cost of testing each guess.<br />
<br />
To protect the user's class-B data against compromises of our keyserver, we perform this key stretching on the client, rather than on the server. We use the memory-hard "scrypt" function (pronounced "ess-crypt") for this purpose, as motivated by the attacker-cost studies in [[Identity/CryptoIdeas/01-PBKDF-scrypt]]. Slower (low-RAM) devices can still participate by outsourcing the scrypt step to an "scrypt helper". To provide at least minimal protection against a compromise of this helper, we sandwich the scrypt step between two PBKDF2 steps. The complete protocol looks like this:<br />
<br />
[[File:PICL-IdPAuth-stretch-KDF.png|Stretching KDF]]<br />
<br />
Our initial parameter choices for this stretching are to use 20000 iterations of PBKDF2 on each side (40k in all), and to run scrypt with N/r/p set to 64k/8/1. This requires roughly 60MB of RAM for about 1.3 seconds on a 1.8GHz Intel Atom linux box (it will run faster, but use the same memory, on more modern CPUs). We are still studying performance on various platforms to decide what parameters to use in V1: lowering them speeds up the login process, but reduces security (by reducing the cost of an dictionary attack).<br />
<br />
Since the stretching is expected to take a second or two, the client can optimistically start this process (using default parameters) before receiving the auth/start 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 "mainSalt" is added *after* the stretching, to enable this parallelism (at a tiny cost in security).<br />
<br />
After "stretchedPW" is derived, a second HKDF call is used to derive "srpPW" and "unwrapBKey" which will be used later.<br />
<br />
[[File:PICL-IdPAuth-main-KDF.png|masterKey KDF]]<br />
<br />
== SRP Protocol Details ==<br />
<br />
The PiCL client uses the SRP protocol (http://srp.stanford.edu/) to prove that it knows the (stretched) account password without revealing the actual password (or information enabling a brute-force attack) to the server or any eavesdroppers. <br />
<br />
SRP is somewhat underspecified. We use SRP-6a, with SHA256 as the hash, and the 2048-bit modulus defined in RFC 5053 Appendix A. We consistently zero-pad all string values to 256 bytes (2048 bits), and use H(A+B+S) as the key-confirmation message "M1". These details, plus the SRP design papers and RFCs 2945 and 5054, should be enough to build a compatible implementation. The diagrams below and the test vectors at the end of this page can be used to verify compatibility.<br />
<br />
The server should use Jed's SRP module from https://github.com/jedp/node-srp . The client might use SJCL (http://crypto.stanford.edu/sjcl/) or native code (NSS).<br />
<br />
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).<br />
<br />
Note that SRP-6a uses a "k" value which basically encodes the group being used ("N" and "g"). Since all PICL accounts use the same 2048-bit group, they will all use the same "k" value (not to be confused with the per-session shared-secret "K" key that emerges from the protocol). This group's "k" integer is (as a base-10 number):<br />
2590038599070950300691544216303772122846747035652616593381637186118123578112<br />
<br />
=== SRP Verifier Calculation ===<br />
<br />
When the client first creates the account, it must combine the account email address, the stretched password (srpPW), and a randomly-generated srpSalt, to compute the srpVerifier. The server will use this verifier later, to check whether or not the client really knows the password.<br />
<br />
If the server is compromised and an attacker learns the srpVerifier for a given account, they cannot use this to directly log in (the verifier is not "password-equivalent"), but it does allow them to perform an offline brute-force attack against the user's password. In this respect, it is similar to a traditional hashed password. We make these attacks somewhat more expensive by performing the client-side stretching described above, instead of using the raw user password in the SRP calculation.<br />
<br />
[[File:PICL-IdPAuth-SRP-Verifier.png|client-side SRP Verifier calculation]]<br />
<br />
Test vectors are provided at the end of this page. The protocol requires that several values ("a", "b", "srpSalt") are chosen randomly, but for illustrative purposes, the test vectors use carefully-crafted non-random values. For the user's sake, please ensure implementations use proper random values instead of simply copying the test vectors.<br />
<br />
The client sends srpSalt and srpVerifier to the server when it creates the account. It will also re-compute the 'x' value (as an integer) during sign-in. The server will convert the srpVerifier string back into an integer ('v') for use during its own sign-in calculations.<br />
<br />
=== SRP Server-side Sign-In Flow ===<br />
<br />
When the user connects a new device to their account, they use the /auth/start API to start the SRP protocol. This sends the account email address to the server. The server looks up the stored srpVerifier for this account, creates a random 'b' integer, performs some math to compute the "B" number, then converts B into a string known as "srpB". "srpB" is returned to the client, along with srpSalt and the key-stretching parameters. "b" and "srpB" are retained for the subsequent /auth/finish call. '''Note that it is critical that the "b" integer remain secret on the server.''' The server also allocates a random loginSRPToken to connect the two calls.<br />
<br />
[[File:PICL-IdPAuth-SRP-Server.png|server-side SRP]]<br />
<br />
Later, /auth/start will be called with the client's srpA string and its M1 key-confirmation string. srpA is combined with srpB to calculate the "u" integer. srpA is also turned into an integer and used to compute the shared-secret "S" integer. S is then used to compute the shared key "srpK", which is the output of the SRP process.<br />
<br />
=== SRP Client Calculation ===<br />
<br />
While the client is waiting for the response to /auth/start, it begins its key-stretching calculations. Everything else must wait until the response to /auth/start arrives, which includes the key-stretching parameters (which are retroactively confirmed), srpSalt, and the server's generated srpB value.<br />
<br />
[[File:PICL-IdPAuth-SRP-Client.png|client-side SRP]]<br />
<br />
Once the client knows srpSalt, it computes the same "x" integer as it did in the middle of the srpVerifier calculation. It also converts srpB into an integer named "B". Then it creates a random "a" integer, uses it to compute the string "srpA", then combines srpA with the server's srpB to compute the "u" integer. It then combines the static "k", the password-derived "x", the combined "u", and the server's "B", together with some magic math, to derive the "S" integer. If everything went well, the client will compute the same "S" value as the server did. If not (the password was wrong, or the client is talking to a fake server that doesn't really know srpVerifier), then the two "S" values will not match.<br />
<br />
('''Again, it is critical that the client keep its "a" and "x" integers secret, both during and after the protocol run.''')<br />
<br />
To safely tell if the "S" values match, both client and server combine srpA, srpB, and their (independently) generated "S" strings to form a string named "M1". The client sends M1 (along with srpA) in the /session/auth/finish message. The server compares the client's copy of M1 against its own. If they match, the client knew the password and the server can safely respond with the encrypted account data. If they do not match, the client (or a man-in-the-middle attacker) did not know the password, and the client should increment a counter that can trigger defenses against online guessing attacks. The server must then return an error to the client, and '''not''' use or reveal srpK (or the correct M1) in any way.<br />
<br />
Both client and server also hash "S" into "srpK". This is the shared session key, from which specific message encryption and MAC keys are derived (as described below).<br />
<br />
=== SRP Notes ===<br />
<br />
The SRP "g" (generator) and "N" (prime modulus) should use the 2048-bit value from RFC 5054 Appendix A. 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.<br />
<br />
There are several places in SRP where integers (usually in the range 1..N-1) are converted into bytestrings, either for transmission over a wire, or to be passed into a hash function. The SRP spec is somewhat ambiguous about padding here: if the integer happens to be less than 2^2040, the simplest toString() approach will yield a '''255''' byte string, not a 256 byte string. PiCL consistently uses padding, so compatible implementations must prepend one or more NUL bytes to these short strings before transmission or hashing. The test vectors below were brute-forced to ensure that "srpVerifier", "srpA", "srpB", and "S" all wind up with leading zeros, to exercise the padding code in compatible implementations. If you are having problems getting your code to match these results, add some assertions to test that the stringified integers being put into hashes are exactly 256 bytes long.<br />
<br />
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 (/auth/finish).<br />
<br />
The server receives "A" in /auth/finish, 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 keyFetchToken+sessionToken as described below, returning the encrypted/MACed bundle in the response to /auth/finish.<br />
<br />
On the server, it is critical to reject an "A" value that is 0, or some other multiple of N. If the server does not check this, anybody can trivially sign in to any account without knowing the password. Likewise, it is critical for the client to reject a "B" value where B%N==0. If the client does not check this, the server (or an attacker pretending to be the server) will get a value that can be used in an offline brute-force search for the user's password.<br />
<br />
Outstanding crypto questions:<br />
<br />
* How exactly should the "a" and "b" integers be generated? The issue is of how much bias SRP can tolerate. Ideally these integers are uniformly distributed from 1 to N-1 (inclusive). The only way to obtain a purely uniform distribution from a source of random bytes is try-try-again: pick a (integral-number-of-bytes) number, compare it to the desired range, try again if it falls outside the range. If you get unlucky, this can take a lot of guesses, depending upon how close the range is to a power of two. ECDSA implementations tend to pick a number twice as long as the modulus and then modulo it down (rand(2^4096) % N), which yields a tiny fraction of a bit of bias. The cheaper approach is to do the same with a number of equal length (rand(2^2048) % N), which imposes more bias. Some SRP implementations appear to be satisfied by rand(2^256). We need more review here.<br />
* The original SRP papers defined M1=H(A+B+S) as we use here, but other implementation (in particular RFC2945) uses a curious construct that includes the username, the salt, and an odd XOR combination of N and g. We need to decide what to use for M1.<br />
<br />
== /auth/finish ==<br />
<br />
The client-side SRP calculation results in two values that are sent to the server in the /auth/finish 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.<br />
<br />
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.<br />
<br />
The server then allocates a single-use 32-byte random token named "authToken". It encrypts the token with the session key, and returns a success message with the encrypted bundle.<br />
<br />
All tokens have an associated tokenID, described below. The server needs to maintain a table that maps the tokenID to the token itself, so it can derive other values from the token later. The tokens are also associated with a specific account, so later API requests do not specify an email address or account ID.<br />
<br />
== Decrypting the /auth/finish Response ==<br />
<br />
The SRP session key ("srpK") is used to derive two other keys: respHMACkey and respXORkey.<br />
<br />
[[File:PICL-IdPAuth-encrypt-authToken.png|Decrypting the authToken]]<br />
<br />
The respXORkey is used to encrypt the authToken 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.<br />
<br />
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 returns the authToken value.<br />
<br />
= Creating a Session =<br />
<br />
For login, the single-use authToken is spent on a call to /session/create .<br />
This allocates two new (random 32-byte) tokens: a long-lived "sessionToken", and a single-use "keyFetchToken". The /session/create call returns an encrypted bundle containing the two tokens.<br />
<br />
[[File:PICL-IdPAuth-encrypt-sessionToken.png|Decrypting the sessionToken and keyFetchToken]]<br />
<br />
For calls which accept an authToken, the client uses authToken to derive three values:<br />
<br />
* tokenID<br />
* reqHMACkey<br />
* requestKey<br />
<br />
<br />
The client uses tokenID and reqHMACkey for a HAWK (https://github.com/hueniverse/hawk/) request to the "POST /session/create" API, using tokenID as "credentials.id" and reqHMACkey as "credentials.key". The server uses tokenID to look up the corresponding token, then derives reqHMACkey to validate the request.<br />
<br />
Each authToken-using call then derives additional API-specific values from requestKey. /session/create uses two derived values:<br />
<br />
* respHMACkey<br />
* respXORkey<br />
<br />
<br />
When the server receives a valid /session/create request, it allocates sessionToken and keyFetchToken, concatenates them, encrypts the pair by XORing it with the derived respXORkey, and attaches a MAC generated with respHMACkey. The encrypted MACed bundle is returned to the client.<br />
<br />
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 keyFetchToken and sessionToken values.<br />
<br />
Each authToken is single-use: once a successful request has been made with it, the authToken and its corresponding ID is removed from the server's memory, and subsequent attempts to use it will return a "no such token" error. The token is consumed even if the request fails (e.g. the MAC did not match).<br />
<br />
The authToken can be used (once) by multiple APIs. The server only needs to maintain one table mapping tokenID to authToken, because all these APIs share the same method of deriving a tokenID and reqHMACkey from the authToken.<br />
<br />
When a HAWK request appears, it should look up the tokenID in this table, retrieve the authToken, recompute reqHMACkey and requestKey, validate the request, delete the table entry, then hand the HTTP request and requestKey to the specific API handler. That handler should derive the API-specific values (respHMACkey, respXORkey, etc) to process the request or construct the response.<br />
<br />
The server can support multiple sessions per account (typically one per client device, plus perhaps others for account-management portals). There can also be multiple outstanding keyFetchTokens. The sessionToken lasts forever (until revoked by a password change or explicit revocation command), and can be used an unlimited number of times. The keyFetchToken expires after 60 seconds, and is single-use.<br />
<br />
= Obtaining keys kA and kB =<br />
<br />
Clients which have exchanged an authToken for either a sessionToken or an accountResetToken will also receive a keyFetchToken. This single-use token allows the client to retrieve kA and wrap(kB), which enables the client to encrypt and decrypt browser data (bookmarks, open-tabs, etc) correctly. As above, the keyFetchToken is used to derive tokenID, reqHMACkey, respHMACkey, and respXORkey, which are used in a HAWK request to the "GET /account/keys" API.<br />
<br />
The server pulls kA and wrap(kB) from the account table, concatenates them, encrypts the pair by XORing it with the derived respXORkey, and attaches a MAC generated with respHMACkey.<br />
<br />
[[File:PICL-IdPAuth-keys-server.png|keyFetchToken: server encrypts keys]]<br />
<br />
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 and wrap(kB) values.<br />
<br />
[[File:PICL-IdPAuth-keys-client.png|keyFetchToken: client decrypts keys]]<br />
<br />
Finally, the server-provided wrap(kB) value is simply XORed with the password-derived unwrapBKey (both are 32-byte strings) to obtain kB. There is no MAC on wrap(kB).<br />
<br />
[[File:PICL-IdPAuth-key-unwrap.png|unwrapping kB]]<br />
<br />
"kA" and "kB" enable the browser to encrypt/decrypt synchronized data records. They will be used to derive separate encryption and HMAC keys for each data collection (bookmarks, form-fill data, saved-password, open-tabs, etc). This will allow the user to share some data, but not everything, with a third party. The client may intentionally forget kA and kB (only retaining the derived keys) to reduce the power available to someone who steals their device.<br />
<br />
Note that /account/keys will not succeed until the account's email address has been verified. Also note that each keyFetchToken is single-use and short-lived. The token is consumed even if the request fails (e.g. the MAC does not match).<br />
<br />
= Signing Certificates =<br />
<br />
The sessionToken is used to derive two values:<br />
<br />
* tokenID<br />
* request HMAC key<br />
<br />
<br />
[[File:PICL-IdPAuth-use-session.png|Using the sessionToken, signing certificates]]<br />
<br />
The requestHMACkey is used in a HAWK request to provide integrity over many APIs, including /certificate/sign. requestHMACkey 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.<br />
<br />
For /certificate/sign, it is critical to enable payload verification by setting options.payload=true (on both client and server). Otherwise a man-in-the-middle could submit their own public key, get it signed, and then delete the user's data on the storage servers.<br />
<br />
The following keyserver APIs require a HAWK-protected request that uses the sessionToken. In addition, some require that the account be in the "verified" state:<br />
<br />
* GET /account/devices<br />
* POST /session/destroy<br />
* GET /recovery_email/status<br />
* POST /recovery_email/resend_code<br />
* POST /certificate/sign (requires "verified" account)<br />
<br />
= Resetting The Account =<br />
<br />
The account may be reset in two circumstances: when the user changes their password, and when the user forgets their password. In both cases, the client first obtains an "accountResetToken". This token is then used to change the SRP Verifier and either reset or replace the wrap(kB) value.<br />
<br />
== Changing the Password ==<br />
<br />
When the user wishes to change their password (i.e. they still know the old password), they first use the /auth/start+/auth/finish SRP protocol safely obtain an "authToken". They they use the "/password/change/start" API to exchange the authToken for an accountResetToken and a keyFetchToken.<br />
<br />
[[File:PICL-IdPAuth-encrypt-passwordChange.png|Server encrypts passwordChange response]]<br />
<br />
The accountResetToken will be used below to set the new password safely. The keyFetchToken should be used first, to obtain kB, so the subsequent account reset can replace wrap(kB) with a new value. This allows the password-changing client to retain their class-B data.<br />
<br />
Requiring an authToken proves that the user has provided the correct account password recently. When the account is reset, all active sessions and tokens will be cancelled (disconnecting all devices from the account). The client should immediately establish a new session as described above.<br />
<br />
This API is only used when the user knows their old password: if they have forgotten the password, use the "/password/forgot" APIs below.<br />
<br />
== Handling a Forgotten Password ==<br />
<br />
When the user has forgotten their password, they can use one of their "recovery methods" to obtain an accountResetToken. For now, this means we send a random code to the email address associated with their account. The user must copy this code from the email into their client, whereupon the client will get an accountResetToken that can be delivered to the API below.<br />
<br />
Note that, since the forgotten-password client never learns kB, any class-B data will be lost. This is necessary to protect class-B data from attackers who can read the users's email but do not know the account password (including those who compromise the IdP and the keyserver itself). When using /account/reset below, they will set wrap(kB) to a string of all zeros, which means the server should generate a new random wrap(kB) (just as it does during account creation).<br />
<br />
The /password/forgot/send_code API is used to ask the server to send a recovery code. This takes a recovery method, which for now is just an email address. This API is unauthenticated (after all, the user who has forgotten their password knows nothing but their email address). The server marks the corresponding account as "pending recovery", allocates a random forgotPasswordToken for the account, creates a recovery code, and sends the code (with instructions) via email. The API returns forgotPasswordToken to the client.<br />
<br />
The user must copy the recovery code into the same browser where they started the process. The client then submits the code to /password/forgot/verify_code along with the forgotPasswordToken they received. If they match, the server allocates a accountResetToken and returns it to the client. If they do not match, the server increments a counter (which is used to decide if an online guessing attack is happening).<br />
<br />
forgotPasswordToken can be used three times before it is exhausted. If the user guesses incorrectly this often, the client must call send_code again to get a new token and code. Each account has at most one token+code active at a time.<br />
<br />
The recovery code is initially a random 8-digit decimal number. If an attacker tries to sign in as someone else, hits the "forgot my password" button, then submits a guess to /password/forgot/verify_code, they will have a 1-in-100-million chance of success. If the server detects too many wrong guesses, it should increase the length of new codes. Another defensive technique is to require that users click an email link before being given the code: the server is told when the link is clicked, so the code will not be enabled until the email has been read. It remains to be seen whether this will be sufficient.<br />
<br />
The exact thresholds are TBD, but a nominal goal is to keep the chances of any attack succeeding to below 1-in-a-million per year. To achieve this, we can tolerate 100 verify_code failures in a single year before we must increase the length of the code.<br />
<br />
== Using accountResetToken ==<br />
<br />
An accountResetToken, obtained through either of the methods above, is used to invoke the /account/reset API. If the request is accepted, the server replaces the account password with a new one, updates the wrap(kB) value, cancels all active sessions and tokens (disconnecting all devices from the account), and sends a "your password has been changed" email to the user.<br />
<br />
If the client knew the old password, it can supply a new wrap(kB) value that will yield the same kB key as before, so no data will be lost. If the client did *not* know the old password, then it will supply a wrap(kB) of all-zeros, which tells the server to generate a new random wrap(kB) (just like during account creation), which means the class-B data will be lost.<br />
<br />
The client puts their new password through the same stretching procedure as described in the new-account section above, resulting in a new srpVerifier and unwrapBKey. If they knew the old kB, they XOR it with the new unwrapBKey to obtain a new wrap(kB).<br />
<br />
/account/reset needs request confidentiality, since the arguments include the newly wrapped kB value and the new SRP verifier, both of which enable a brute-force attack against the password. HAWK provides request integrity. The response is a single "ok" or "fail", conveyed by the HTTP headers, so we do not require response confidentiality, and can live without response integrity.<br />
<br />
So the single-use resetToken is used to derive three values:<br />
<br />
* tokenID<br />
* request HMAC key<br />
* request XOR key<br />
<br />
<br />
[[File:PICL-IdPAuth-encrypt-resetAccount.png|Client encrypts resetAccount request]]<br />
<br />
The request data will contain wrap(kB) and the new SRP verifier, concatenated together. Since we always pad the SRP verifier to the full (256-byte) group length, both pieces are fixed-length. We generate enough reqXORkey bytes to cover both values.<br />
<br />
The request data is XORed with requestXORkey, then delivered in the body of a HAWK request that uses tokenID as credentials.id and requestHMACkey as credentials.key . Note: it is critical to include the request body in the HAWK integrity check (options.payload=true, on both client and server), otherwise a man-in-the-middle could substitute their own SRP verifier, giving them control over the account (access to the user's class-A data, and a brute-force attack on their password).<br />
<br />
The client submits other values in the same request:<br />
<br />
* stretchParams<br />
* mainKDFSalt<br />
* srpSalt<br />
<br />
<br />
These values do not require confidentiality, so are not included in the encrypted bundle. They are still protected by the HAWK integrity check. Note that the server should assert that both salts are different than the previously stored values.<br />
<br />
After using /account/reset, clients should immediately perform the login protocol from above. If the old password was forgotten, this is necessary to fetch kA. In either case, a new sessionToken is required, since old sessions and tokens are revoked by /account/reset. Clients should retain the new srpPassword value during this process to avoid needing to run the lengthy key-stretching routine a second time.<br />
<br />
= Deleting The Account =<br />
<br />
When the user wishes to completely delete their account, the browser needs to perform two actions:<br />
<br />
* contact the storage servers and delete all records and collections<br />
* contact the keyserver and delete the account information<br />
<br />
The user should be prompted for their password as confirmation (i.e. a browser in the normal attached-and-synchronizing state should not be able to erase the account information: it must acquire a new authToken first).<br />
<br />
The device then obtains an authToken as described above, then spends it on a HAWK-protected request to the /account/destroy endpoint. This request contains no body and returns only a success code.<br />
<br />
[[File:PICL-IdPAuth-deleteAccount.png|Deleting the Account]]<br />
<br />
= Crypto Notes =<br />
<br />
Strong entropy is needed in the following places:<br />
<br />
* (client) creation of private "a" value inside SRP<br />
* (server) initial creation of kA and wrap(kB)<br />
* (server) creation of private "B" value inside SRP<br />
* (server) creation of signToken and resetToken<br />
<br />
<br />
On the server, code should get entropy from /dev/urandom via a function that uses it, like "crypto.randomBytes()" in node.js or "os.urandom()" in python. On the client, code should combine local entropy with some fetched from the keyserver via getEntropy(), to guard against failures in the local entropy pool. Something like HKDF(IKM=localEntropy+remoteEntropy, salt="", info=KW("mergeEntropy")).<br />
<br />
An HKDF-based stream cipher is used to protect the response for getToken2(), and the request for resetAccount(). HKDF is used to create a number of random bytes equal to the length of the message, then these are XORed with the plaintext to produce the ciphertext. An HMAC is then computed from the ciphertext, to protect the integrity of the message.<br />
<br />
HKDF, like any KDF, is defined to produce output that is indistinguishable from random data ("The HKDF Scheme", http://eprint.iacr.org/2010/264.pdf , by Hugo Krawczyk, section 3). XORing a plaintext with a random keystream to produce ciphertext is a simple and secure approach to data encryption, epitomized by AES-CTR or a stream cipher (http://cr.yp.to/snuffle/design.pdf). HKDF is not the fastest way to generate such a keystream, but it is safe, easy to specify, and easy to implement (just HMAC and XOR).<br />
<br />
Each keystream must be unique. SRP is defined to produce a random session key for each run (as long as at least one of the sides provides a random ephemeral key). We define resetToken to be a single-use randomly-generated value. Hence our two HKDF-XOR keystreams will be unique.<br />
<br />
A slightly more-traditional alternative would be to use AES-CTR (with the same HMAC-SHA256 used here), with a randomly-generated IV. This is equally secure, but requires implementors to obtain an AES library (with CTR mode, which does not seem to be universal). An even more traditional technique would be AES-CBC, which introduces the need for padding and a way to specify the length of the plaintext. The additional specification complexity, plus the library load, leads me to prefer HKDF+XOR.<br />
<br />
kB is equal to the XOR of wrapKey (which is a deterministic function of the user's email address, password, mainSalt, and the stretching parameters) and the server's randomly-generated wrap(kB) value, making kB a random value too. Using XOR as a wrapping function allows us to avoid sending kB or wrap(kB) in the initial createAccount arguments, which are not protected by SRP, and thus would enable an eavesdropper to mount a dictionary attack on the password (using wrap(kB) as their oracle).<br />
<br />
Likewise, allowing the server to generate kA avoids exposure during createAccount. The only point of vulnerability is a forgotten-password resetAccount() message, if an eavesdropper can learn the resetToken. In this case, the attacker might either use the resetToken themselves (then use signToken to learn kA and wrap(kB)), or passively decrypt the resetAccount() arguments to retrieve just wrap(kB). Given wrap(kB) and some encrypted browser data, the attacker can guess passwords (and derive kB, etc) until the data decrypts properly.<br />
<br />
To make this technique safe, any time kB or the password is changed, the mainSalt should be changed too. Otherwise knowledge of both wrap(old-kB) and old-kB would reveal wrapKey, making it easy to deduce the new kB. Changing mainSalt causes wrapKey to change too, preventing this.<br />
<br />
mainSalt is incorporated at the end of the stretching process to allow it to proceed in parallel with the getToken1() call that retrieves the salt. The inputs to the lengthy stretch come entirely from the user (email and password) or are optimistically (stretching parameters). This speedup seems more important than the minor security benefit of including the salt at the beginning of the stretch.<br />
<br />
There is no MAC on wrap(kB). If the keyserver chooses to deliver a bogus wrap(kB) or kA, the client will discover the problem a moment later when it talks to a storage server and attempts to retrieve data from an unrecognized collection-ID (since we intend to derive collection-IDs from the key used to encrypt their data, which will be derived from kA or kB as appropriate). It might be useful to add a checksum to kA and wrap(kB) to detect accidental corruption (e.g. store and deliver kA+SHA256(kA)), but this doesn't protect against intentional changes. We omit this checksum for now, assuming that disks will be reliable enough to let us never experience such failures.<br />
<br />
We use scrypt without a per-user salt. This is safe because the "password" input to scrypt is already diversified by the user's email address. The intention is to allow the scrypt-helper to run on anonymous data, so that an attacker who compromises this helper cannot easily learn the email addresses of the partially-stretched K1 values that it is receiving, confounding their attack.<br />
<br />
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. <br />
For /certificate/sign, we do not need request confidentiality or response confidentiality, since the client's pubkey and the resulting certificate will both be exposed over a similar SSL connection to the storage server later. And it is sufficient to rely on the response integrity provided by SSL, since the client can verify the returned certificate for itself. For the other keyserver APIs protected by HAWK, these properties are either unnecessary, or are provided by additional mechanisms.<br />
<br />
= Proof-Of-Work =<br />
<br />
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(). The server can control how much work is required.<br />
<br />
The getToken1() call looks for a "X-PiCL-PoW:" HTTP header. Most of the time, clients don't supply this header. But if the server responds to the getToken1() call with an error that indicates PoW is required, clients must create a valid PoW string and include it as the value of an "X-PiCL-PoW:" header in their next call to getToken1().<br />
<br />
The server's error message includes two parameters. The first is a "prefix string": the client's PoW string is required to begin with this prefix. The second is a "threshold hash". SHA256(PoWString) is required to be lexicographically earlier than the thresholdHashString (i.e. the numerical value of its hash must be closer to zero than the threshold). The client is expected to concatenate the prefix with a counter, then repeatedly increment the counter and hash the result until they meet the threshold, then re-submit their getToken1() request with the combined prefix+counter string in the header. If the client has spent more than e.g. 10 seconds doing this, the client should probably help the user cancel the operation and try again.<br />
<br />
When a server is under a DoS attack (either via some manual configuration tool or sensed automatically), it should start requiring valid unique X-PiCL-PoW headers. The server should initially require very little work, by using a threshold hash with just a few leading zero bits. If this is insufficient to reduce the attack volume, the threshold should be lowered, requiring even more work (from both the attacker and legitimate clients).<br />
<br />
The server should create a prefix string that contains a parseable timestamp and a random nonce (e.g. "%d-%d-" % (int(time.time()), b32encode(os.urandom(8)))). The server should also decide on a cutoff time (perhaps ten minutes ago). Each server must then maintain a table of "old PoW strings" to prevent replay attacks (these do not need to be shared among all servers: an in-RAM cache is fine).<br />
<br />
When the server receives a proposed PoW string, it first splits off the leading timestamp, and if the timestamp is older than the cutoff time, it rejects the string (either by dropping the connection, or returning a new "PoW required" error if it's feeling nice). Then it hashes the whole string and compares it against the threshold, rejecting those which fail to meet the threshold. Finally, for strings that pass the hash threshold, it checks the "old strings" table, and rejects any that appear on that list.<br />
<br />
If the PoW string makes it past all these checks, the server should add the string to the "old strings" table, then accept the request (i.e. compute an srpB value and add a session-id table entry for the request).<br />
<br />
The old-strings table check should be optimized to reject present strings quickly (i.e. if we are under attack, we should expect to see lots of duplicates of the same string, and must minimize the work we do when this occurs).<br />
<br />
The server can remove values from the old-strings table that have timestamps older than the cutoff time. The server can also discard values at other times (to avoid consuming too much memory), without losing anything but protection against resource consumption.<br />
<br />
Other notes:<br />
<br />
The server-side code for this can be deferred until we care to have a response to a DoS attack. However the client-side code for this must be present from day one, otherwise we won't be able to turn on the defense later without fear of disabling legitimate old clients.<br />
<br />
The server should perform as little work as possible before rejecting a token. Every extra CPU cycle it spends in this path is increasing the DoS attack amplification factor.<br />
<br />
The nonce in the prefix string exists to make sure that two successive clients get different prefixes, and thus do not come up with the same counter value (and inadvertently create identical strings, looking like a replay attack). If this proved annoying or expensive, we could instead obligate clients to produce their own nonce.<br />
<br />
TBD: Is this worth it? Should the PoW string go into an HTTP header? (I want it to be cheap to extract, and not clutter logs). Should the error response be a distinctive HTTP error code so our monitoring tools can easily count them? We can also use this feature to slow down online guessing attacks (i.e. trigger it either when getToken1 is called too much or when getToken2 produces too many errors). Since getToken1() includes an email address, we could also requires PoWs for some addresses (e.g. those we know to be under attack) but not others.<br />
<br />
= Glossary =<br />
<br />
This defines some of the jargon we've developed for this protocol.<br />
<br />
* data classes: each type of browser data (bookmarks, passwords, history, etc) can be assigned, by the user, to either class-A or class-B<br />
* class-A: data assigned to this class can be recovered, even if the user forgets their password, by proving control over an email address and resetting the account. It can also be read by Mozilla (since it runs the keyserver and knows kA), or by the user's IdP (by resetting the account without the user's permission).<br />
* class-B: data in this class cannot be recovered if the password is forgotten. It cannot be read by the IdP. Mozilla (via the keyserver) cannot read this data, but can attempt a brute-force dictionary attack against the password.<br />
* kA: the master key for data stored as "class-A", a 32-byte binary string. Individual encryption keys for different datatypes are derived from kA.<br />
* kB: the master key for data stored as "class-B", a 32-byte binary string.<br />
* wrap(kB): an encrypted copy of kB. The keyserver stores wrap(kB) and never sees kB itself. The client (browser) uses a key derived from the user's password to decrypt wrap(kB), obtaining the real kB.<br />
* sessionToken: a long-lived per-device token which allows the device to obtained signed BrowserID certificates for the account's identity (GUID@picl-something.org). This token remains valid until the user revokes it (either by changing their password, or triggering some kind of "revoke a specific device" or "revoke all devices" function).<br />
<br />
= Test Vectors =<br />
<br />
The following example uses a non-ASCII email address of "andré@example.org" (with an accented "e", UTF8 encoding is 616e6472c3a9406578616d706c652e6f7267) and a non-ascii password of "pässwörd" (with accents on "a" and "o", UTF8 encoding is 70c3a4737377c3b67264).<br />
<br />
These test vectors were produced by the python code in https://github.com/warner/picl-spec-crypto . The diagrams may lag behind the latest version of that code.<br />
<br />
== stretch-KDF ==<br />
<br />
email:<br />
616e6472c3a94065<br />
78616d706c652e6f<br />
7267<br />
<br />
password:<br />
70c3a4737377c3b6<br />
7264<br />
<br />
K1 (scrypt input):<br />
f84913e3d8e6d624<br />
689d0a3e9678ac8d<br />
cc79d2c2f3d96414<br />
88cd9d6ef6cd83dd<br />
<br />
K2 (scrypt output):<br />
5b82f146a6412692<br />
3e4167a0350bb181<br />
feba61f63cb17140<br />
12b19cb0be0119c5<br />
<br />
stretchedPW:<br />
c16d46c31bee242c<br />
b31f916e9e38d60b<br />
76431d3f5304549c<br />
c75ae4bc20c7108c<br />
<br />
== main-KDF ==<br />
<br />
mainSalt (normally random):<br />
00f0000000000000<br />
0000000000000000<br />
0000000000000000<br />
000000000000034d<br />
<br />
srpPW:<br />
00f9b71800ab5337<br />
d51177d8fbc682a3<br />
653fa6dae5b87628<br />
eeec43a18af59a9d<br />
<br />
unwrapBKey:<br />
6ea660be9c89ec35<br />
5397f89afb282ea0<br />
bf21095760c8c500<br />
9bbcc894155bbe2a<br />
<br />
internal x (base 10):<br />
8192518690918<br />
99580124814080709381476194749939<br />
03899664126296984459627523279550<br />
<br />
internal x (hex):<br />
b5200337cc3f3f92<br />
6cdddae0b2d31029<br />
c069936a844aff58<br />
779a545be89d0abe<br />
<br />
v (verifier as number) (base 10):<br />
114649<br />
57230405843056840989945621595830<br />
71784395917725741221739574165799<br />
54316134303691657140298181419198<br />
87853709633756255809680435884948<br />
69849281177012209169281795507853<br />
57610332070005048463659745521969<br />
83218225819721112680718485091921<br />
64608360806562626442477160609654<br />
43167308814558974899899506977051<br />
96721477608178869100211706638584<br />
53875100985456239693728258285562<br />
04889672594983678412848291529879<br />
88548996842770025110751388952323<br />
22170663943486107183421205517476<br />
84831590615660554713667726412525<br />
73641352721966728239512914666806<br />
49625530438034148797508015907639<br />
67594925530663571631035463732161<br />
30193328802116982288883318596822<br />
<br />
== SRP Verifier ==<br />
<br />
k (base 10):<br />
259003859907<br />
09503006915442163037721228467470<br />
35652616593381637186118123578112<br />
<br />
srpSalt (normally random):<br />
00f1000000000000<br />
0000000000000000<br />
0000000000000000<br />
0000000000000179<br />
<br />
srpVerifier:<br />
00173ffa0263e63c cfd6791b8ee2a40f<br />
048ec94cd95aa8a3 125726f9805e0c82<br />
83c658dc0b607fbb 25db68e68e93f265<br />
8483049c68af7e82 14c49fde2712a775<br />
b63e545160d64b00 189a86708c69657d<br />
a7a1678eda0cd79f 86b8560ebdb1ffc2<br />
21db360eab901d64 3a75bf1205070a57<br />
91230ae56466b8c3 c1eb656e19b794f1<br />
ea0d2a077b3a7553 50208ea0118fec8c<br />
4b2ec344a05c66ae 1449b32609ca7189<br />
451c259d65bd15b3 4d8729afdb5faff8<br />
af1f3437bbdc0c3d 0b069a8ab2a959c9<br />
0c5a43d42082c774 90f3afcc10ef5648<br />
625c0605cdaace6c 6fdc9e9a7e6635d6<br />
19f50af773452247 0502cab26a52a198<br />
f5b00a2798589165 07b0b4e9ef9524d6<br />
<br />
== SRP B ==<br />
<br />
private b (normally random) (base 10):<br />
1198277<br />
66042000957856349411550091527197<br />
08125226960544768993643095227800<br />
29105361555030352774505625606029<br />
71778328003253459733139844872578<br />
33596964143417216389157582554932<br />
02841499373672188315534280693274<br />
23189198736863575142046053414928<br />
39548731879043135718309539706489<br />
29157321423483527294767988835942<br />
53343430842313006332606344714480<br />
99439808861069316482621424231409<br />
08830704769167700098392968117727<br />
43420990997238759832829219109897<br />
32876428831985487823417312772399<br />
92628295469389578458363237146486<br />
38545526799188280210660508721582<br />
00403102624831815596140094933216<br />
29832845626116777080504444704039<br />
04739431335617585333671378812943<br />
<br />
private b (hex):<br />
00f3000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 000000000000000f<br />
<br />
transmitted srpB:<br />
0022ce5a7b9d8127 7172caa20b0f1efb<br />
4643b3becc535664 73959b07b790d3c3<br />
f08650d5531c19ad 30ebb67bdb481d1d<br />
9cf61bf272f84398 48fdda58a4e6abc5<br />
abb2ac496da5098d 5cbf90e29b4b110e<br />
4e2c033c70af7392 5fa37457ee13ea3e<br />
8fde4ab516dff1c2 ae8e57a6b264fb9d<br />
b637eeeae9b5e43d faba9b329d3b8770<br />
ce89888709e02627 0e474eef822436e6<br />
397562f284778673 a1a7bc12b6883d1c<br />
21fbc27ffb3dbeb8 5efda279a69a1941<br />
4969113f10451603 065f0a0126666456<br />
51dde44a52f4d8de 113e2131321df1bf<br />
4369d2585364f9e5 36c39a4dce33221b<br />
e57d50ddccb4384e 3612bbfd03a268a3<br />
6e4f7e01de651401 e108cc247db50392<br />
<br />
== SRP A ==<br />
<br />
private a (normally random) (base 10):<br />
1193346<br />
47663227291363113405741243413916<br />
43482736314616601220006703889414<br />
28162541137108417166380088052095<br />
43910927476491099816542561560345<br />
50331133015255005622124012256352<br />
06121987030570656676375703406470<br />
63422988042473190059156975005813<br />
46381864669664357382020200036915<br />
26156674010218162984912976536206<br />
14440782978764393137821956464627<br />
16314542157937343986808167341567<br />
89864323268060014089757606109012<br />
50649711198896213496068605039486<br />
22864591676298304745954690086093<br />
75374681084741884719851454277570<br />
80362211874088739962880012800917<br />
05751238004976540634839106888223<br />
63866455314898189520502368799907<br />
19946264951520393624479315579863<br />
<br />
private a (hex):<br />
00f2000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 000000000000d3d7<br />
<br />
transmitted srpA:<br />
007da76cb7e77af5 ab61f334dbd5a958<br />
513afcdf0f47ab99 271fc5f7860fe213<br />
2e5802ca79d2e5c0 64bb80a38ee08771<br />
c98a937696698d87 8d78571568c98a1c<br />
40cc6e7cb101988a 2f9ba3d65679027d<br />
4d9068cb8aad6ebf f0101bab6d52b5fd<br />
fa81d2ed48bba119 d4ecdb7f3f478bd2<br />
36d5749f2275e948 4f2d0a9259d05e49<br />
d78a23dd26c60bfb a04fd346e5146469<br />
a8c3f010a627be81 c58ded1caaef2363<br />
635a45f97ca0d895 cc92ace1d09a99d6<br />
beb6b0dc0829535c 857a419e834db128<br />
64cd6ee8a843563b 0240520ff0195735<br />
cd9d316842d5d3f8 ef7209a0bb4b54ad<br />
7374d73e79be2c39 75632de562c59647<br />
0bb27bad79c3e2fc ddf194e1666cb9fc<br />
<br />
== SRP key-agreement ==<br />
<br />
u:<br />
b284aa1064e87751 50da6b5e2147b47c<br />
a7df505bed94a6f4 bb2ad873332ad732<br />
<br />
S:<br />
0092aaf0f527906a a5e8601f5d707907<br />
a03137e1b601e04b 5a1deb02a981f4be<br />
037b39829a27dba5 0f1b27545ff2e287<br />
29c2b79dcbdd32c9 d6b20d340affab91<br />
a626a8075806c26f e39df91d0ad979f9<br />
b2ee8aad1bc783e7 097407b63bfe58d9<br />
118b9b0b2a7c5c4c debaf8e9a460f4bf<br />
6247b0da34b760a5 9fac891757ddedca<br />
f08eed823b090586 c63009b2d740cc9f<br />
5397be89a2c32cdc fe6d6251ce11e44e<br />
6ecbdd9b6d93f30e 90896d2527564c7e<br />
b9ff70aa91acc0ba c1740a11cd184ffb<br />
989554ab58117c21 96b353d70c356160<br />
100ef5f4c28d19f6 e59ea2508e8e8aac<br />
6001497c27f362ed bafb25e0f045bfdf<br />
9fb02db9c908f103 40a639fe84c31b27<br />
<br />
M1:<br />
27949ec1e0f16256<br />
33436865edb037e2<br />
3eb6bf5cb91873f2<br />
a2729373c2039008<br />
<br />
srpK:<br />
e68fd0112bfa31dc<br />
ffc8e9c96a1cbadb<br />
4c3145978ff35c73<br />
e5bf8d30bbc7499a<br />
<br />
== /auth ==<br />
<br />
srpK:<br />
e68fd0112bfa31dc<br />
ffc8e9c96a1cbadb<br />
4c3145978ff35c73<br />
e5bf8d30bbc7499a<br />
<br />
respHMACkey:<br />
6584613597ef012f<br />
f1752b7869f01d03<br />
c72547a7b7199681<br />
531d9df1991edf23<br />
<br />
respXORkey:<br />
455835926ae37a1b<br />
627bd16affbeeab6<br />
27ecc737121826ca<br />
4a2bac2c100bf417<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
plaintext:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
ciphertext:<br />
253957f10e861c7c<br />
0a12bb0193d384d9<br />
579db544666d50bd<br />
3252d6576c768a68<br />
<br />
MAC:<br />
a98c87f5769ab4cc<br />
ca3df863faeb217e<br />
b16ddc29d712b301<br />
12b446324ee806d6<br />
<br />
response:<br />
253957f10e861c7c<br />
0a12bb0193d384d9<br />
579db544666d50bd<br />
3252d6576c768a68<br />
a98c87f5769ab4cc<br />
ca3df863faeb217e<br />
b16ddc29d712b301<br />
12b446324ee806d6<br />
<br />
== authtoken ==<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
tokenID (authToken):<br />
9a39818e3bbe6132<br />
38c9d7ff013a1841<br />
1ed2c66c3565c3c4<br />
de03feefecb7d212<br />
<br />
reqHMACkey:<br />
4a17cbdd54ee17db<br />
426fcd7baddff587<br />
231d7eadb408c091<br />
ce19ca915b715985<br />
<br />
requestKey:<br />
9d93978e662bfc6e<br />
8cc203fa4628ef5a<br />
7bf1ddfd7ee54e97<br />
ec5c033257b4fca9<br />
<br />
== /session ==<br />
<br />
requestKey:<br />
9d93978e662bfc6e<br />
8cc203fa4628ef5a<br />
7bf1ddfd7ee54e97<br />
ec5c033257b4fca9<br />
<br />
respHMACkey:<br />
cd3f50403d060b21<br />
76d32f71ca105bd8<br />
7c9b6c4e10e3ebf9<br />
3f5077bec2db24fa<br />
<br />
respXORkey:<br />
8422c53143dea9c6<br />
044afbe95228f291<br />
74996b830b1794a3<br />
eff132da53174d43<br />
92eeb87ccf8ad7a8<br />
0c432894e066de6b<br />
0ff70658dfbf2f07<br />
b9c7704045edcd54<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
sessionToken:<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
plaintext:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
ciphertext:<br />
04a347b2c75b2f41<br />
8cc37162dea57c1e<br />
e408f9109f820234<br />
7768a841cf8ad3dc<br />
324f1adf6b2f710f<br />
a4ea823f4ccb70c4<br />
bf46b4eb6b0a99b0<br />
017ecafbf95073eb<br />
<br />
MAC:<br />
7973ddbb184b601a<br />
c4df09704028ebfc<br />
754dd50e7d8eebfa<br />
52ce3fd868c69852<br />
<br />
response:<br />
04a347b2c75b2f41<br />
8cc37162dea57c1e<br />
e408f9109f820234<br />
7768a841cf8ad3dc<br />
324f1adf6b2f710f<br />
a4ea823f4ccb70c4<br />
bf46b4eb6b0a99b0<br />
017ecafbf95073eb<br />
7973ddbb184b601a<br />
c4df09704028ebfc<br />
754dd50e7d8eebfa<br />
52ce3fd868c69852<br />
<br />
== /account/keys ==<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
tokenID (keyFetchToken):<br />
d010c94c753c012c<br />
d6801e8beb1aa6cc<br />
3da9ea3de3de1dee<br />
32785dbd99a579e8<br />
<br />
reqHMACkey:<br />
1707b05908acc4dc<br />
cda5b8304d9500d0<br />
8c53e00c31672a53<br />
490dfb5ef2934060<br />
<br />
respHMACkey:<br />
31d0c12186b76897<br />
c3351878a65097cf<br />
d595da4ce48e69a2<br />
485ff1a77c71b0d0<br />
<br />
respXORkey:<br />
eed35591e1f1c43b<br />
7cd604e371b9cfb7<br />
a980c9a36fa737c6<br />
a48c5d60a89fc291<br />
4ec1a2150a0777b7<br />
9a1e8499058cd17a<br />
ebc1441db8b3bf18<br />
2cd0aefa92482692<br />
<br />
kA:<br />
2021222324252627<br />
28292a2b2c2d2e2f<br />
3031323334353637<br />
38393a3b3c3d3e3f<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
plaintext:<br />
2021222324252627<br />
28292a2b2c2d2e2f<br />
3031323334353637<br />
38393a3b3c3d3e3f<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
ciphertext:<br />
cef277b2c5d4e21c<br />
54ff2ec85d94e198<br />
99b1fb905b9201f1<br />
9cb5675b94a2fcae<br />
0e80e0564e4231f0<br />
d257ced249c19f35<br />
bb90164eece6e94f<br />
7489f4a1ce1578cd<br />
<br />
MAC:<br />
86f1c57d2e7f6c97<br />
8181684e189b710f<br />
dd26a3f34e3aaed8<br />
64be9577ae81a256<br />
<br />
response:<br />
cef277b2c5d4e21c<br />
54ff2ec85d94e198<br />
99b1fb905b9201f1<br />
9cb5675b94a2fcae<br />
0e80e0564e4231f0<br />
d257ced249c19f35<br />
bb90164eece6e94f<br />
7489f4a1ce1578cd<br />
86f1c57d2e7f6c97<br />
8181684e189b710f<br />
dd26a3f34e3aaed8<br />
64be9577ae81a256<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
unwrapBKey:<br />
6ea660be9c89ec35<br />
5397f89afb282ea0<br />
bf21095760c8c500<br />
9bbcc894155bbe2a<br />
<br />
kB:<br />
2ee722fdd8ccaa72<br />
1bdeb2d1b76560ef<br />
ef705b04349d9357<br />
c3e592cf4906e075<br />
<br />
== use session (certificate/sign, etc) ==<br />
<br />
sessionToken:<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
tokenID (sessionToken):<br />
639503a218ffbb62<br />
983e9628be5cd64a<br />
0438d0ae81b2b9da<br />
deb900a83470bc6b<br />
<br />
reqHMACkey:<br />
3a0188943837ab22<br />
8fe74e759566d0e4<br />
837cbcc7494157aa<br />
c4da82025b2811b2<br />
<br />
== /password/change ==<br />
<br />
requestKey:<br />
9d93978e662bfc6e<br />
8cc203fa4628ef5a<br />
7bf1ddfd7ee54e97<br />
ec5c033257b4fca9<br />
<br />
respHMACkey:<br />
60d01e3d1da53b10<br />
93124a30c26889d7<br />
b2e067e7a09fde14<br />
6f935e3c653614f9<br />
<br />
respXORkey:<br />
3de5bd5e80faf84a<br />
dfca5396148123ef<br />
8184cd4bc10a7c8a<br />
db1688495affee67<br />
e07f80d914c5105f<br />
c86d6af24c4be1b1<br />
ef6c9c661422ac43<br />
181b3d29624a0cc2<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
accountResetToken:<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
plaintext:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
ciphertext:<br />
bd643fdd047f7ecd<br />
5743d91d980cad60<br />
11155fd8559fea1d<br />
438f12d2c66270f8<br />
20be421ad000d698<br />
00a4a03980862f7e<br />
3fbd4eb5c0f77a94<br />
c0c2e7f2be97d21d<br />
<br />
MAC:<br />
804fc4bc30923cc0<br />
d6c07ffea954848e<br />
0076b94f7deee71f<br />
a34db5c106d91980<br />
<br />
response:<br />
bd643fdd047f7ecd<br />
5743d91d980cad60<br />
11155fd8559fea1d<br />
438f12d2c66270f8<br />
20be421ad000d698<br />
00a4a03980862f7e<br />
3fbd4eb5c0f77a94<br />
c0c2e7f2be97d21d<br />
804fc4bc30923cc0<br />
d6c07ffea954848e<br />
0076b94f7deee71f<br />
a34db5c106d91980<br />
<br />
== /account/reset ==<br />
<br />
accountResetToken:<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
tokenID (accountResetToken):<br />
a6857e5d53d35073<br />
d50ef2ce2c4dd747<br />
32bb2eae1af5bf79<br />
618ed945e1310792<br />
<br />
reqHMACkey:<br />
47fab27352ee6b48<br />
33938d76519bbdb8<br />
ac7293f8b5e74335<br />
6fdd1d5edf39f52d<br />
<br />
reqXORkey:<br />
82ed612313a11673 95108d7d379b2029<br />
7a539ce9d3861e95 1bf5a9b9cdbfb332<br />
bd6aba056ce0c568 2c5a93963446b1b4<br />
7397c8c24f3a1d67 2a0ddc856474f5b1<br />
33ab884ce33335c1 5578a1a7302933cb<br />
458fbee0a5e52414 c914beb97568a30c<br />
28364dc8fb03ae7c 76a2f324a9a1cee6<br />
71b74aa8906d0e03 39fb52a1bf2b1ef5<br />
ab5d883295db62af 20701cb3af42a09e<br />
c76cda585ab5644b 7250ef7b780537e5<br />
b3e784d37a118bd6 57a0fe29ec6e5cd3<br />
325be8e1d8a3dd71 b360ea266757e463<br />
ada6b0a7a85a8ac0 eed618d9f6ee91ab<br />
1d2f714f224d67db 46843c4e3339de15<br />
efe0297a45f9fe0d 6d768b5c589a290f<br />
11f03237192cc0a3 a02645a810d83bb1<br />
84d582bfb15d2393 3fa4805374da62c6<br />
a2c887b157285c6a 79b47156c9abe02e<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
newSRPv:<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
<br />
plaintext:<br />
4041424344454647 48494a4b4c4d4e4f<br />
5051525354555657 58595a5b5c5d5e5f<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
<br />
ciphertext:<br />
c2ac236057e45034 dd59c7367bd66e66<br />
2a02ceba87d348c2 43acf3e291e2ed6d<br />
ac7bab147df1d479 3d4b82872557a0a5<br />
6286d9d35e2b0c76 3b1ccd947565e4a0<br />
22ba995df22224d0 4469b0b6213822da<br />
549eaff1b4f43505 d805afa86479b21d<br />
39275cd9ea12bf6d 67b3e235b8b0dff7<br />
60a65bb9817c1f12 28ea43b0ae3a0fe4<br />
ba4c992384ca73be 31610da2be53b18f<br />
d67dcb494ba4755a 6341fe6a691426f4<br />
a2f695c26b009ac7 46b1ef38fd7f4dc2<br />
234af9f0c9b2cc60 a271fb377646f572<br />
bcb7a1b6b94b9bd1 ffc709c8e7ff80ba<br />
0c3e605e335c76ca 57952d5f2228cf04<br />
fef1386b54e8ef1c 7c679a4d498b381e<br />
00e12326083dd1b2 b13754b901c92aa0<br />
95c493aea04c3282 2eb5914265cb73d7<br />
b3d996a046394d7b 68a56047d8baf13f<br />
<br />
== /account/destroy ==<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
tokenID (authToken):<br />
9a39818e3bbe6132<br />
38c9d7ff013a1841<br />
1ed2c66c3565c3c4<br />
de03feefecb7d212<br />
<br />
reqHMACkey:<br />
4a17cbdd54ee17db<br />
426fcd7baddff587<br />
231d7eadb408c091<br />
ce19ca915b715985<br />
<br />
= Keyserver Protocol Summary =<br />
<br />
* POST /account/create (email,srpV,srpSalt) -> ok (server sends verification email)<br />
** creates a user account<br />
* GET /account/devices [sessionToken] () -> list of devices<br />
* GET /account/keys [keyFetchToken,needs-verf] () -> kA/wrap(kB)<br />
** single-use, only if email is verified, encrypted results<br />
* POST /account/reset [authed+encrypted by accountResetToken] (wrap(kB),srpV,srpSalt) -> ok<br />
** single-use, does not require email to be verified, revoke all tokens for account, send notification email to user<br />
* POST /account/delete [authToken] () -> ok, account deleted<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authToken] () -> keyFetchToken, sessionToken<br />
* POST /session/destroy [sessionToken] () -> ok<br />
** for detaching a device, destroy all tokens<br />
* POST /recovery_email/status [sessionToken] () -> "verified" status of email<br />
** use "Accept: text/event-stream" header for server-sent-events; server will send "update" event with the new content of the resource any time it changes.<br />
* POST /recovery_email/resend_code [sessionToken] () -> re-send verification email<br />
* POST /recovery_email/verify_code (code) -> set "verified" flag<br />
** this code will come from a clickable link and is an unauthenticated endpoint<br />
** this could maybe take the recovery method if that would be helpful<br />
** sets verified flag on recovery method<br />
* POST /certificate/sign [sessionToken,needs-verf] (pubkey) -> cert<br />
** only if recovery email is verified<br />
* POST /password/change/start [authToken,needs-verf] () -> accountResetToken, keyFetchToken<br />
* POST /password/forgot/send_code () -> forgotPasswordToken<br />
** sends code to recovery method (email for now, maybe SMS later)<br />
** this is a short code, not a clickable link<br />
* POST /password/forgot/resend_code (forgotPasswordToken) -> re-sends code<br />
* POST /password/forgot/verify_code (forgotPasswordToken, code) -> accountResetToken<br />
** sets verified flag on recovery method<br />
* POST /get_random_bytes<br />
<br />
<br />
== Typical Client Flows ==<br />
<br />
Create account<br />
<br />
* POST /account/create (email,srpV,srpSalt) -> ok (server sends verification email)<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authed with authToken]() -> keyFetchToken, sessionToken<br />
* GET /recovery_email/status [sessionToken] () -> "verified" status<br />
** (optional, only if user requests resend) POST /recovery_email/resend_code [sessionToken]() -> ok<br />
** POST /recovery_email/verify_code (code) -> ok<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
* POST /certificate/sign [sessionToken] (pubkey) -> cert<br />
<br />
<br />
Attach to new device<br />
<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authToken] () -> keyFetchToken, sessionToken<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
** (if unverified-error, do waitUntilEmailVerified, then try again)<br />
* POST /certificate/sign [sessionToken] (pubkey) -> cert<br />
<br />
<br />
Forgot password<br />
<br />
* POST /password/forgot/send_code (email) -> forgotPasswordToken<br />
* POST /password/forgot/verify_code (forgotPasswordToken, code) -> accountResetToken<br />
* POST /account/reset [authed+encrypted by accountResetToken] (0000,srpV,srpSalt) -> ok<br />
* GOTO "Attach to new device"<br />
<br />
<br />
Change Password<br />
<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /password/change/start [authToken] () -> accountResetToken, keyFetchToken<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
* POST /account/reset [authed+encrypted by accountResetToken] (wrap(kB),srpV,srpSalt) -> ok<br />
* GOTO "Attach to new device"</div>Warnerhttps://wiki.mozilla.org/index.php?title=Identity/AttachedServices/KeyServerProtocol&diff=692475Identity/AttachedServices/KeyServerProtocol2013-08-08T23:53:43Z<p>Warner: /* SRP Notes */ reminder: A and B must not be zero</p>
<hr />
<div>= PiCL Key Server / IdP Protocol =<br />
<br />
NOTE: This specification is slowly converging on stability (31-Jul-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.<br />
<br />
The server is being developed in https://github.com/mozilla/picl-idp . This repo currently includes a demonstration client (node.js CLI).<br />
<br />
Note that all messages are delivered over an HTTPS connection. The client browser may also implement cert-pinning to improve on the certificate validation process. The protections described below are in addition to those provided by TLS.<br />
<br />
Remaining TODO items:<br />
<br />
* decide on client-side key-stretching parameters: http://keywrapping.appspot.com can help<br />
* finalize SRP questions (definition of M1, generation of a/b)<br />
* finalize proof-of-work/DoS-prevention details<br />
* decide how to rate-limit account-creation calls<br />
* confirm this is actually implementable inside Firefox (especially w.r.t. NSS and Android/Java crypto)<br />
<br />
= Creating The Account =<br />
<br />
The first act performed by a user is to create the account. They enter email+password into their browser, which then does the following steps:<br />
<br />
* decide upon stretching parameters (perhaps consulting the keyserver for recommendations, but imposing a minimum strength requirement). For now we used a fixed {firstPBKDF:20000, scrypt: {N:65536, r:8, p:1}, secondPBKDF:20000}.<br />
* randomly choose a 32-byte mainSalt (this should be unique, but is not secret)<br />
* choose the SRP group parameters (fixed: use the 2048-bit group described below)<br />
* perform key-stretching (as described below), derive stretchedPW and srpPW<br />
* randomly choose a 32-byte srpSalt (unique, but not secret)<br />
* create srpVerifier from srpPW and srpSalt (as described below)<br />
* deliver (email, stretchParams, mainSalt, srpParams, srpSalt) to the keyserver's "POST /account/create" API<br />
<br />
<br />
To limit abuse, the createAccount() should also require a fresh "createToken". This should be created by some other API, outside the scope of this document, that perhaps requires a CAPTCHA or something. createAccount() might also require a proof-of-work token, as described below.<br />
<br />
The server, when creating a new account, creates both kA and wrap(kB) as randomly-generated 256-bit (32-byte) strings. It stores these, along with all the remaining values, indexed by email, in the account table where they can be retrieved by getToken later.<br />
<br />
== Email Verification ==<br />
<br />
To prevent fixation attacks, we require new accounts to verify their configured recovery email address before letting them learn the generated keys or obtain a signed certificate. Nevertheless, we wish clients to forget the user's password while they wait for email verification to complete. To achieve this, clients can obtain a sessionToken before verification, but most APIs that require it will raise errors until verification is finished.<br />
<br />
The server will send email with a URL that contains a long random "verification code" in the "fragment" hash. This URL points to a static page with some javascript that submits the code to the "POST /account/recovery_methods/verify_code" API. The URL can be clicked by any browser (it is not bound to anything), and when the API is hit, the account is marked as verified.<br />
<br />
After the client submits /account/create, it performs the "/session/auth" login sequence below to obtain a sessionToken. It then polls the "GET /account/recovery_methods" (which requires a sessionToken but not account verification) until the user clicks the email link and the API reports verification is complete. Then the client uses "GET /account/keys" and "POST /certificate/sign", described below, to obtain kA, kB, and a signed certificate to talk to the storage server.<br />
<br />
= Login: Obtaining the authToken =<br />
<br />
To connect a browser to an existing account, we use the following login protocol to transform an email+password pair into a single-use authToken. We will use this in the next section to create a session, from which we can obtain encryption keys and signed certificates.<br />
<br />
This protocol starts by using key-stretching to transform the email+password into a "stretchedPW", then feeds this into an SRP protocol to get the authToken.<br />
<br />
[[File:PICL-IdPAuth-auth-start.png|IdP Auth Protocol]]<br />
<br />
This authToken can be used (once) to do one of the following:<br />
<br />
* /session/create: obtain a sessionToken (and keyFetchToken), which enables storage server access<br />
* /password/change: obtain an accountResetToken, to safely reset the account password<br />
* /account/delete: to delete the entire account<br />
<br />
The protocol is designed to enable parallelism between key-stretching and the initial network messages, to reduce the time it takes to connect a browser to the account. In total, the browser requires five messages in four roundtrips (1: /auth/start, 2: /auth/finish, 3: /session/create, 4: /account/keys and /certificate/sign in parallel) before it is ready to talk to the storage server.<br />
<br />
== /auth/start ==<br />
<br />
As soon as the user finishes typing in the email address, the client should send it in the "/auth/start" 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 loginSRPToken that is used to associate this request with the subsequent /auth/finish 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.<br />
<br />
To mitigate DoS abuse, /auth/start may also require a proof-of-work string, described below.<br />
<br />
== Client-Side Key Stretching ==<br />
<br />
"Key Stretching" is the practice of running a password through a computationally-expensive one-way function before using it for encryption or authentication. The goal is to make brute-force dictionary attacks more expensive, by raising the cost of testing each guess.<br />
<br />
To protect the user's class-B data against compromises of our keyserver, we perform this key stretching on the client, rather than on the server. We use the memory-hard "scrypt" function (pronounced "ess-crypt") for this purpose, as motivated by the attacker-cost studies in [[Identity/CryptoIdeas/01-PBKDF-scrypt]]. Slower (low-RAM) devices can still participate by outsourcing the scrypt step to an "scrypt helper". To provide at least minimal protection against a compromise of this helper, we sandwich the scrypt step between two PBKDF2 steps. The complete protocol looks like this:<br />
<br />
[[File:PICL-IdPAuth-stretch-KDF.png|Stretching KDF]]<br />
<br />
Our initial parameter choices for this stretching are to use 20000 iterations of PBKDF2 on each side (40k in all), and to run scrypt with N/r/p set to 64k/8/1. This requires roughly 60MB of RAM for about 1.3 seconds on a 1.8GHz Intel Atom linux box (it will run faster, but use the same memory, on more modern CPUs). We are still studying performance on various platforms to decide what parameters to use in V1: lowering them speeds up the login process, but reduces security (by reducing the cost of an dictionary attack).<br />
<br />
Since the stretching is expected to take a second or two, the client can optimistically start this process (using default parameters) before receiving the auth/start 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 "mainSalt" is added *after* the stretching, to enable this parallelism (at a tiny cost in security).<br />
<br />
After "stretchedPW" is derived, a second HKDF call is used to derive "srpPW" and "unwrapBKey" which will be used later.<br />
<br />
[[File:PICL-IdPAuth-main-KDF.png|masterKey KDF]]<br />
<br />
== SRP Protocol Details ==<br />
<br />
The PiCL client uses the SRP protocol (http://srp.stanford.edu/) to prove that it knows the (stretched) account password without revealing the actual password (or information enabling a brute-force attack) to the server or any eavesdroppers. <br />
<br />
SRP is somewhat underspecified. We use SRP-6a, with SHA256 as the hash, and the 2048-bit modulus defined in RFC 5053 Appendix A. We consistently zero-pad all string values to 256 bytes (2048 bits), and use H(A+B+S) as the key-confirmation message "M1". These details, plus the SRP design papers and RFCs 2945 and 5054, should be enough to build a compatible implementation. The diagrams below and the test vectors at the end of this page can be used to verify compatibility.<br />
<br />
The server should use Jed's SRP module from https://github.com/jedp/node-srp . The client might use SJCL (http://crypto.stanford.edu/sjcl/) or native code (NSS).<br />
<br />
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).<br />
<br />
Note that SRP-6a uses a "k" value which basically encodes the group being used ("N" and "g"). Since all PICL accounts use the same 2048-bit group, they will all use the same "k" value (not to be confused with the per-session shared-secret "K" key that emerges from the protocol). This group's "k" integer is (as a base-10 number):<br />
2590038599070950300691544216303772122846747035652616593381637186118123578112<br />
<br />
=== SRP Verifier Calculation ===<br />
<br />
When the client first creates the account, it must combine the account email address, the stretched password (srpPW), and a randomly-generated srpSalt, to compute the srpVerifier. The server will use this verifier later, to check whether or not the client really knows the password.<br />
<br />
If the server is compromised and an attacker learns the srpVerifier for a given account, they cannot use this to directly log in (the verifier is not "password-equivalent"), but it does allow them to perform an offline brute-force attack against the user's password. In this respect, it is similar to a traditional hashed password. We make these attacks somewhat more expensive by performing the client-side stretching described above, instead of using the raw user password in the SRP calculation.<br />
<br />
[[File:PICL-IdPAuth-SRP-Verifier.png|client-side SRP Verifier calculation]]<br />
<br />
Test vectors are provided at the end of this page. The protocol requires that several values ("a", "b", "srpSalt") are chosen randomly, but for illustrative purposes, the test vectors use carefully-crafted non-random values. For the user's sake, please ensure implementations use proper random values instead of simply copying the test vectors.<br />
<br />
The client sends srpSalt and srpVerifier to the server when it creates the account. It will also re-compute the 'x' value (as an integer) during sign-in. The server will convert the srpVerifier string back into an integer ('v') for use during its own sign-in calculations.<br />
<br />
=== SRP Server-side Sign-In Flow ===<br />
<br />
When the user connects a new device to their account, they use the /auth/start API to start the SRP protocol. This sends the account email address to the server. The server looks up the stored srpVerifier for this account, creates a random 'b' integer, performs some math to compute the "B" number, then converts B into a string known as "srpB". "srpB" is returned to the client, along with srpSalt and the key-stretching parameters. "b" and "srpB" are retained for the subsequent /auth/finish call. '''Note that it is critical that the "b" integer remain secret on the server.''' The server also allocates a random loginSRPToken to connect the two calls.<br />
<br />
[[File:PICL-IdPAuth-SRP-Server.png|server-side SRP]]<br />
<br />
Later, /auth/start will be called with the client's srpA string and its M1 key-confirmation string. srpA is combined with srpB to calculate the "u" integer. srpA is also turned into an integer and used to compute the shared-secret "S" integer. S is then used to compute the shared key "srpK", which is the output of the SRP process.<br />
<br />
=== SRP Client Calculation ===<br />
<br />
While the client is waiting for the response to /auth/start, it begins its key-stretching calculations. Everything else must wait until the response to /auth/start arrives, which includes the key-stretching parameters (which are retroactively confirmed), srpSalt, and the server's generated srpB value.<br />
<br />
[[File:PICL-IdPAuth-SRP-Client.png|client-side SRP]]<br />
<br />
Once the client knows srpSalt, it computes the same "x" integer as it did in the middle of the srpVerifier calculation. It also converts srpB into an integer named "B". Then it creates a random "a" integer, uses it to compute the string "srpA", then combines srpA with the server's srpB to compute the "u" integer. It then combines the static "k", the password-derived "x", the combined "u", and the server's "B", together with some magic math, to derive the "S" integer. If everything went well, the client will compute the same "S" value as the server did. If not (the password was wrong, or the client is talking to a fake server that doesn't really know srpVerifier), then the two "S" values will not match.<br />
<br />
('''Again, it is critical that the client keep its "a" and "x" integers secret, both during and after the protocol run.''')<br />
<br />
To safely tell if the "S" values match, both client and server combine srpA, srpB, and their (independently) generated "S" strings to form a string named "M1". The client sends M1 (along with srpA) in the /session/auth/finish message. The server compares the client's copy of M1 against its own. If they match, the client knew the password and the server can safely respond with the encrypted account data. If they do not match, the client (or a man-in-the-middle attacker) did not know the password, and the client should increment a counter that can trigger defenses against online guessing attacks. The server must then return an error to the client, and '''not''' use or reveal srpK (or the correct M1) in any way.<br />
<br />
Both client and server also hash "S" into "srpK". This is the shared session key, from which specific message encryption and MAC keys are derived (as described below).<br />
<br />
=== SRP Notes ===<br />
<br />
The SRP "g" (generator) and "N" (prime modulus) should use the 2048-bit value from RFC 5054 Appendix A. 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.<br />
<br />
There are several places in SRP where integers (usually in the range 1..N-1) are converted into bytestrings, either for transmission over a wire, or to be passed into a hash function. The SRP spec is somewhat ambiguous about padding here: if the integer happens to be less than 2^2040, the simplest toString() approach will yield a '''255''' byte string, not a 256 byte string. PiCL consistently uses padding, so compatible implementations must prepend one or more NUL bytes to these short strings before transmission or hashing. The test vectors below were brute-forced to ensure that "srpVerifier", "srpA", "srpB", and "S" all wind up with leading zeros, to exercise the padding code in compatible implementations. If you are having problems getting your code to match these results, add some assertions to test that the stringified integers being put into hashes are exactly 256 bytes long.<br />
<br />
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 (/auth/finish).<br />
<br />
The server receives "A" in /auth/finish, 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 keyFetchToken+sessionToken as described below, returning the encrypted/MACed bundle in the response to /auth/finish.<br />
<br />
On the server, it is critical to reject an "A" value that is 0, or some other multiple of N. If the server does not check this, anybody can trivially sign in to any account without knowing the password. Likewise, it is critical for the client to reject a "B" value where B%N==0. If the client does not check this, the server (or an attacker pretending to be the server) will get a value that can be used in an offline brute-force search for the user's password.<br />
<br />
Outstanding crypto questions:<br />
<br />
* How exactly should the "a" and "b" integers be generated? The issue is of how much bias SRP can tolerate. Ideally these integers are uniformly distributed from 1 to N-1 (inclusive). The only way to obtain a purely uniform distribution from a source of random bytes is try-try-again: pick a (integral-number-of-bytes) number, compare it to the desired range, try again if it falls outside the range. If you get unlucky, this can take a lot of guesses, depending upon how close the range is to a power of two. ECDSA implementations tend to pick a number twice as long as the modulus and then modulo it down (rand(2^4096) % N), which yields a tiny fraction of a bit of bias. The cheaper approach is to do the same with a number of equal length (rand(2^2048) % N), which imposes more bias. Some SRP implementations appear to be satisfied by rand(2^256). We need more review here.<br />
* The original SRP papers defined M1=H(A+B+S) as we use here, but other implementation (in particular RFC2945) uses a curious construct that includes the username, the salt, and an odd XOR combination of N and g. We need to decide what to use for M1.<br />
<br />
== /auth/finish ==<br />
<br />
The client-side SRP calculation results in two values that are sent to the server in the /auth/finish 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.<br />
<br />
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.<br />
<br />
The server then allocates a single-use 32-byte random token named "authToken". It encrypts the token with the session key, and returns a success message with the encrypted bundle.<br />
<br />
All tokens have an associated tokenID, described below. The server needs to maintain a table that maps the tokenID to the token itself, so it can derive other values from the token later. The tokens are also associated with a specific account, so later API requests do not specify an email address or account ID.<br />
<br />
== Decrypting the /auth/finish Response ==<br />
<br />
The SRP session key ("srpK") is used to derive two other keys: respHMACkey and respXORkey.<br />
<br />
[[File:PICL-IdPAuth-encrypt-authToken.png|Decrypting the authToken]]<br />
<br />
The respXORkey is used to encrypt the authToken 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.<br />
<br />
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 returns the authToken value.<br />
<br />
= Creating a Session =<br />
<br />
For login, the single-use authToken is spent on a call to /session/create .<br />
This allocates two new (random 32-byte) tokens: a long-lived "sessionToken", and a single-use "keyFetchToken". The /session/create call returns an encrypted bundle containing the two tokens.<br />
<br />
[[File:PICL-IdPAuth-encrypt-sessionToken.png|Decrypting the sessionToken and keyFetchToken]]<br />
<br />
For calls which accept an authToken, the client uses authToken to derive three values:<br />
<br />
* tokenID<br />
* reqHMACkey<br />
* requestKey<br />
<br />
<br />
The client uses tokenID and reqHMACkey for a HAWK (https://github.com/hueniverse/hawk/) request to the "POST /session/create" API, using tokenID as "credentials.id" and reqHMACkey as "credentials.key". The server uses tokenID to look up the corresponding token, then derives reqHMACkey to validate the request.<br />
<br />
Each authToken-using call then derives additional API-specific values from requestKey. /session/create uses two derived values:<br />
<br />
* respHMACkey<br />
* respXORkey<br />
<br />
<br />
When the server receives a valid /session/create request, it allocates sessionToken and keyFetchToken, concatenates them, encrypts the pair by XORing it with the derived respXORkey, and attaches a MAC generated with respHMACkey. The encrypted MACed bundle is returned to the client.<br />
<br />
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 keyFetchToken and sessionToken values.<br />
<br />
Each authToken is single-use: once a successful request has been made with it, the authToken and its corresponding ID is removed from the server's memory, and subsequent attempts to use it will return a "no such token" error. The token is consumed even if the request fails (e.g. the MAC did not match).<br />
<br />
The authToken can be used (once) by multiple APIs. The server only needs to maintain one table mapping tokenID to authToken, because all these APIs share the same method of deriving a tokenID and reqHMACkey from the authToken.<br />
<br />
When a HAWK request appears, it should look up the tokenID in this table, retrieve the authToken, recompute reqHMACkey and requestKey, validate the request, delete the table entry, then hand the HTTP request and requestKey to the specific API handler. That handler should derive the API-specific values (respHMACkey, respXORkey, etc) to process the request or construct the response.<br />
<br />
The server can support multiple sessions per account (typically one per client device, plus perhaps others for account-management portals). There can also be multiple outstanding keyFetchTokens. The sessionToken lasts forever (until revoked by a password change or explicit revocation command), and can be used an unlimited number of times. The keyFetchToken expires after 60 seconds, and is single-use.<br />
<br />
= Obtaining keys kA and kB =<br />
<br />
Clients which have exchanged an authToken for either a sessionToken or an accountResetToken will also receive a keyFetchToken. This single-use token allows the client to retrieve kA and wrap(kB), which enables the client to encrypt and decrypt browser data (bookmarks, open-tabs, etc) correctly. As above, the keyFetchToken is used to derive tokenID, reqHMACkey, respHMACkey, and respXORkey, which are used in a HAWK request to the "GET /account/keys" API.<br />
<br />
The server pulls kA and wrap(kB) from the account table, concatenates them, encrypts the pair by XORing it with the derived respXORkey, and attaches a MAC generated with respHMACkey.<br />
<br />
[[File:PICL-IdPAuth-keys-server.png|keyFetchToken: server encrypts keys]]<br />
<br />
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 and wrap(kB) values.<br />
<br />
[[File:PICL-IdPAuth-keys-client.png|keyFetchToken: client decrypts keys]]<br />
<br />
Finally, the server-provided wrap(kB) value is simply XORed with the password-derived unwrapBKey (both are 32-byte strings) to obtain kB. There is no MAC on wrap(kB).<br />
<br />
[[File:PICL-IdPAuth-key-unwrap.png|unwrapping kB]]<br />
<br />
"kA" and "kB" enable the browser to encrypt/decrypt synchronized data records. They will be used to derive separate encryption and HMAC keys for each data collection (bookmarks, form-fill data, saved-password, open-tabs, etc). This will allow the user to share some data, but not everything, with a third party. The client may intentionally forget kA and kB (only retaining the derived keys) to reduce the power available to someone who steals their device.<br />
<br />
Note that /account/keys will not succeed until the account's email address has been verified. Also note that each keyFetchToken is single-use and short-lived. The token is consumed even if the request fails (e.g. the MAC does not match).<br />
<br />
= Signing Certificates =<br />
<br />
The sessionToken is used to derive two values:<br />
<br />
* tokenID<br />
* request HMAC key<br />
<br />
<br />
[[File:PICL-IdPAuth-use-session.png|Using the sessionToken, signing certificates]]<br />
<br />
The requestHMACkey is used in a HAWK request to provide integrity over many APIs, including /certificate/sign. requestHMACkey 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.<br />
<br />
For /certificate/sign, it is critical to enable payload verification by setting options.payload=true (on both client and server). Otherwise a man-in-the-middle could submit their own public key, get it signed, and then delete the user's data on the storage servers.<br />
<br />
The following keyserver APIs require a HAWK-protected request that uses the sessionToken. In addition, some require that the account be in the "verified" state:<br />
<br />
* GET /account/devices<br />
* POST /session/destroy<br />
* GET /recovery_email/status<br />
* POST /recovery_email/resend_code<br />
* POST /certificate/sign (requires "verified" account)<br />
<br />
= Resetting The Account =<br />
<br />
The account may be reset in two circumstances: when the user changes their password, and when the user forgets their password. In both cases, the client first obtains an "accountResetToken". This token is then used to change the SRP Verifier and either reset or replace the wrap(kB) value.<br />
<br />
== Changing the Password ==<br />
<br />
When the user wishes to change their password (i.e. they still know the old password), they first use the /auth/start+/auth/finish SRP protocol safely obtain an "authToken". They they use the "/password/change/start" API to exchange the authToken for an accountResetToken and a keyFetchToken.<br />
<br />
[[File:PICL-IdPAuth-encrypt-passwordChange.png|Server encrypts passwordChange response]]<br />
<br />
The accountResetToken will be used below to set the new password safely. The keyFetchToken should be used first, to obtain kB, so the subsequent account reset can replace wrap(kB) with a new value. This allows the password-changing client to retain their class-B data.<br />
<br />
Requiring an authToken proves that the user has provided the correct account password recently. When the account is reset, all active sessions and tokens will be cancelled (disconnecting all devices from the account). The client should immediately establish a new session as described above.<br />
<br />
This API is only used when the user knows their old password: if they have forgotten the password, use the "/password/forgot" APIs below.<br />
<br />
== Handling a Forgotten Password ==<br />
<br />
When the user has forgotten their password, they can use one of their "recovery methods" to obtain an accountResetToken. For now, this means we send a random code to the email address associated with their account. The user must copy this code from the email into their client, whereupon the client will get an accountResetToken that can be delivered to the API below.<br />
<br />
Note that, since the forgotten-password client never learns kB, any class-B data will be lost. This is necessary to protect class-B data from attackers who can read the users's email but do not know the account password (including those who compromise the IdP and the keyserver itself). When using /account/reset below, they will set wrap(kB) to a string of all zeros, which means the server should generate a new random wrap(kB) (just as it does during account creation).<br />
<br />
The /password/forgot/send_code API is used to ask the server to send a recovery code. This takes a recovery method, which for now is just an email address. This API is unauthenticated (after all, the user who has forgotten their password knows nothing but their email address). The server marks the corresponding account as "pending recovery", allocates a random forgotPasswordToken for the account, creates a recovery code, and sends the code (with instructions) via email. The API returns forgotPasswordToken to the client.<br />
<br />
The user must copy the recovery code into the same browser where they started the process. The client then submits the code to /password/forgot/verify_code along with the forgotPasswordToken they received. If they match, the server allocates a accountResetToken and returns it to the client. If they do not match, the server increments a counter (which is used to decide if an online guessing attack is happening).<br />
<br />
forgotPasswordToken can be used three times before it is exhausted. If the user guesses incorrectly this often, the client must call send_code again to get a new token and code. Each account has at most one token+code active at a time.<br />
<br />
The recovery code is initially a random 8-digit decimal number. If an attacker tries to sign in as someone else, hits the "forgot my password" button, then submits a guess to /password/forgot/verify_code, they will have a 1-in-100-million chance of success. If the server detects too many wrong guesses, it should increase the length of new codes. Another defensive technique is to require that users click an email link before being given the code: the server is told when the link is clicked, so the code will not be enabled until the email has been read. It remains to be seen whether this will be sufficient.<br />
<br />
The exact thresholds are TBD, but a nominal goal is to keep the chances of any attack succeeding to below 1-in-a-million per year. To achieve this, we can tolerate 100 verify_code failures in a single year before we must increase the length of the code.<br />
<br />
== Using accountResetToken ==<br />
<br />
An accountResetToken, obtained through either of the methods above, is used to invoke the /account/reset API. If the request is accepted, the server replaces the account password with a new one, updates the wrap(kB) value, cancels all active sessions and tokens (disconnecting all devices from the account), and sends a "your password has been changed" email to the user.<br />
<br />
If the client knew the old password, it can supply a new wrap(kB) value that will yield the same kB key as before, so no data will be lost. If the client did *not* know the old password, then it will supply a wrap(kB) of all-zeros, which tells the server to generate a new random wrap(kB) (just like during account creation), which means the class-B data will be lost.<br />
<br />
The client puts their new password through the same stretching procedure as described in the new-account section above, resulting in a new srpVerifier and unwrapBKey. If they knew the old kB, they XOR it with the new unwrapBKey to obtain a new wrap(kB).<br />
<br />
/account/reset needs request confidentiality, since the arguments include the newly wrapped kB value and the new SRP verifier, both of which enable a brute-force attack against the password. HAWK provides request integrity. The response is a single "ok" or "fail", conveyed by the HTTP headers, so we do not require response confidentiality, and can live without response integrity.<br />
<br />
So the single-use resetToken is used to derive three values:<br />
<br />
* tokenID<br />
* request HMAC key<br />
* request XOR key<br />
<br />
<br />
[[File:PICL-IdPAuth-encrypt-resetAccount.png|Client encrypts resetAccount request]]<br />
<br />
The request data will contain wrap(kB) and the new SRP verifier, concatenated together. Since we always pad the SRP verifier to the full (256-byte) group length, both pieces are fixed-length. We generate enough reqXORkey bytes to cover both values.<br />
<br />
The request data is XORed with requestXORkey, then delivered in the body of a HAWK request that uses tokenID as credentials.id and requestHMACkey as credentials.key . Note: it is critical to include the request body in the HAWK integrity check (options.payload=true, on both client and server), otherwise a man-in-the-middle could substitute their own SRP verifier, giving them control over the account (access to the user's class-A data, and a brute-force attack on their password).<br />
<br />
The client submits other values in the same request:<br />
<br />
* stretchParams<br />
* mainKDFSalt<br />
* srpSalt<br />
<br />
<br />
These values do not require confidentiality, so are not included in the encrypted bundle. They are still protected by the HAWK integrity check. Note that the server should assert that both salts are different than the previously stored values.<br />
<br />
After using /account/reset, clients should immediately perform the login protocol from above. If the old password was forgotten, this is necessary to fetch kA. In either case, a new sessionToken is required, since old sessions and tokens are revoked by /account/reset. Clients should retain the new srpPassword value during this process to avoid needing to run the lengthy key-stretching routine a second time.<br />
<br />
= Deleting The Account =<br />
<br />
When the user wishes to completely delete their account, the browser needs to perform two actions:<br />
<br />
* contact the storage servers and delete all records and collections<br />
* contact the keyserver and delete the account information<br />
<br />
The user should be prompted for their password as confirmation (i.e. a browser in the normal attached-and-synchronizing state should not be able to erase the account information: it must acquire a new authToken first).<br />
<br />
The device then obtains an authToken as described above, then spends it on a HAWK-protected request to the /account/destroy endpoint. This request contains no body and returns only a success code.<br />
<br />
[[File:PICL-IdPAuth-deleteAccount.png|Deleting the Account]]<br />
<br />
= Crypto Notes =<br />
<br />
Strong entropy is needed in the following places:<br />
<br />
* (client) creation of private "a" value inside SRP<br />
* (server) initial creation of kA and wrap(kB)<br />
* (server) creation of private "B" value inside SRP<br />
* (server) creation of signToken and resetToken<br />
<br />
<br />
On the server, code should get entropy from /dev/urandom via a function that uses it, like "crypto.randomBytes()" in node.js or "os.urandom()" in python. On the client, code should combine local entropy with some fetched from the keyserver via getEntropy(), to guard against failures in the local entropy pool. Something like HKDF(IKM=localEntropy+remoteEntropy, salt="", info=KW("mergeEntropy")).<br />
<br />
An HKDF-based stream cipher is used to protect the response for getToken2(), and the request for resetAccount(). HKDF is used to create a number of random bytes equal to the length of the message, then these are XORed with the plaintext to produce the ciphertext. An HMAC is then computed from the ciphertext, to protect the integrity of the message.<br />
<br />
HKDF, like any KDF, is defined to produce output that is indistinguishable from random data ("The HKDF Scheme", http://eprint.iacr.org/2010/264.pdf , by Hugo Krawczyk, section 3). XORing a plaintext with a random keystream to produce ciphertext is a simple and secure approach to data encryption, epitomized by AES-CTR or a stream cipher (http://cr.yp.to/snuffle/design.pdf). HKDF is not the fastest way to generate such a keystream, but it is safe, easy to specify, and easy to implement (just HMAC and XOR).<br />
<br />
Each keystream must be unique. SRP is defined to produce a random session key for each run (as long as at least one of the sides provides a random ephemeral key). We define resetToken to be a single-use randomly-generated value. Hence our two HKDF-XOR keystreams will be unique.<br />
<br />
A slightly more-traditional alternative would be to use AES-CTR (with the same HMAC-SHA256 used here), with a randomly-generated IV. This is equally secure, but requires implementors to obtain an AES library (with CTR mode, which does not seem to be universal). An even more traditional technique would be AES-CBC, which introduces the need for padding and a way to specify the length of the plaintext. The additional specification complexity, plus the library load, leads me to prefer HKDF+XOR.<br />
<br />
kB is equal to the XOR of wrapKey (which is a deterministic function of the user's email address, password, mainSalt, and the stretching parameters) and the server's randomly-generated wrap(kB) value, making kB a random value too. Using XOR as a wrapping function allows us to avoid sending kB or wrap(kB) in the initial createAccount arguments, which are not protected by SRP, and thus would enable an eavesdropper to mount a dictionary attack on the password (using wrap(kB) as their oracle).<br />
<br />
Likewise, allowing the server to generate kA avoids exposure during createAccount. The only point of vulnerability is a forgotten-password resetAccount() message, if an eavesdropper can learn the resetToken. In this case, the attacker might either use the resetToken themselves (then use signToken to learn kA and wrap(kB)), or passively decrypt the resetAccount() arguments to retrieve just wrap(kB). Given wrap(kB) and some encrypted browser data, the attacker can guess passwords (and derive kB, etc) until the data decrypts properly.<br />
<br />
To make this technique safe, any time kB or the password is changed, the mainSalt should be changed too. Otherwise knowledge of both wrap(old-kB) and old-kB would reveal wrapKey, making it easy to deduce the new kB. Changing mainSalt causes wrapKey to change too, preventing this.<br />
<br />
mainSalt is incorporated at the end of the stretching process to allow it to proceed in parallel with the getToken1() call that retrieves the salt. The inputs to the lengthy stretch come entirely from the user (email and password) or are optimistically (stretching parameters). This speedup seems more important than the minor security benefit of including the salt at the beginning of the stretch.<br />
<br />
There is no MAC on wrap(kB). If the keyserver chooses to deliver a bogus wrap(kB) or kA, the client will discover the problem a moment later when it talks to a storage server and attempts to retrieve data from an unrecognized collection-ID (since we intend to derive collection-IDs from the key used to encrypt their data, which will be derived from kA or kB as appropriate). It might be useful to add a checksum to kA and wrap(kB) to detect accidental corruption (e.g. store and deliver kA+SHA256(kA)), but this doesn't protect against intentional changes. We omit this checksum for now, assuming that disks will be reliable enough to let us never experience such failures.<br />
<br />
We use scrypt without a per-user salt. This is safe because the "password" input to scrypt is already diversified by the user's email address. The intention is to allow the scrypt-helper to run on anonymous data, so that an attacker who compromises this helper cannot easily learn the email addresses of the partially-stretched K1 values that it is receiving, confounding their attack.<br />
<br />
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. <br />
For /certificate/sign, we do not need request confidentiality or response confidentiality, since the client's pubkey and the resulting certificate will both be exposed over a similar SSL connection to the storage server later. And it is sufficient to rely on the response integrity provided by SSL, since the client can verify the returned certificate for itself. For the other keyserver APIs protected by HAWK, these properties are either unnecessary, or are provided by additional mechanisms.<br />
<br />
= Proof-Of-Work =<br />
<br />
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(). The server can control how much work is required.<br />
<br />
The getToken1() call looks for a "X-PiCL-PoW:" HTTP header. Most of the time, clients don't supply this header. But if the server responds to the getToken1() call with an error that indicates PoW is required, clients must create a valid PoW string and include it as the value of an "X-PiCL-PoW:" header in their next call to getToken1().<br />
<br />
The server's error message includes two parameters. The first is a "prefix string": the client's PoW string is required to begin with this prefix. The second is a "threshold hash". SHA256(PoWString) is required to be lexicographically earlier than the thresholdHashString (i.e. the numerical value of its hash must be closer to zero than the threshold). The client is expected to concatenate the prefix with a counter, then repeatedly increment the counter and hash the result until they meet the threshold, then re-submit their getToken1() request with the combined prefix+counter string in the header. If the client has spent more than e.g. 10 seconds doing this, the client should probably help the user cancel the operation and try again.<br />
<br />
When a server is under a DoS attack (either via some manual configuration tool or sensed automatically), it should start requiring valid unique X-PiCL-PoW headers. The server should initially require very little work, by using a threshold hash with just a few leading zero bits. If this is insufficient to reduce the attack volume, the threshold should be lowered, requiring even more work (from both the attacker and legitimate clients).<br />
<br />
The server should create a prefix string that contains a parseable timestamp and a random nonce (e.g. "%d-%d-" % (int(time.time()), b32encode(os.urandom(8)))). The server should also decide on a cutoff time (perhaps ten minutes ago). Each server must then maintain a table of "old PoW strings" to prevent replay attacks (these do not need to be shared among all servers: an in-RAM cache is fine).<br />
<br />
When the server receives a proposed PoW string, it first splits off the leading timestamp, and if the timestamp is older than the cutoff time, it rejects the string (either by dropping the connection, or returning a new "PoW required" error if it's feeling nice). Then it hashes the whole string and compares it against the threshold, rejecting those which fail to meet the threshold. Finally, for strings that pass the hash threshold, it checks the "old strings" table, and rejects any that appear on that list.<br />
<br />
If the PoW string makes it past all these checks, the server should add the string to the "old strings" table, then accept the request (i.e. compute an srpB value and add a session-id table entry for the request).<br />
<br />
The old-strings table check should be optimized to reject present strings quickly (i.e. if we are under attack, we should expect to see lots of duplicates of the same string, and must minimize the work we do when this occurs).<br />
<br />
The server can remove values from the old-strings table that have timestamps older than the cutoff time. The server can also discard values at other times (to avoid consuming too much memory), without losing anything but protection against resource consumption.<br />
<br />
Other notes:<br />
<br />
The server-side code for this can be deferred until we care to have a response to a DoS attack. However the client-side code for this must be present from day one, otherwise we won't be able to turn on the defense later without fear of disabling legitimate old clients.<br />
<br />
The server should perform as little work as possible before rejecting a token. Every extra CPU cycle it spends in this path is increasing the DoS attack amplification factor.<br />
<br />
The nonce in the prefix string exists to make sure that two successive clients get different prefixes, and thus do not come up with the same counter value (and inadvertently create identical strings, looking like a replay attack). If this proved annoying or expensive, we could instead obligate clients to produce their own nonce.<br />
<br />
TBD: Is this worth it? Should the PoW string go into an HTTP header? (I want it to be cheap to extract, and not clutter logs). Should the error response be a distinctive HTTP error code so our monitoring tools can easily count them? We can also use this feature to slow down online guessing attacks (i.e. trigger it either when getToken1 is called too much or when getToken2 produces too many errors). Since getToken1() includes an email address, we could also requires PoWs for some addresses (e.g. those we know to be under attack) but not others.<br />
<br />
= Glossary =<br />
<br />
This defines some of the jargon we've developed for this protocol.<br />
<br />
* data classes: each type of browser data (bookmarks, passwords, history, etc) can be assigned, by the user, to either class-A or class-B<br />
* class-A: data assigned to this class can be recovered, even if the user forgets their password, by proving control over an email address and resetting the account. It can also be read by Mozilla (since it runs the keyserver and knows kA), or by the user's IdP (by resetting the account without the user's permission).<br />
* class-B: data in this class cannot be recovered if the password is forgotten. It cannot be read by the IdP. Mozilla (via the keyserver) cannot read this data, but can attempt a brute-force dictionary attack against the password.<br />
* kA: the master key for data stored as "class-A", a 32-byte binary string. Individual encryption keys for different datatypes are derived from kA.<br />
* kB: the master key for data stored as "class-B", a 32-byte binary string.<br />
* wrap(kB): an encrypted copy of kB. The keyserver stores wrap(kB) and never sees kB itself. The client (browser) uses a key derived from the user's password to decrypt wrap(kB), obtaining the real kB.<br />
* sessionToken: a long-lived per-device token which allows the device to obtained signed BrowserID certificates for the account's identity (GUID@picl-something.org). This token remains valid until the user revokes it (either by changing their password, or triggering some kind of "revoke a specific device" or "revoke all devices" function).<br />
<br />
= Test Vectors =<br />
<br />
The following example uses a non-ASCII email address of "andré@example.org" (with an accented "e", UTF8 encoding is 616e6472c3a9406578616d706c652e6f7267) and a non-ascii password of "pässwörd" (with accents on "a" and "o", UTF8 encoding is 70c3a4737377c3b67264).<br />
<br />
These test vectors were produced by the python code in https://github.com/warner/picl-spec-crypto . The diagrams may lag behind the latest version of that code.<br />
<br />
== stretch-KDF ==<br />
<br />
email:<br />
616e6472c3a94065<br />
78616d706c652e6f<br />
7267<br />
<br />
password:<br />
70c3a4737377c3b6<br />
7264<br />
<br />
K1 (scrypt input):<br />
f84913e3d8e6d624<br />
689d0a3e9678ac8d<br />
cc79d2c2f3d96414<br />
88cd9d6ef6cd83dd<br />
<br />
K2 (scrypt output):<br />
5b82f146a6412692<br />
3e4167a0350bb181<br />
feba61f63cb17140<br />
12b19cb0be0119c5<br />
<br />
stretchedPW:<br />
c16d46c31bee242c<br />
b31f916e9e38d60b<br />
76431d3f5304549c<br />
c75ae4bc20c7108c<br />
<br />
== main-KDF ==<br />
<br />
mainSalt (normally random):<br />
00f0000000000000<br />
0000000000000000<br />
0000000000000000<br />
000000000000034d<br />
<br />
srpPW:<br />
00f9b71800ab5337<br />
d51177d8fbc682a3<br />
653fa6dae5b87628<br />
eeec43a18af59a9d<br />
<br />
unwrapBKey:<br />
6ea660be9c89ec35<br />
5397f89afb282ea0<br />
bf21095760c8c500<br />
9bbcc894155bbe2a<br />
<br />
internal x (base 10):<br />
8192518690918<br />
99580124814080709381476194749939<br />
03899664126296984459627523279550<br />
<br />
internal x (hex):<br />
b5200337cc3f3f92<br />
6cdddae0b2d31029<br />
c069936a844aff58<br />
779a545be89d0abe<br />
<br />
v (verifier as number) (base 10):<br />
114649<br />
57230405843056840989945621595830<br />
71784395917725741221739574165799<br />
54316134303691657140298181419198<br />
87853709633756255809680435884948<br />
69849281177012209169281795507853<br />
57610332070005048463659745521969<br />
83218225819721112680718485091921<br />
64608360806562626442477160609654<br />
43167308814558974899899506977051<br />
96721477608178869100211706638584<br />
53875100985456239693728258285562<br />
04889672594983678412848291529879<br />
88548996842770025110751388952323<br />
22170663943486107183421205517476<br />
84831590615660554713667726412525<br />
73641352721966728239512914666806<br />
49625530438034148797508015907639<br />
67594925530663571631035463732161<br />
30193328802116982288883318596822<br />
<br />
== SRP Verifier ==<br />
<br />
k (base 10):<br />
259003859907<br />
09503006915442163037721228467470<br />
35652616593381637186118123578112<br />
<br />
srpSalt (normally random):<br />
00f1000000000000<br />
0000000000000000<br />
0000000000000000<br />
0000000000000179<br />
<br />
srpVerifier:<br />
00173ffa0263e63c cfd6791b8ee2a40f<br />
048ec94cd95aa8a3 125726f9805e0c82<br />
83c658dc0b607fbb 25db68e68e93f265<br />
8483049c68af7e82 14c49fde2712a775<br />
b63e545160d64b00 189a86708c69657d<br />
a7a1678eda0cd79f 86b8560ebdb1ffc2<br />
21db360eab901d64 3a75bf1205070a57<br />
91230ae56466b8c3 c1eb656e19b794f1<br />
ea0d2a077b3a7553 50208ea0118fec8c<br />
4b2ec344a05c66ae 1449b32609ca7189<br />
451c259d65bd15b3 4d8729afdb5faff8<br />
af1f3437bbdc0c3d 0b069a8ab2a959c9<br />
0c5a43d42082c774 90f3afcc10ef5648<br />
625c0605cdaace6c 6fdc9e9a7e6635d6<br />
19f50af773452247 0502cab26a52a198<br />
f5b00a2798589165 07b0b4e9ef9524d6<br />
<br />
== SRP B ==<br />
<br />
private b (normally random) (base 10):<br />
1198277<br />
66042000957856349411550091527197<br />
08125226960544768993643095227800<br />
29105361555030352774505625606029<br />
71778328003253459733139844872578<br />
33596964143417216389157582554932<br />
02841499373672188315534280693274<br />
23189198736863575142046053414928<br />
39548731879043135718309539706489<br />
29157321423483527294767988835942<br />
53343430842313006332606344714480<br />
99439808861069316482621424231409<br />
08830704769167700098392968117727<br />
43420990997238759832829219109897<br />
32876428831985487823417312772399<br />
92628295469389578458363237146486<br />
38545526799188280210660508721582<br />
00403102624831815596140094933216<br />
29832845626116777080504444704039<br />
04739431335617585333671378812943<br />
<br />
private b (hex):<br />
00f3000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 000000000000000f<br />
<br />
transmitted srpB:<br />
0022ce5a7b9d8127 7172caa20b0f1efb<br />
4643b3becc535664 73959b07b790d3c3<br />
f08650d5531c19ad 30ebb67bdb481d1d<br />
9cf61bf272f84398 48fdda58a4e6abc5<br />
abb2ac496da5098d 5cbf90e29b4b110e<br />
4e2c033c70af7392 5fa37457ee13ea3e<br />
8fde4ab516dff1c2 ae8e57a6b264fb9d<br />
b637eeeae9b5e43d faba9b329d3b8770<br />
ce89888709e02627 0e474eef822436e6<br />
397562f284778673 a1a7bc12b6883d1c<br />
21fbc27ffb3dbeb8 5efda279a69a1941<br />
4969113f10451603 065f0a0126666456<br />
51dde44a52f4d8de 113e2131321df1bf<br />
4369d2585364f9e5 36c39a4dce33221b<br />
e57d50ddccb4384e 3612bbfd03a268a3<br />
6e4f7e01de651401 e108cc247db50392<br />
<br />
== SRP A ==<br />
<br />
private a (normally random) (base 10):<br />
1193346<br />
47663227291363113405741243413916<br />
43482736314616601220006703889414<br />
28162541137108417166380088052095<br />
43910927476491099816542561560345<br />
50331133015255005622124012256352<br />
06121987030570656676375703406470<br />
63422988042473190059156975005813<br />
46381864669664357382020200036915<br />
26156674010218162984912976536206<br />
14440782978764393137821956464627<br />
16314542157937343986808167341567<br />
89864323268060014089757606109012<br />
50649711198896213496068605039486<br />
22864591676298304745954690086093<br />
75374681084741884719851454277570<br />
80362211874088739962880012800917<br />
05751238004976540634839106888223<br />
63866455314898189520502368799907<br />
19946264951520393624479315579863<br />
<br />
private a (hex):<br />
00f2000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 000000000000d3d7<br />
<br />
transmitted srpA:<br />
007da76cb7e77af5 ab61f334dbd5a958<br />
513afcdf0f47ab99 271fc5f7860fe213<br />
2e5802ca79d2e5c0 64bb80a38ee08771<br />
c98a937696698d87 8d78571568c98a1c<br />
40cc6e7cb101988a 2f9ba3d65679027d<br />
4d9068cb8aad6ebf f0101bab6d52b5fd<br />
fa81d2ed48bba119 d4ecdb7f3f478bd2<br />
36d5749f2275e948 4f2d0a9259d05e49<br />
d78a23dd26c60bfb a04fd346e5146469<br />
a8c3f010a627be81 c58ded1caaef2363<br />
635a45f97ca0d895 cc92ace1d09a99d6<br />
beb6b0dc0829535c 857a419e834db128<br />
64cd6ee8a843563b 0240520ff0195735<br />
cd9d316842d5d3f8 ef7209a0bb4b54ad<br />
7374d73e79be2c39 75632de562c59647<br />
0bb27bad79c3e2fc ddf194e1666cb9fc<br />
<br />
== SRP key-agreement ==<br />
<br />
u:<br />
b284aa1064e87751 50da6b5e2147b47c<br />
a7df505bed94a6f4 bb2ad873332ad732<br />
<br />
S:<br />
0092aaf0f527906a a5e8601f5d707907<br />
a03137e1b601e04b 5a1deb02a981f4be<br />
037b39829a27dba5 0f1b27545ff2e287<br />
29c2b79dcbdd32c9 d6b20d340affab91<br />
a626a8075806c26f e39df91d0ad979f9<br />
b2ee8aad1bc783e7 097407b63bfe58d9<br />
118b9b0b2a7c5c4c debaf8e9a460f4bf<br />
6247b0da34b760a5 9fac891757ddedca<br />
f08eed823b090586 c63009b2d740cc9f<br />
5397be89a2c32cdc fe6d6251ce11e44e<br />
6ecbdd9b6d93f30e 90896d2527564c7e<br />
b9ff70aa91acc0ba c1740a11cd184ffb<br />
989554ab58117c21 96b353d70c356160<br />
100ef5f4c28d19f6 e59ea2508e8e8aac<br />
6001497c27f362ed bafb25e0f045bfdf<br />
9fb02db9c908f103 40a639fe84c31b27<br />
<br />
M1:<br />
27949ec1e0f16256<br />
33436865edb037e2<br />
3eb6bf5cb91873f2<br />
a2729373c2039008<br />
<br />
srpK:<br />
e68fd0112bfa31dc<br />
ffc8e9c96a1cbadb<br />
4c3145978ff35c73<br />
e5bf8d30bbc7499a<br />
<br />
== /auth ==<br />
<br />
srpK:<br />
e68fd0112bfa31dc<br />
ffc8e9c96a1cbadb<br />
4c3145978ff35c73<br />
e5bf8d30bbc7499a<br />
<br />
respHMACkey:<br />
6584613597ef012f<br />
f1752b7869f01d03<br />
c72547a7b7199681<br />
531d9df1991edf23<br />
<br />
respXORkey:<br />
455835926ae37a1b<br />
627bd16affbeeab6<br />
27ecc737121826ca<br />
4a2bac2c100bf417<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
plaintext:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
ciphertext:<br />
253957f10e861c7c<br />
0a12bb0193d384d9<br />
579db544666d50bd<br />
3252d6576c768a68<br />
<br />
MAC:<br />
a98c87f5769ab4cc<br />
ca3df863faeb217e<br />
b16ddc29d712b301<br />
12b446324ee806d6<br />
<br />
response:<br />
253957f10e861c7c<br />
0a12bb0193d384d9<br />
579db544666d50bd<br />
3252d6576c768a68<br />
a98c87f5769ab4cc<br />
ca3df863faeb217e<br />
b16ddc29d712b301<br />
12b446324ee806d6<br />
<br />
== /session ==<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
tokenID:<br />
6dcae8ff8f55a793<br />
a0fa1ed31115451b<br />
4df233b3a0641cc6<br />
18ecadfd1fe4a691<br />
<br />
reqHMACkey:<br />
1640a4e6bc8c8e54<br />
858be9960a8b0740<br />
fa06effdf169246f<br />
52012ae868fc6c48<br />
<br />
respHMACkey:<br />
7f3e075e74523ced<br />
fa817c2fa4ae97e1<br />
e51da38d7a992b66<br />
8a35c86af946b155<br />
<br />
respXORkey:<br />
02977a9167830705<br />
74b610cc25320262<br />
175b45fbd7b26438<br />
f9e200abc029f14e<br />
f38399314b172f1e<br />
e928fcdcd194ab19<br />
92433cab0e94569d<br />
bf623b46dd9fbf55<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
sessionToken:<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
plaintext:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
ciphertext:<br />
8216f812e3068182<br />
fc3f9a47a9bf8ced<br />
87cad7684327f2af<br />
617b9a305cb46fd1<br />
53223b92efb289b9<br />
418156777d3905b6<br />
22f28e18ba21e02a<br />
07db81fd612201ea<br />
<br />
MAC:<br />
639fd132f637abd3<br />
ecd2482ccf11ed76<br />
8cfd6979e1954046<br />
1e8ef5204e66c542<br />
<br />
response:<br />
8216f812e3068182<br />
fc3f9a47a9bf8ced<br />
87cad7684327f2af<br />
617b9a305cb46fd1<br />
53223b92efb289b9<br />
418156777d3905b6<br />
22f28e18ba21e02a<br />
07db81fd612201ea<br />
639fd132f637abd3<br />
ecd2482ccf11ed76<br />
8cfd6979e1954046<br />
1e8ef5204e66c542<br />
<br />
== /account/keys ==<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
tokenID:<br />
d010c94c753c012c<br />
d6801e8beb1aa6cc<br />
3da9ea3de3de1dee<br />
32785dbd99a579e8<br />
<br />
reqHMACkey:<br />
1707b05908acc4dc<br />
cda5b8304d9500d0<br />
8c53e00c31672a53<br />
490dfb5ef2934060<br />
<br />
respHMACkey:<br />
31d0c12186b76897<br />
c3351878a65097cf<br />
d595da4ce48e69a2<br />
485ff1a77c71b0d0<br />
<br />
respXORkey:<br />
eed35591e1f1c43b<br />
7cd604e371b9cfb7<br />
a980c9a36fa737c6<br />
a48c5d60a89fc291<br />
4ec1a2150a0777b7<br />
9a1e8499058cd17a<br />
ebc1441db8b3bf18<br />
2cd0aefa92482692<br />
<br />
kA:<br />
2021222324252627<br />
28292a2b2c2d2e2f<br />
3031323334353637<br />
38393a3b3c3d3e3f<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
plaintext:<br />
2021222324252627<br />
28292a2b2c2d2e2f<br />
3031323334353637<br />
38393a3b3c3d3e3f<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
ciphertext:<br />
cef277b2c5d4e21c<br />
54ff2ec85d94e198<br />
99b1fb905b9201f1<br />
9cb5675b94a2fcae<br />
0e80e0564e4231f0<br />
d257ced249c19f35<br />
bb90164eece6e94f<br />
7489f4a1ce1578cd<br />
<br />
MAC:<br />
86f1c57d2e7f6c97<br />
8181684e189b710f<br />
dd26a3f34e3aaed8<br />
64be9577ae81a256<br />
<br />
response:<br />
cef277b2c5d4e21c<br />
54ff2ec85d94e198<br />
99b1fb905b9201f1<br />
9cb5675b94a2fcae<br />
0e80e0564e4231f0<br />
d257ced249c19f35<br />
bb90164eece6e94f<br />
7489f4a1ce1578cd<br />
86f1c57d2e7f6c97<br />
8181684e189b710f<br />
dd26a3f34e3aaed8<br />
64be9577ae81a256<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
unwrapBKey:<br />
6ea660be9c89ec35<br />
5397f89afb282ea0<br />
bf21095760c8c500<br />
9bbcc894155bbe2a<br />
<br />
kB:<br />
2ee722fdd8ccaa72<br />
1bdeb2d1b76560ef<br />
ef705b04349d9357<br />
c3e592cf4906e075<br />
<br />
== use session (certificate/sign, etc) ==<br />
<br />
sessionToken:<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
tokenID:<br />
639503a218ffbb62<br />
983e9628be5cd64a<br />
0438d0ae81b2b9da<br />
deb900a83470bc6b<br />
<br />
reqHMACkey:<br />
3a0188943837ab22<br />
8fe74e759566d0e4<br />
837cbcc7494157aa<br />
c4da82025b2811b2<br />
<br />
== /password/change ==<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
tokenID:<br />
cafc36360afd92de<br />
5ca21800022a9af1<br />
3a5766b91bd82fd4<br />
0eaa5b6e01489796<br />
<br />
reqHMACkey:<br />
b07c0cf4553e44ff<br />
fe991caa2546b50d<br />
895fb9ac8f8746d2<br />
d29119d9616de193<br />
<br />
respHMACkey:<br />
d2ddfefd1913fa34<br />
48e18abda9b54c92<br />
43fd51bf14dc9091<br />
2179269c0e958a04<br />
<br />
respXORkey:<br />
dcc5425e13b876ea<br />
f1d3aa95a4735622<br />
46994088d86adb5a<br />
526d9f1f5d170254<br />
456dd26dcc54483e<br />
f489d55097b69028<br />
8826f0cf1985a6ad<br />
e3e83461517c8d49<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
accountResetToken:<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
plaintext:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
ciphertext:<br />
5c44c0dd973df06d<br />
795a201e28fed8ad<br />
d608d21b4cff4dcd<br />
caf40584c18a9ccb<br />
85ac10ae08918ef9<br />
3c401f9b5b7b5ee7<br />
58f7221ccd50707a<br />
3b31eeba8da15396<br />
<br />
MAC:<br />
cc3053fe922268d7<br />
9c0dd6eb74bd40f5<br />
07ae2d587483b864<br />
8ef771b699dd39d9<br />
<br />
response:<br />
5c44c0dd973df06d<br />
795a201e28fed8ad<br />
d608d21b4cff4dcd<br />
caf40584c18a9ccb<br />
85ac10ae08918ef9<br />
3c401f9b5b7b5ee7<br />
58f7221ccd50707a<br />
3b31eeba8da15396<br />
cc3053fe922268d7<br />
9c0dd6eb74bd40f5<br />
07ae2d587483b864<br />
8ef771b699dd39d9<br />
<br />
== /account/reset ==<br />
<br />
accountResetToken:<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
tokenID:<br />
a6857e5d53d35073<br />
d50ef2ce2c4dd747<br />
32bb2eae1af5bf79<br />
618ed945e1310792<br />
<br />
reqHMACkey:<br />
47fab27352ee6b48<br />
33938d76519bbdb8<br />
ac7293f8b5e74335<br />
6fdd1d5edf39f52d<br />
<br />
reqXORkey:<br />
82ed612313a11673 95108d7d379b2029<br />
7a539ce9d3861e95 1bf5a9b9cdbfb332<br />
bd6aba056ce0c568 2c5a93963446b1b4<br />
7397c8c24f3a1d67 2a0ddc856474f5b1<br />
33ab884ce33335c1 5578a1a7302933cb<br />
458fbee0a5e52414 c914beb97568a30c<br />
28364dc8fb03ae7c 76a2f324a9a1cee6<br />
71b74aa8906d0e03 39fb52a1bf2b1ef5<br />
ab5d883295db62af 20701cb3af42a09e<br />
c76cda585ab5644b 7250ef7b780537e5<br />
b3e784d37a118bd6 57a0fe29ec6e5cd3<br />
325be8e1d8a3dd71 b360ea266757e463<br />
ada6b0a7a85a8ac0 eed618d9f6ee91ab<br />
1d2f714f224d67db 46843c4e3339de15<br />
efe0297a45f9fe0d 6d768b5c589a290f<br />
11f03237192cc0a3 a02645a810d83bb1<br />
84d582bfb15d2393 3fa4805374da62c6<br />
a2c887b157285c6a 79b47156c9abe02e<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
newSRPv:<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
<br />
plaintext:<br />
4041424344454647 48494a4b4c4d4e4f<br />
5051525354555657 58595a5b5c5d5e5f<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
<br />
ciphertext:<br />
c2ac236057e45034 dd59c7367bd66e66<br />
2a02ceba87d348c2 43acf3e291e2ed6d<br />
ac7bab147df1d479 3d4b82872557a0a5<br />
6286d9d35e2b0c76 3b1ccd947565e4a0<br />
22ba995df22224d0 4469b0b6213822da<br />
549eaff1b4f43505 d805afa86479b21d<br />
39275cd9ea12bf6d 67b3e235b8b0dff7<br />
60a65bb9817c1f12 28ea43b0ae3a0fe4<br />
ba4c992384ca73be 31610da2be53b18f<br />
d67dcb494ba4755a 6341fe6a691426f4<br />
a2f695c26b009ac7 46b1ef38fd7f4dc2<br />
234af9f0c9b2cc60 a271fb377646f572<br />
bcb7a1b6b94b9bd1 ffc709c8e7ff80ba<br />
0c3e605e335c76ca 57952d5f2228cf04<br />
fef1386b54e8ef1c 7c679a4d498b381e<br />
00e12326083dd1b2 b13754b901c92aa0<br />
95c493aea04c3282 2eb5914265cb73d7<br />
b3d996a046394d7b 68a56047d8baf13f<br />
<br />
== /account/destroy ==<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
tokenID:<br />
b2512ff41c4e6d8a<br />
beb3bda37e326f51<br />
cf4efdbf90e50e77<br />
029be2563884b9fe<br />
<br />
reqHMACkey:<br />
75cfa782c19e41f9<br />
c7e125f3dc4c3bf1<br />
0a77c93a9999e06f<br />
b2646b3038e4ea44<br />
<br />
= Keyserver Protocol Summary =<br />
<br />
* POST /account/create (email,srpV,srpSalt) -> ok (server sends verification email)<br />
** creates a user account<br />
* GET /account/devices [sessionToken] () -> list of devices<br />
* GET /account/keys [keyFetchToken,needs-verf] () -> kA/wrap(kB)<br />
** single-use, only if email is verified, encrypted results<br />
* POST /account/reset [authed+encrypted by accountResetToken] (wrap(kB),srpV,srpSalt) -> ok<br />
** single-use, does not require email to be verified, revoke all tokens for account, send notification email to user<br />
* POST /account/delete [authToken] () -> ok, account deleted<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authToken] () -> keyFetchToken, sessionToken<br />
* POST /session/destroy [sessionToken] () -> ok<br />
** for detaching a device, destroy all tokens<br />
* POST /recovery_email/status [sessionToken] () -> "verified" status of email<br />
** use "Accept: text/event-stream" header for server-sent-events; server will send "update" event with the new content of the resource any time it changes.<br />
* POST /recovery_email/resend_code [sessionToken] () -> re-send verification email<br />
* POST /recovery_email/verify_code (code) -> set "verified" flag<br />
** this code will come from a clickable link and is an unauthenticated endpoint<br />
** this could maybe take the recovery method if that would be helpful<br />
** sets verified flag on recovery method<br />
* POST /certificate/sign [sessionToken,needs-verf] (pubkey) -> cert<br />
** only if recovery email is verified<br />
* POST /password/change/start [authToken,needs-verf] () -> accountResetToken, keyFetchToken<br />
* POST /password/forgot/send_code () -> forgotPasswordToken<br />
** sends code to recovery method (email for now, maybe SMS later)<br />
** this is a short code, not a clickable link<br />
* POST /password/forgot/resend_code (forgotPasswordToken) -> re-sends code<br />
* POST /password/forgot/verify_code (forgotPasswordToken, code) -> accountResetToken<br />
** sets verified flag on recovery method<br />
* POST /get_random_bytes<br />
<br />
<br />
== Typical Client Flows ==<br />
<br />
Create account<br />
<br />
* POST /account/create (email,srpV,srpSalt) -> ok (server sends verification email)<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authed with authToken]() -> keyFetchToken, sessionToken<br />
* GET /recovery_email/status [sessionToken] () -> "verified" status<br />
** (optional, only if user requests resend) POST /recovery_email/resend_code [sessionToken]() -> ok<br />
** POST /recovery_email/verify_code (code) -> ok<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
* POST /certificate/sign [sessionToken] (pubkey) -> cert<br />
<br />
<br />
Attach to new device<br />
<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authToken] () -> keyFetchToken, sessionToken<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
** (if unverified-error, do waitUntilEmailVerified, then try again)<br />
* POST /certificate/sign [sessionToken] (pubkey) -> cert<br />
<br />
<br />
Forgot password<br />
<br />
* POST /password/forgot/send_code (email) -> forgotPasswordToken<br />
* POST /password/forgot/verify_code (forgotPasswordToken, code) -> accountResetToken<br />
* POST /account/reset [authed+encrypted by accountResetToken] (0000,srpV,srpSalt) -> ok<br />
* GOTO "Attach to new device"<br />
<br />
<br />
Change Password<br />
<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /password/change/start [authToken] () -> accountResetToken, keyFetchToken<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
* POST /account/reset [authed+encrypted by accountResetToken] (wrap(kB),srpV,srpSalt) -> ok<br />
* GOTO "Attach to new device"</div>Warnerhttps://wiki.mozilla.org/index.php?title=Identity/AttachedServices/KeyServerProtocol&diff=692473Identity/AttachedServices/KeyServerProtocol2013-08-08T23:45:41Z<p>Warner: /* SRP Notes */</p>
<hr />
<div>= PiCL Key Server / IdP Protocol =<br />
<br />
NOTE: This specification is slowly converging on stability (31-Jul-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.<br />
<br />
The server is being developed in https://github.com/mozilla/picl-idp . This repo currently includes a demonstration client (node.js CLI).<br />
<br />
Note that all messages are delivered over an HTTPS connection. The client browser may also implement cert-pinning to improve on the certificate validation process. The protections described below are in addition to those provided by TLS.<br />
<br />
Remaining TODO items:<br />
<br />
* decide on client-side key-stretching parameters: http://keywrapping.appspot.com can help<br />
* finalize SRP questions (definition of M1, generation of a/b)<br />
* finalize proof-of-work/DoS-prevention details<br />
* decide how to rate-limit account-creation calls<br />
* confirm this is actually implementable inside Firefox (especially w.r.t. NSS and Android/Java crypto)<br />
<br />
= Creating The Account =<br />
<br />
The first act performed by a user is to create the account. They enter email+password into their browser, which then does the following steps:<br />
<br />
* decide upon stretching parameters (perhaps consulting the keyserver for recommendations, but imposing a minimum strength requirement). For now we used a fixed {firstPBKDF:20000, scrypt: {N:65536, r:8, p:1}, secondPBKDF:20000}.<br />
* randomly choose a 32-byte mainSalt (this should be unique, but is not secret)<br />
* choose the SRP group parameters (fixed: use the 2048-bit group described below)<br />
* perform key-stretching (as described below), derive stretchedPW and srpPW<br />
* randomly choose a 32-byte srpSalt (unique, but not secret)<br />
* create srpVerifier from srpPW and srpSalt (as described below)<br />
* deliver (email, stretchParams, mainSalt, srpParams, srpSalt) to the keyserver's "POST /account/create" API<br />
<br />
<br />
To limit abuse, the createAccount() should also require a fresh "createToken". This should be created by some other API, outside the scope of this document, that perhaps requires a CAPTCHA or something. createAccount() might also require a proof-of-work token, as described below.<br />
<br />
The server, when creating a new account, creates both kA and wrap(kB) as randomly-generated 256-bit (32-byte) strings. It stores these, along with all the remaining values, indexed by email, in the account table where they can be retrieved by getToken later.<br />
<br />
== Email Verification ==<br />
<br />
To prevent fixation attacks, we require new accounts to verify their configured recovery email address before letting them learn the generated keys or obtain a signed certificate. Nevertheless, we wish clients to forget the user's password while they wait for email verification to complete. To achieve this, clients can obtain a sessionToken before verification, but most APIs that require it will raise errors until verification is finished.<br />
<br />
The server will send email with a URL that contains a long random "verification code" in the "fragment" hash. This URL points to a static page with some javascript that submits the code to the "POST /account/recovery_methods/verify_code" API. The URL can be clicked by any browser (it is not bound to anything), and when the API is hit, the account is marked as verified.<br />
<br />
After the client submits /account/create, it performs the "/session/auth" login sequence below to obtain a sessionToken. It then polls the "GET /account/recovery_methods" (which requires a sessionToken but not account verification) until the user clicks the email link and the API reports verification is complete. Then the client uses "GET /account/keys" and "POST /certificate/sign", described below, to obtain kA, kB, and a signed certificate to talk to the storage server.<br />
<br />
= Login: Obtaining the authToken =<br />
<br />
To connect a browser to an existing account, we use the following login protocol to transform an email+password pair into a single-use authToken. We will use this in the next section to create a session, from which we can obtain encryption keys and signed certificates.<br />
<br />
This protocol starts by using key-stretching to transform the email+password into a "stretchedPW", then feeds this into an SRP protocol to get the authToken.<br />
<br />
[[File:PICL-IdPAuth-auth-start.png|IdP Auth Protocol]]<br />
<br />
This authToken can be used (once) to do one of the following:<br />
<br />
* /session/create: obtain a sessionToken (and keyFetchToken), which enables storage server access<br />
* /password/change: obtain an accountResetToken, to safely reset the account password<br />
* /account/delete: to delete the entire account<br />
<br />
The protocol is designed to enable parallelism between key-stretching and the initial network messages, to reduce the time it takes to connect a browser to the account. In total, the browser requires five messages in four roundtrips (1: /auth/start, 2: /auth/finish, 3: /session/create, 4: /account/keys and /certificate/sign in parallel) before it is ready to talk to the storage server.<br />
<br />
== /auth/start ==<br />
<br />
As soon as the user finishes typing in the email address, the client should send it in the "/auth/start" 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 loginSRPToken that is used to associate this request with the subsequent /auth/finish 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.<br />
<br />
To mitigate DoS abuse, /auth/start may also require a proof-of-work string, described below.<br />
<br />
== Client-Side Key Stretching ==<br />
<br />
"Key Stretching" is the practice of running a password through a computationally-expensive one-way function before using it for encryption or authentication. The goal is to make brute-force dictionary attacks more expensive, by raising the cost of testing each guess.<br />
<br />
To protect the user's class-B data against compromises of our keyserver, we perform this key stretching on the client, rather than on the server. We use the memory-hard "scrypt" function (pronounced "ess-crypt") for this purpose, as motivated by the attacker-cost studies in [[Identity/CryptoIdeas/01-PBKDF-scrypt]]. Slower (low-RAM) devices can still participate by outsourcing the scrypt step to an "scrypt helper". To provide at least minimal protection against a compromise of this helper, we sandwich the scrypt step between two PBKDF2 steps. The complete protocol looks like this:<br />
<br />
[[File:PICL-IdPAuth-stretch-KDF.png|Stretching KDF]]<br />
<br />
Our initial parameter choices for this stretching are to use 20000 iterations of PBKDF2 on each side (40k in all), and to run scrypt with N/r/p set to 64k/8/1. This requires roughly 60MB of RAM for about 1.3 seconds on a 1.8GHz Intel Atom linux box (it will run faster, but use the same memory, on more modern CPUs). We are still studying performance on various platforms to decide what parameters to use in V1: lowering them speeds up the login process, but reduces security (by reducing the cost of an dictionary attack).<br />
<br />
Since the stretching is expected to take a second or two, the client can optimistically start this process (using default parameters) before receiving the auth/start 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 "mainSalt" is added *after* the stretching, to enable this parallelism (at a tiny cost in security).<br />
<br />
After "stretchedPW" is derived, a second HKDF call is used to derive "srpPW" and "unwrapBKey" which will be used later.<br />
<br />
[[File:PICL-IdPAuth-main-KDF.png|masterKey KDF]]<br />
<br />
== SRP Protocol Details ==<br />
<br />
The PiCL client uses the SRP protocol (http://srp.stanford.edu/) to prove that it knows the (stretched) account password without revealing the actual password (or information enabling a brute-force attack) to the server or any eavesdroppers. <br />
<br />
SRP is somewhat underspecified. We use SRP-6a, with SHA256 as the hash, and the 2048-bit modulus defined in RFC 5053 Appendix A. We consistently zero-pad all string values to 256 bytes (2048 bits), and use H(A+B+S) as the key-confirmation message "M1". These details, plus the SRP design papers and RFCs 2945 and 5054, should be enough to build a compatible implementation. The diagrams below and the test vectors at the end of this page can be used to verify compatibility.<br />
<br />
The server should use Jed's SRP module from https://github.com/jedp/node-srp . The client might use SJCL (http://crypto.stanford.edu/sjcl/) or native code (NSS).<br />
<br />
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).<br />
<br />
Note that SRP-6a uses a "k" value which basically encodes the group being used ("N" and "g"). Since all PICL accounts use the same 2048-bit group, they will all use the same "k" value (not to be confused with the per-session shared-secret "K" key that emerges from the protocol). This group's "k" integer is (as a base-10 number):<br />
2590038599070950300691544216303772122846747035652616593381637186118123578112<br />
<br />
=== SRP Verifier Calculation ===<br />
<br />
When the client first creates the account, it must combine the account email address, the stretched password (srpPW), and a randomly-generated srpSalt, to compute the srpVerifier. The server will use this verifier later, to check whether or not the client really knows the password.<br />
<br />
If the server is compromised and an attacker learns the srpVerifier for a given account, they cannot use this to directly log in (the verifier is not "password-equivalent"), but it does allow them to perform an offline brute-force attack against the user's password. In this respect, it is similar to a traditional hashed password. We make these attacks somewhat more expensive by performing the client-side stretching described above, instead of using the raw user password in the SRP calculation.<br />
<br />
[[File:PICL-IdPAuth-SRP-Verifier.png|client-side SRP Verifier calculation]]<br />
<br />
Test vectors are provided at the end of this page. The protocol requires that several values ("a", "b", "srpSalt") are chosen randomly, but for illustrative purposes, the test vectors use carefully-crafted non-random values. For the user's sake, please ensure implementations use proper random values instead of simply copying the test vectors.<br />
<br />
The client sends srpSalt and srpVerifier to the server when it creates the account. It will also re-compute the 'x' value (as an integer) during sign-in. The server will convert the srpVerifier string back into an integer ('v') for use during its own sign-in calculations.<br />
<br />
=== SRP Server-side Sign-In Flow ===<br />
<br />
When the user connects a new device to their account, they use the /auth/start API to start the SRP protocol. This sends the account email address to the server. The server looks up the stored srpVerifier for this account, creates a random 'b' integer, performs some math to compute the "B" number, then converts B into a string known as "srpB". "srpB" is returned to the client, along with srpSalt and the key-stretching parameters. "b" and "srpB" are retained for the subsequent /auth/finish call. '''Note that it is critical that the "b" integer remain secret on the server.''' The server also allocates a random loginSRPToken to connect the two calls.<br />
<br />
[[File:PICL-IdPAuth-SRP-Server.png|server-side SRP]]<br />
<br />
Later, /auth/start will be called with the client's srpA string and its M1 key-confirmation string. srpA is combined with srpB to calculate the "u" integer. srpA is also turned into an integer and used to compute the shared-secret "S" integer. S is then used to compute the shared key "srpK", which is the output of the SRP process.<br />
<br />
=== SRP Client Calculation ===<br />
<br />
While the client is waiting for the response to /auth/start, it begins its key-stretching calculations. Everything else must wait until the response to /auth/start arrives, which includes the key-stretching parameters (which are retroactively confirmed), srpSalt, and the server's generated srpB value.<br />
<br />
[[File:PICL-IdPAuth-SRP-Client.png|client-side SRP]]<br />
<br />
Once the client knows srpSalt, it computes the same "x" integer as it did in the middle of the srpVerifier calculation. It also converts srpB into an integer named "B". Then it creates a random "a" integer, uses it to compute the string "srpA", then combines srpA with the server's srpB to compute the "u" integer. It then combines the static "k", the password-derived "x", the combined "u", and the server's "B", together with some magic math, to derive the "S" integer. If everything went well, the client will compute the same "S" value as the server did. If not (the password was wrong, or the client is talking to a fake server that doesn't really know srpVerifier), then the two "S" values will not match.<br />
<br />
('''Again, it is critical that the client keep its "a" and "x" integers secret, both during and after the protocol run.''')<br />
<br />
To safely tell if the "S" values match, both client and server combine srpA, srpB, and their (independently) generated "S" strings to form a string named "M1". The client sends M1 (along with srpA) in the /session/auth/finish message. The server compares the client's copy of M1 against its own. If they match, the client knew the password and the server can safely respond with the encrypted account data. If they do not match, the client (or a man-in-the-middle attacker) did not know the password, and the client should increment a counter that can trigger defenses against online guessing attacks. The server must then return an error to the client, and '''not''' use or reveal srpK (or the correct M1) in any way.<br />
<br />
Both client and server also hash "S" into "srpK". This is the shared session key, from which specific message encryption and MAC keys are derived (as described below).<br />
<br />
=== SRP Notes ===<br />
<br />
The SRP "g" (generator) and "N" (prime modulus) should use the 2048-bit value from RFC 5054 Appendix A. 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.<br />
<br />
There are several places in SRP where integers (usually in the range 1..N-1) are converted into bytestrings, either for transmission over a wire, or to be passed into a hash function. The SRP spec is somewhat ambiguous about padding here: if the integer happens to be less than 2^2040, the simplest toString() approach will yield a '''255''' byte string, not a 256 byte string. PiCL consistently uses padding, so compatible implementations must prepend one or more NUL bytes to these short strings before transmission or hashing. The test vectors below were brute-forced to ensure that "srpVerifier", "srpA", "srpB", and "S" all wind up with leading zeros, to exercise the padding code in compatible implementations. If you are having problems getting your code to match these results, add some assertions to test that the stringified integers being put into hashes are exactly 256 bytes long.<br />
<br />
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 (/auth/finish).<br />
<br />
The server receives "A" in /auth/finish, 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 keyFetchToken+sessionToken as described below, returning the encrypted/MACed bundle in the response to /auth/finish.<br />
<br />
Outstanding crypto questions:<br />
<br />
* How exactly should the "a" and "b" integers be generated? The issue is of how much bias SRP can tolerate. Ideally these integers are uniformly distributed from 1 to N-1 (inclusive). The only way to obtain a purely uniform distribution from a source of random bytes is try-try-again: pick a (integral-number-of-bytes) number, compare it to the desired range, try again if it falls outside the range. If you get unlucky, this can take a lot of guesses, depending upon how close the range is to a power of two. ECDSA implementations tend to pick a number twice as long as the modulus and then modulo it down (rand(2^4096) % N), which yields a tiny fraction of a bit of bias. The cheaper approach is to do the same with a number of equal length (rand(2^2048) % N), which imposes more bias. Some SRP implementations appear to be satisfied by rand(2^256). We need more review here.<br />
* The original SRP papers defined M1=H(A+B+S) as we use here, but other implementation (in particular RFC2945) uses a curious construct that includes the username, the salt, and an odd XOR combination of N and g. We need to decide what to use for M1.<br />
<br />
== /auth/finish ==<br />
<br />
The client-side SRP calculation results in two values that are sent to the server in the /auth/finish 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.<br />
<br />
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.<br />
<br />
The server then allocates a single-use 32-byte random token named "authToken". It encrypts the token with the session key, and returns a success message with the encrypted bundle.<br />
<br />
All tokens have an associated tokenID, described below. The server needs to maintain a table that maps the tokenID to the token itself, so it can derive other values from the token later. The tokens are also associated with a specific account, so later API requests do not specify an email address or account ID.<br />
<br />
== Decrypting the /auth/finish Response ==<br />
<br />
The SRP session key ("srpK") is used to derive two other keys: respHMACkey and respXORkey.<br />
<br />
[[File:PICL-IdPAuth-encrypt-authToken.png|Decrypting the authToken]]<br />
<br />
The respXORkey is used to encrypt the authToken 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.<br />
<br />
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 returns the authToken value.<br />
<br />
= Creating a Session =<br />
<br />
For login, the single-use authToken is spent on a call to /session/create .<br />
This allocates two new (random 32-byte) tokens: a long-lived "sessionToken", and a single-use "keyFetchToken". The /session/create call returns an encrypted bundle containing the two tokens.<br />
<br />
[[File:PICL-IdPAuth-encrypt-sessionToken.png|Decrypting the sessionToken and keyFetchToken]]<br />
<br />
For calls which accept an authToken, the client uses authToken to derive three values:<br />
<br />
* tokenID<br />
* reqHMACkey<br />
* requestKey<br />
<br />
<br />
The client uses tokenID and reqHMACkey for a HAWK (https://github.com/hueniverse/hawk/) request to the "POST /session/create" API, using tokenID as "credentials.id" and reqHMACkey as "credentials.key". The server uses tokenID to look up the corresponding token, then derives reqHMACkey to validate the request.<br />
<br />
Each authToken-using call then derives additional API-specific values from requestKey. /session/create uses two derived values:<br />
<br />
* respHMACkey<br />
* respXORkey<br />
<br />
<br />
When the server receives a valid /session/create request, it allocates sessionToken and keyFetchToken, concatenates them, encrypts the pair by XORing it with the derived respXORkey, and attaches a MAC generated with respHMACkey. The encrypted MACed bundle is returned to the client.<br />
<br />
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 keyFetchToken and sessionToken values.<br />
<br />
Each authToken is single-use: once a successful request has been made with it, the authToken and its corresponding ID is removed from the server's memory, and subsequent attempts to use it will return a "no such token" error. The token is consumed even if the request fails (e.g. the MAC did not match).<br />
<br />
The authToken can be used (once) by multiple APIs. The server only needs to maintain one table mapping tokenID to authToken, because all these APIs share the same method of deriving a tokenID and reqHMACkey from the authToken.<br />
<br />
When a HAWK request appears, it should look up the tokenID in this table, retrieve the authToken, recompute reqHMACkey and requestKey, validate the request, delete the table entry, then hand the HTTP request and requestKey to the specific API handler. That handler should derive the API-specific values (respHMACkey, respXORkey, etc) to process the request or construct the response.<br />
<br />
The server can support multiple sessions per account (typically one per client device, plus perhaps others for account-management portals). There can also be multiple outstanding keyFetchTokens. The sessionToken lasts forever (until revoked by a password change or explicit revocation command), and can be used an unlimited number of times. The keyFetchToken expires after 60 seconds, and is single-use.<br />
<br />
= Obtaining keys kA and kB =<br />
<br />
Clients which have exchanged an authToken for either a sessionToken or an accountResetToken will also receive a keyFetchToken. This single-use token allows the client to retrieve kA and wrap(kB), which enables the client to encrypt and decrypt browser data (bookmarks, open-tabs, etc) correctly. As above, the keyFetchToken is used to derive tokenID, reqHMACkey, respHMACkey, and respXORkey, which are used in a HAWK request to the "GET /account/keys" API.<br />
<br />
The server pulls kA and wrap(kB) from the account table, concatenates them, encrypts the pair by XORing it with the derived respXORkey, and attaches a MAC generated with respHMACkey.<br />
<br />
[[File:PICL-IdPAuth-keys-server.png|keyFetchToken: server encrypts keys]]<br />
<br />
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 and wrap(kB) values.<br />
<br />
[[File:PICL-IdPAuth-keys-client.png|keyFetchToken: client decrypts keys]]<br />
<br />
Finally, the server-provided wrap(kB) value is simply XORed with the password-derived unwrapBKey (both are 32-byte strings) to obtain kB. There is no MAC on wrap(kB).<br />
<br />
[[File:PICL-IdPAuth-key-unwrap.png|unwrapping kB]]<br />
<br />
"kA" and "kB" enable the browser to encrypt/decrypt synchronized data records. They will be used to derive separate encryption and HMAC keys for each data collection (bookmarks, form-fill data, saved-password, open-tabs, etc). This will allow the user to share some data, but not everything, with a third party. The client may intentionally forget kA and kB (only retaining the derived keys) to reduce the power available to someone who steals their device.<br />
<br />
Note that /account/keys will not succeed until the account's email address has been verified. Also note that each keyFetchToken is single-use and short-lived. The token is consumed even if the request fails (e.g. the MAC does not match).<br />
<br />
= Signing Certificates =<br />
<br />
The sessionToken is used to derive two values:<br />
<br />
* tokenID<br />
* request HMAC key<br />
<br />
<br />
[[File:PICL-IdPAuth-use-session.png|Using the sessionToken, signing certificates]]<br />
<br />
The requestHMACkey is used in a HAWK request to provide integrity over many APIs, including /certificate/sign. requestHMACkey 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.<br />
<br />
For /certificate/sign, it is critical to enable payload verification by setting options.payload=true (on both client and server). Otherwise a man-in-the-middle could submit their own public key, get it signed, and then delete the user's data on the storage servers.<br />
<br />
The following keyserver APIs require a HAWK-protected request that uses the sessionToken. In addition, some require that the account be in the "verified" state:<br />
<br />
* GET /account/devices<br />
* POST /session/destroy<br />
* GET /recovery_email/status<br />
* POST /recovery_email/resend_code<br />
* POST /certificate/sign (requires "verified" account)<br />
<br />
= Resetting The Account =<br />
<br />
The account may be reset in two circumstances: when the user changes their password, and when the user forgets their password. In both cases, the client first obtains an "accountResetToken". This token is then used to change the SRP Verifier and either reset or replace the wrap(kB) value.<br />
<br />
== Changing the Password ==<br />
<br />
When the user wishes to change their password (i.e. they still know the old password), they first use the /auth/start+/auth/finish SRP protocol safely obtain an "authToken". They they use the "/password/change/start" API to exchange the authToken for an accountResetToken and a keyFetchToken.<br />
<br />
[[File:PICL-IdPAuth-encrypt-passwordChange.png|Server encrypts passwordChange response]]<br />
<br />
The accountResetToken will be used below to set the new password safely. The keyFetchToken should be used first, to obtain kB, so the subsequent account reset can replace wrap(kB) with a new value. This allows the password-changing client to retain their class-B data.<br />
<br />
Requiring an authToken proves that the user has provided the correct account password recently. When the account is reset, all active sessions and tokens will be cancelled (disconnecting all devices from the account). The client should immediately establish a new session as described above.<br />
<br />
This API is only used when the user knows their old password: if they have forgotten the password, use the "/password/forgot" APIs below.<br />
<br />
== Handling a Forgotten Password ==<br />
<br />
When the user has forgotten their password, they can use one of their "recovery methods" to obtain an accountResetToken. For now, this means we send a random code to the email address associated with their account. The user must copy this code from the email into their client, whereupon the client will get an accountResetToken that can be delivered to the API below.<br />
<br />
Note that, since the forgotten-password client never learns kB, any class-B data will be lost. This is necessary to protect class-B data from attackers who can read the users's email but do not know the account password (including those who compromise the IdP and the keyserver itself). When using /account/reset below, they will set wrap(kB) to a string of all zeros, which means the server should generate a new random wrap(kB) (just as it does during account creation).<br />
<br />
The /password/forgot/send_code API is used to ask the server to send a recovery code. This takes a recovery method, which for now is just an email address. This API is unauthenticated (after all, the user who has forgotten their password knows nothing but their email address). The server marks the corresponding account as "pending recovery", allocates a random forgotPasswordToken for the account, creates a recovery code, and sends the code (with instructions) via email. The API returns forgotPasswordToken to the client.<br />
<br />
The user must copy the recovery code into the same browser where they started the process. The client then submits the code to /password/forgot/verify_code along with the forgotPasswordToken they received. If they match, the server allocates a accountResetToken and returns it to the client. If they do not match, the server increments a counter (which is used to decide if an online guessing attack is happening).<br />
<br />
forgotPasswordToken can be used three times before it is exhausted. If the user guesses incorrectly this often, the client must call send_code again to get a new token and code. Each account has at most one token+code active at a time.<br />
<br />
The recovery code is initially a random 8-digit decimal number. If an attacker tries to sign in as someone else, hits the "forgot my password" button, then submits a guess to /password/forgot/verify_code, they will have a 1-in-100-million chance of success. If the server detects too many wrong guesses, it should increase the length of new codes. Another defensive technique is to require that users click an email link before being given the code: the server is told when the link is clicked, so the code will not be enabled until the email has been read. It remains to be seen whether this will be sufficient.<br />
<br />
The exact thresholds are TBD, but a nominal goal is to keep the chances of any attack succeeding to below 1-in-a-million per year. To achieve this, we can tolerate 100 verify_code failures in a single year before we must increase the length of the code.<br />
<br />
== Using accountResetToken ==<br />
<br />
An accountResetToken, obtained through either of the methods above, is used to invoke the /account/reset API. If the request is accepted, the server replaces the account password with a new one, updates the wrap(kB) value, cancels all active sessions and tokens (disconnecting all devices from the account), and sends a "your password has been changed" email to the user.<br />
<br />
If the client knew the old password, it can supply a new wrap(kB) value that will yield the same kB key as before, so no data will be lost. If the client did *not* know the old password, then it will supply a wrap(kB) of all-zeros, which tells the server to generate a new random wrap(kB) (just like during account creation), which means the class-B data will be lost.<br />
<br />
The client puts their new password through the same stretching procedure as described in the new-account section above, resulting in a new srpVerifier and unwrapBKey. If they knew the old kB, they XOR it with the new unwrapBKey to obtain a new wrap(kB).<br />
<br />
/account/reset needs request confidentiality, since the arguments include the newly wrapped kB value and the new SRP verifier, both of which enable a brute-force attack against the password. HAWK provides request integrity. The response is a single "ok" or "fail", conveyed by the HTTP headers, so we do not require response confidentiality, and can live without response integrity.<br />
<br />
So the single-use resetToken is used to derive three values:<br />
<br />
* tokenID<br />
* request HMAC key<br />
* request XOR key<br />
<br />
<br />
[[File:PICL-IdPAuth-encrypt-resetAccount.png|Client encrypts resetAccount request]]<br />
<br />
The request data will contain wrap(kB) and the new SRP verifier, concatenated together. Since we always pad the SRP verifier to the full (256-byte) group length, both pieces are fixed-length. We generate enough reqXORkey bytes to cover both values.<br />
<br />
The request data is XORed with requestXORkey, then delivered in the body of a HAWK request that uses tokenID as credentials.id and requestHMACkey as credentials.key . Note: it is critical to include the request body in the HAWK integrity check (options.payload=true, on both client and server), otherwise a man-in-the-middle could substitute their own SRP verifier, giving them control over the account (access to the user's class-A data, and a brute-force attack on their password).<br />
<br />
The client submits other values in the same request:<br />
<br />
* stretchParams<br />
* mainKDFSalt<br />
* srpSalt<br />
<br />
<br />
These values do not require confidentiality, so are not included in the encrypted bundle. They are still protected by the HAWK integrity check. Note that the server should assert that both salts are different than the previously stored values.<br />
<br />
After using /account/reset, clients should immediately perform the login protocol from above. If the old password was forgotten, this is necessary to fetch kA. In either case, a new sessionToken is required, since old sessions and tokens are revoked by /account/reset. Clients should retain the new srpPassword value during this process to avoid needing to run the lengthy key-stretching routine a second time.<br />
<br />
= Deleting The Account =<br />
<br />
When the user wishes to completely delete their account, the browser needs to perform two actions:<br />
<br />
* contact the storage servers and delete all records and collections<br />
* contact the keyserver and delete the account information<br />
<br />
The user should be prompted for their password as confirmation (i.e. a browser in the normal attached-and-synchronizing state should not be able to erase the account information: it must acquire a new authToken first).<br />
<br />
The device then obtains an authToken as described above, then spends it on a HAWK-protected request to the /account/destroy endpoint. This request contains no body and returns only a success code.<br />
<br />
[[File:PICL-IdPAuth-deleteAccount.png|Deleting the Account]]<br />
<br />
= Crypto Notes =<br />
<br />
Strong entropy is needed in the following places:<br />
<br />
* (client) creation of private "a" value inside SRP<br />
* (server) initial creation of kA and wrap(kB)<br />
* (server) creation of private "B" value inside SRP<br />
* (server) creation of signToken and resetToken<br />
<br />
<br />
On the server, code should get entropy from /dev/urandom via a function that uses it, like "crypto.randomBytes()" in node.js or "os.urandom()" in python. On the client, code should combine local entropy with some fetched from the keyserver via getEntropy(), to guard against failures in the local entropy pool. Something like HKDF(IKM=localEntropy+remoteEntropy, salt="", info=KW("mergeEntropy")).<br />
<br />
An HKDF-based stream cipher is used to protect the response for getToken2(), and the request for resetAccount(). HKDF is used to create a number of random bytes equal to the length of the message, then these are XORed with the plaintext to produce the ciphertext. An HMAC is then computed from the ciphertext, to protect the integrity of the message.<br />
<br />
HKDF, like any KDF, is defined to produce output that is indistinguishable from random data ("The HKDF Scheme", http://eprint.iacr.org/2010/264.pdf , by Hugo Krawczyk, section 3). XORing a plaintext with a random keystream to produce ciphertext is a simple and secure approach to data encryption, epitomized by AES-CTR or a stream cipher (http://cr.yp.to/snuffle/design.pdf). HKDF is not the fastest way to generate such a keystream, but it is safe, easy to specify, and easy to implement (just HMAC and XOR).<br />
<br />
Each keystream must be unique. SRP is defined to produce a random session key for each run (as long as at least one of the sides provides a random ephemeral key). We define resetToken to be a single-use randomly-generated value. Hence our two HKDF-XOR keystreams will be unique.<br />
<br />
A slightly more-traditional alternative would be to use AES-CTR (with the same HMAC-SHA256 used here), with a randomly-generated IV. This is equally secure, but requires implementors to obtain an AES library (with CTR mode, which does not seem to be universal). An even more traditional technique would be AES-CBC, which introduces the need for padding and a way to specify the length of the plaintext. The additional specification complexity, plus the library load, leads me to prefer HKDF+XOR.<br />
<br />
kB is equal to the XOR of wrapKey (which is a deterministic function of the user's email address, password, mainSalt, and the stretching parameters) and the server's randomly-generated wrap(kB) value, making kB a random value too. Using XOR as a wrapping function allows us to avoid sending kB or wrap(kB) in the initial createAccount arguments, which are not protected by SRP, and thus would enable an eavesdropper to mount a dictionary attack on the password (using wrap(kB) as their oracle).<br />
<br />
Likewise, allowing the server to generate kA avoids exposure during createAccount. The only point of vulnerability is a forgotten-password resetAccount() message, if an eavesdropper can learn the resetToken. In this case, the attacker might either use the resetToken themselves (then use signToken to learn kA and wrap(kB)), or passively decrypt the resetAccount() arguments to retrieve just wrap(kB). Given wrap(kB) and some encrypted browser data, the attacker can guess passwords (and derive kB, etc) until the data decrypts properly.<br />
<br />
To make this technique safe, any time kB or the password is changed, the mainSalt should be changed too. Otherwise knowledge of both wrap(old-kB) and old-kB would reveal wrapKey, making it easy to deduce the new kB. Changing mainSalt causes wrapKey to change too, preventing this.<br />
<br />
mainSalt is incorporated at the end of the stretching process to allow it to proceed in parallel with the getToken1() call that retrieves the salt. The inputs to the lengthy stretch come entirely from the user (email and password) or are optimistically (stretching parameters). This speedup seems more important than the minor security benefit of including the salt at the beginning of the stretch.<br />
<br />
There is no MAC on wrap(kB). If the keyserver chooses to deliver a bogus wrap(kB) or kA, the client will discover the problem a moment later when it talks to a storage server and attempts to retrieve data from an unrecognized collection-ID (since we intend to derive collection-IDs from the key used to encrypt their data, which will be derived from kA or kB as appropriate). It might be useful to add a checksum to kA and wrap(kB) to detect accidental corruption (e.g. store and deliver kA+SHA256(kA)), but this doesn't protect against intentional changes. We omit this checksum for now, assuming that disks will be reliable enough to let us never experience such failures.<br />
<br />
We use scrypt without a per-user salt. This is safe because the "password" input to scrypt is already diversified by the user's email address. The intention is to allow the scrypt-helper to run on anonymous data, so that an attacker who compromises this helper cannot easily learn the email addresses of the partially-stretched K1 values that it is receiving, confounding their attack.<br />
<br />
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. <br />
For /certificate/sign, we do not need request confidentiality or response confidentiality, since the client's pubkey and the resulting certificate will both be exposed over a similar SSL connection to the storage server later. And it is sufficient to rely on the response integrity provided by SSL, since the client can verify the returned certificate for itself. For the other keyserver APIs protected by HAWK, these properties are either unnecessary, or are provided by additional mechanisms.<br />
<br />
= Proof-Of-Work =<br />
<br />
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(). The server can control how much work is required.<br />
<br />
The getToken1() call looks for a "X-PiCL-PoW:" HTTP header. Most of the time, clients don't supply this header. But if the server responds to the getToken1() call with an error that indicates PoW is required, clients must create a valid PoW string and include it as the value of an "X-PiCL-PoW:" header in their next call to getToken1().<br />
<br />
The server's error message includes two parameters. The first is a "prefix string": the client's PoW string is required to begin with this prefix. The second is a "threshold hash". SHA256(PoWString) is required to be lexicographically earlier than the thresholdHashString (i.e. the numerical value of its hash must be closer to zero than the threshold). The client is expected to concatenate the prefix with a counter, then repeatedly increment the counter and hash the result until they meet the threshold, then re-submit their getToken1() request with the combined prefix+counter string in the header. If the client has spent more than e.g. 10 seconds doing this, the client should probably help the user cancel the operation and try again.<br />
<br />
When a server is under a DoS attack (either via some manual configuration tool or sensed automatically), it should start requiring valid unique X-PiCL-PoW headers. The server should initially require very little work, by using a threshold hash with just a few leading zero bits. If this is insufficient to reduce the attack volume, the threshold should be lowered, requiring even more work (from both the attacker and legitimate clients).<br />
<br />
The server should create a prefix string that contains a parseable timestamp and a random nonce (e.g. "%d-%d-" % (int(time.time()), b32encode(os.urandom(8)))). The server should also decide on a cutoff time (perhaps ten minutes ago). Each server must then maintain a table of "old PoW strings" to prevent replay attacks (these do not need to be shared among all servers: an in-RAM cache is fine).<br />
<br />
When the server receives a proposed PoW string, it first splits off the leading timestamp, and if the timestamp is older than the cutoff time, it rejects the string (either by dropping the connection, or returning a new "PoW required" error if it's feeling nice). Then it hashes the whole string and compares it against the threshold, rejecting those which fail to meet the threshold. Finally, for strings that pass the hash threshold, it checks the "old strings" table, and rejects any that appear on that list.<br />
<br />
If the PoW string makes it past all these checks, the server should add the string to the "old strings" table, then accept the request (i.e. compute an srpB value and add a session-id table entry for the request).<br />
<br />
The old-strings table check should be optimized to reject present strings quickly (i.e. if we are under attack, we should expect to see lots of duplicates of the same string, and must minimize the work we do when this occurs).<br />
<br />
The server can remove values from the old-strings table that have timestamps older than the cutoff time. The server can also discard values at other times (to avoid consuming too much memory), without losing anything but protection against resource consumption.<br />
<br />
Other notes:<br />
<br />
The server-side code for this can be deferred until we care to have a response to a DoS attack. However the client-side code for this must be present from day one, otherwise we won't be able to turn on the defense later without fear of disabling legitimate old clients.<br />
<br />
The server should perform as little work as possible before rejecting a token. Every extra CPU cycle it spends in this path is increasing the DoS attack amplification factor.<br />
<br />
The nonce in the prefix string exists to make sure that two successive clients get different prefixes, and thus do not come up with the same counter value (and inadvertently create identical strings, looking like a replay attack). If this proved annoying or expensive, we could instead obligate clients to produce their own nonce.<br />
<br />
TBD: Is this worth it? Should the PoW string go into an HTTP header? (I want it to be cheap to extract, and not clutter logs). Should the error response be a distinctive HTTP error code so our monitoring tools can easily count them? We can also use this feature to slow down online guessing attacks (i.e. trigger it either when getToken1 is called too much or when getToken2 produces too many errors). Since getToken1() includes an email address, we could also requires PoWs for some addresses (e.g. those we know to be under attack) but not others.<br />
<br />
= Glossary =<br />
<br />
This defines some of the jargon we've developed for this protocol.<br />
<br />
* data classes: each type of browser data (bookmarks, passwords, history, etc) can be assigned, by the user, to either class-A or class-B<br />
* class-A: data assigned to this class can be recovered, even if the user forgets their password, by proving control over an email address and resetting the account. It can also be read by Mozilla (since it runs the keyserver and knows kA), or by the user's IdP (by resetting the account without the user's permission).<br />
* class-B: data in this class cannot be recovered if the password is forgotten. It cannot be read by the IdP. Mozilla (via the keyserver) cannot read this data, but can attempt a brute-force dictionary attack against the password.<br />
* kA: the master key for data stored as "class-A", a 32-byte binary string. Individual encryption keys for different datatypes are derived from kA.<br />
* kB: the master key for data stored as "class-B", a 32-byte binary string.<br />
* wrap(kB): an encrypted copy of kB. The keyserver stores wrap(kB) and never sees kB itself. The client (browser) uses a key derived from the user's password to decrypt wrap(kB), obtaining the real kB.<br />
* sessionToken: a long-lived per-device token which allows the device to obtained signed BrowserID certificates for the account's identity (GUID@picl-something.org). This token remains valid until the user revokes it (either by changing their password, or triggering some kind of "revoke a specific device" or "revoke all devices" function).<br />
<br />
= Test Vectors =<br />
<br />
The following example uses a non-ASCII email address of "andré@example.org" (with an accented "e", UTF8 encoding is 616e6472c3a9406578616d706c652e6f7267) and a non-ascii password of "pässwörd" (with accents on "a" and "o", UTF8 encoding is 70c3a4737377c3b67264).<br />
<br />
These test vectors were produced by the python code in https://github.com/warner/picl-spec-crypto . The diagrams may lag behind the latest version of that code.<br />
<br />
== stretch-KDF ==<br />
<br />
email:<br />
616e6472c3a94065<br />
78616d706c652e6f<br />
7267<br />
<br />
password:<br />
70c3a4737377c3b6<br />
7264<br />
<br />
K1 (scrypt input):<br />
f84913e3d8e6d624<br />
689d0a3e9678ac8d<br />
cc79d2c2f3d96414<br />
88cd9d6ef6cd83dd<br />
<br />
K2 (scrypt output):<br />
5b82f146a6412692<br />
3e4167a0350bb181<br />
feba61f63cb17140<br />
12b19cb0be0119c5<br />
<br />
stretchedPW:<br />
c16d46c31bee242c<br />
b31f916e9e38d60b<br />
76431d3f5304549c<br />
c75ae4bc20c7108c<br />
<br />
== main-KDF ==<br />
<br />
mainSalt (normally random):<br />
00f0000000000000<br />
0000000000000000<br />
0000000000000000<br />
000000000000034d<br />
<br />
srpPW:<br />
00f9b71800ab5337<br />
d51177d8fbc682a3<br />
653fa6dae5b87628<br />
eeec43a18af59a9d<br />
<br />
unwrapBKey:<br />
6ea660be9c89ec35<br />
5397f89afb282ea0<br />
bf21095760c8c500<br />
9bbcc894155bbe2a<br />
<br />
internal x (base 10):<br />
8192518690918<br />
99580124814080709381476194749939<br />
03899664126296984459627523279550<br />
<br />
internal x (hex):<br />
b5200337cc3f3f92<br />
6cdddae0b2d31029<br />
c069936a844aff58<br />
779a545be89d0abe<br />
<br />
v (verifier as number) (base 10):<br />
114649<br />
57230405843056840989945621595830<br />
71784395917725741221739574165799<br />
54316134303691657140298181419198<br />
87853709633756255809680435884948<br />
69849281177012209169281795507853<br />
57610332070005048463659745521969<br />
83218225819721112680718485091921<br />
64608360806562626442477160609654<br />
43167308814558974899899506977051<br />
96721477608178869100211706638584<br />
53875100985456239693728258285562<br />
04889672594983678412848291529879<br />
88548996842770025110751388952323<br />
22170663943486107183421205517476<br />
84831590615660554713667726412525<br />
73641352721966728239512914666806<br />
49625530438034148797508015907639<br />
67594925530663571631035463732161<br />
30193328802116982288883318596822<br />
<br />
== SRP Verifier ==<br />
<br />
k (base 10):<br />
259003859907<br />
09503006915442163037721228467470<br />
35652616593381637186118123578112<br />
<br />
srpSalt (normally random):<br />
00f1000000000000<br />
0000000000000000<br />
0000000000000000<br />
0000000000000179<br />
<br />
srpVerifier:<br />
00173ffa0263e63c cfd6791b8ee2a40f<br />
048ec94cd95aa8a3 125726f9805e0c82<br />
83c658dc0b607fbb 25db68e68e93f265<br />
8483049c68af7e82 14c49fde2712a775<br />
b63e545160d64b00 189a86708c69657d<br />
a7a1678eda0cd79f 86b8560ebdb1ffc2<br />
21db360eab901d64 3a75bf1205070a57<br />
91230ae56466b8c3 c1eb656e19b794f1<br />
ea0d2a077b3a7553 50208ea0118fec8c<br />
4b2ec344a05c66ae 1449b32609ca7189<br />
451c259d65bd15b3 4d8729afdb5faff8<br />
af1f3437bbdc0c3d 0b069a8ab2a959c9<br />
0c5a43d42082c774 90f3afcc10ef5648<br />
625c0605cdaace6c 6fdc9e9a7e6635d6<br />
19f50af773452247 0502cab26a52a198<br />
f5b00a2798589165 07b0b4e9ef9524d6<br />
<br />
== SRP B ==<br />
<br />
private b (normally random) (base 10):<br />
1198277<br />
66042000957856349411550091527197<br />
08125226960544768993643095227800<br />
29105361555030352774505625606029<br />
71778328003253459733139844872578<br />
33596964143417216389157582554932<br />
02841499373672188315534280693274<br />
23189198736863575142046053414928<br />
39548731879043135718309539706489<br />
29157321423483527294767988835942<br />
53343430842313006332606344714480<br />
99439808861069316482621424231409<br />
08830704769167700098392968117727<br />
43420990997238759832829219109897<br />
32876428831985487823417312772399<br />
92628295469389578458363237146486<br />
38545526799188280210660508721582<br />
00403102624831815596140094933216<br />
29832845626116777080504444704039<br />
04739431335617585333671378812943<br />
<br />
private b (hex):<br />
00f3000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 000000000000000f<br />
<br />
transmitted srpB:<br />
0022ce5a7b9d8127 7172caa20b0f1efb<br />
4643b3becc535664 73959b07b790d3c3<br />
f08650d5531c19ad 30ebb67bdb481d1d<br />
9cf61bf272f84398 48fdda58a4e6abc5<br />
abb2ac496da5098d 5cbf90e29b4b110e<br />
4e2c033c70af7392 5fa37457ee13ea3e<br />
8fde4ab516dff1c2 ae8e57a6b264fb9d<br />
b637eeeae9b5e43d faba9b329d3b8770<br />
ce89888709e02627 0e474eef822436e6<br />
397562f284778673 a1a7bc12b6883d1c<br />
21fbc27ffb3dbeb8 5efda279a69a1941<br />
4969113f10451603 065f0a0126666456<br />
51dde44a52f4d8de 113e2131321df1bf<br />
4369d2585364f9e5 36c39a4dce33221b<br />
e57d50ddccb4384e 3612bbfd03a268a3<br />
6e4f7e01de651401 e108cc247db50392<br />
<br />
== SRP A ==<br />
<br />
private a (normally random) (base 10):<br />
1193346<br />
47663227291363113405741243413916<br />
43482736314616601220006703889414<br />
28162541137108417166380088052095<br />
43910927476491099816542561560345<br />
50331133015255005622124012256352<br />
06121987030570656676375703406470<br />
63422988042473190059156975005813<br />
46381864669664357382020200036915<br />
26156674010218162984912976536206<br />
14440782978764393137821956464627<br />
16314542157937343986808167341567<br />
89864323268060014089757606109012<br />
50649711198896213496068605039486<br />
22864591676298304745954690086093<br />
75374681084741884719851454277570<br />
80362211874088739962880012800917<br />
05751238004976540634839106888223<br />
63866455314898189520502368799907<br />
19946264951520393624479315579863<br />
<br />
private a (hex):<br />
00f2000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 000000000000d3d7<br />
<br />
transmitted srpA:<br />
007da76cb7e77af5 ab61f334dbd5a958<br />
513afcdf0f47ab99 271fc5f7860fe213<br />
2e5802ca79d2e5c0 64bb80a38ee08771<br />
c98a937696698d87 8d78571568c98a1c<br />
40cc6e7cb101988a 2f9ba3d65679027d<br />
4d9068cb8aad6ebf f0101bab6d52b5fd<br />
fa81d2ed48bba119 d4ecdb7f3f478bd2<br />
36d5749f2275e948 4f2d0a9259d05e49<br />
d78a23dd26c60bfb a04fd346e5146469<br />
a8c3f010a627be81 c58ded1caaef2363<br />
635a45f97ca0d895 cc92ace1d09a99d6<br />
beb6b0dc0829535c 857a419e834db128<br />
64cd6ee8a843563b 0240520ff0195735<br />
cd9d316842d5d3f8 ef7209a0bb4b54ad<br />
7374d73e79be2c39 75632de562c59647<br />
0bb27bad79c3e2fc ddf194e1666cb9fc<br />
<br />
== SRP key-agreement ==<br />
<br />
u:<br />
b284aa1064e87751 50da6b5e2147b47c<br />
a7df505bed94a6f4 bb2ad873332ad732<br />
<br />
S:<br />
0092aaf0f527906a a5e8601f5d707907<br />
a03137e1b601e04b 5a1deb02a981f4be<br />
037b39829a27dba5 0f1b27545ff2e287<br />
29c2b79dcbdd32c9 d6b20d340affab91<br />
a626a8075806c26f e39df91d0ad979f9<br />
b2ee8aad1bc783e7 097407b63bfe58d9<br />
118b9b0b2a7c5c4c debaf8e9a460f4bf<br />
6247b0da34b760a5 9fac891757ddedca<br />
f08eed823b090586 c63009b2d740cc9f<br />
5397be89a2c32cdc fe6d6251ce11e44e<br />
6ecbdd9b6d93f30e 90896d2527564c7e<br />
b9ff70aa91acc0ba c1740a11cd184ffb<br />
989554ab58117c21 96b353d70c356160<br />
100ef5f4c28d19f6 e59ea2508e8e8aac<br />
6001497c27f362ed bafb25e0f045bfdf<br />
9fb02db9c908f103 40a639fe84c31b27<br />
<br />
M1:<br />
27949ec1e0f16256<br />
33436865edb037e2<br />
3eb6bf5cb91873f2<br />
a2729373c2039008<br />
<br />
srpK:<br />
e68fd0112bfa31dc<br />
ffc8e9c96a1cbadb<br />
4c3145978ff35c73<br />
e5bf8d30bbc7499a<br />
<br />
== /auth ==<br />
<br />
srpK:<br />
e68fd0112bfa31dc<br />
ffc8e9c96a1cbadb<br />
4c3145978ff35c73<br />
e5bf8d30bbc7499a<br />
<br />
respHMACkey:<br />
6584613597ef012f<br />
f1752b7869f01d03<br />
c72547a7b7199681<br />
531d9df1991edf23<br />
<br />
respXORkey:<br />
455835926ae37a1b<br />
627bd16affbeeab6<br />
27ecc737121826ca<br />
4a2bac2c100bf417<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
plaintext:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
ciphertext:<br />
253957f10e861c7c<br />
0a12bb0193d384d9<br />
579db544666d50bd<br />
3252d6576c768a68<br />
<br />
MAC:<br />
a98c87f5769ab4cc<br />
ca3df863faeb217e<br />
b16ddc29d712b301<br />
12b446324ee806d6<br />
<br />
response:<br />
253957f10e861c7c<br />
0a12bb0193d384d9<br />
579db544666d50bd<br />
3252d6576c768a68<br />
a98c87f5769ab4cc<br />
ca3df863faeb217e<br />
b16ddc29d712b301<br />
12b446324ee806d6<br />
<br />
== /session ==<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
tokenID:<br />
6dcae8ff8f55a793<br />
a0fa1ed31115451b<br />
4df233b3a0641cc6<br />
18ecadfd1fe4a691<br />
<br />
reqHMACkey:<br />
1640a4e6bc8c8e54<br />
858be9960a8b0740<br />
fa06effdf169246f<br />
52012ae868fc6c48<br />
<br />
respHMACkey:<br />
7f3e075e74523ced<br />
fa817c2fa4ae97e1<br />
e51da38d7a992b66<br />
8a35c86af946b155<br />
<br />
respXORkey:<br />
02977a9167830705<br />
74b610cc25320262<br />
175b45fbd7b26438<br />
f9e200abc029f14e<br />
f38399314b172f1e<br />
e928fcdcd194ab19<br />
92433cab0e94569d<br />
bf623b46dd9fbf55<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
sessionToken:<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
plaintext:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
ciphertext:<br />
8216f812e3068182<br />
fc3f9a47a9bf8ced<br />
87cad7684327f2af<br />
617b9a305cb46fd1<br />
53223b92efb289b9<br />
418156777d3905b6<br />
22f28e18ba21e02a<br />
07db81fd612201ea<br />
<br />
MAC:<br />
639fd132f637abd3<br />
ecd2482ccf11ed76<br />
8cfd6979e1954046<br />
1e8ef5204e66c542<br />
<br />
response:<br />
8216f812e3068182<br />
fc3f9a47a9bf8ced<br />
87cad7684327f2af<br />
617b9a305cb46fd1<br />
53223b92efb289b9<br />
418156777d3905b6<br />
22f28e18ba21e02a<br />
07db81fd612201ea<br />
639fd132f637abd3<br />
ecd2482ccf11ed76<br />
8cfd6979e1954046<br />
1e8ef5204e66c542<br />
<br />
== /account/keys ==<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
tokenID:<br />
d010c94c753c012c<br />
d6801e8beb1aa6cc<br />
3da9ea3de3de1dee<br />
32785dbd99a579e8<br />
<br />
reqHMACkey:<br />
1707b05908acc4dc<br />
cda5b8304d9500d0<br />
8c53e00c31672a53<br />
490dfb5ef2934060<br />
<br />
respHMACkey:<br />
31d0c12186b76897<br />
c3351878a65097cf<br />
d595da4ce48e69a2<br />
485ff1a77c71b0d0<br />
<br />
respXORkey:<br />
eed35591e1f1c43b<br />
7cd604e371b9cfb7<br />
a980c9a36fa737c6<br />
a48c5d60a89fc291<br />
4ec1a2150a0777b7<br />
9a1e8499058cd17a<br />
ebc1441db8b3bf18<br />
2cd0aefa92482692<br />
<br />
kA:<br />
2021222324252627<br />
28292a2b2c2d2e2f<br />
3031323334353637<br />
38393a3b3c3d3e3f<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
plaintext:<br />
2021222324252627<br />
28292a2b2c2d2e2f<br />
3031323334353637<br />
38393a3b3c3d3e3f<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
ciphertext:<br />
cef277b2c5d4e21c<br />
54ff2ec85d94e198<br />
99b1fb905b9201f1<br />
9cb5675b94a2fcae<br />
0e80e0564e4231f0<br />
d257ced249c19f35<br />
bb90164eece6e94f<br />
7489f4a1ce1578cd<br />
<br />
MAC:<br />
86f1c57d2e7f6c97<br />
8181684e189b710f<br />
dd26a3f34e3aaed8<br />
64be9577ae81a256<br />
<br />
response:<br />
cef277b2c5d4e21c<br />
54ff2ec85d94e198<br />
99b1fb905b9201f1<br />
9cb5675b94a2fcae<br />
0e80e0564e4231f0<br />
d257ced249c19f35<br />
bb90164eece6e94f<br />
7489f4a1ce1578cd<br />
86f1c57d2e7f6c97<br />
8181684e189b710f<br />
dd26a3f34e3aaed8<br />
64be9577ae81a256<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
unwrapBKey:<br />
6ea660be9c89ec35<br />
5397f89afb282ea0<br />
bf21095760c8c500<br />
9bbcc894155bbe2a<br />
<br />
kB:<br />
2ee722fdd8ccaa72<br />
1bdeb2d1b76560ef<br />
ef705b04349d9357<br />
c3e592cf4906e075<br />
<br />
== use session (certificate/sign, etc) ==<br />
<br />
sessionToken:<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
tokenID:<br />
639503a218ffbb62<br />
983e9628be5cd64a<br />
0438d0ae81b2b9da<br />
deb900a83470bc6b<br />
<br />
reqHMACkey:<br />
3a0188943837ab22<br />
8fe74e759566d0e4<br />
837cbcc7494157aa<br />
c4da82025b2811b2<br />
<br />
== /password/change ==<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
tokenID:<br />
cafc36360afd92de<br />
5ca21800022a9af1<br />
3a5766b91bd82fd4<br />
0eaa5b6e01489796<br />
<br />
reqHMACkey:<br />
b07c0cf4553e44ff<br />
fe991caa2546b50d<br />
895fb9ac8f8746d2<br />
d29119d9616de193<br />
<br />
respHMACkey:<br />
d2ddfefd1913fa34<br />
48e18abda9b54c92<br />
43fd51bf14dc9091<br />
2179269c0e958a04<br />
<br />
respXORkey:<br />
dcc5425e13b876ea<br />
f1d3aa95a4735622<br />
46994088d86adb5a<br />
526d9f1f5d170254<br />
456dd26dcc54483e<br />
f489d55097b69028<br />
8826f0cf1985a6ad<br />
e3e83461517c8d49<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
accountResetToken:<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
plaintext:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
ciphertext:<br />
5c44c0dd973df06d<br />
795a201e28fed8ad<br />
d608d21b4cff4dcd<br />
caf40584c18a9ccb<br />
85ac10ae08918ef9<br />
3c401f9b5b7b5ee7<br />
58f7221ccd50707a<br />
3b31eeba8da15396<br />
<br />
MAC:<br />
cc3053fe922268d7<br />
9c0dd6eb74bd40f5<br />
07ae2d587483b864<br />
8ef771b699dd39d9<br />
<br />
response:<br />
5c44c0dd973df06d<br />
795a201e28fed8ad<br />
d608d21b4cff4dcd<br />
caf40584c18a9ccb<br />
85ac10ae08918ef9<br />
3c401f9b5b7b5ee7<br />
58f7221ccd50707a<br />
3b31eeba8da15396<br />
cc3053fe922268d7<br />
9c0dd6eb74bd40f5<br />
07ae2d587483b864<br />
8ef771b699dd39d9<br />
<br />
== /account/reset ==<br />
<br />
accountResetToken:<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
tokenID:<br />
a6857e5d53d35073<br />
d50ef2ce2c4dd747<br />
32bb2eae1af5bf79<br />
618ed945e1310792<br />
<br />
reqHMACkey:<br />
47fab27352ee6b48<br />
33938d76519bbdb8<br />
ac7293f8b5e74335<br />
6fdd1d5edf39f52d<br />
<br />
reqXORkey:<br />
82ed612313a11673 95108d7d379b2029<br />
7a539ce9d3861e95 1bf5a9b9cdbfb332<br />
bd6aba056ce0c568 2c5a93963446b1b4<br />
7397c8c24f3a1d67 2a0ddc856474f5b1<br />
33ab884ce33335c1 5578a1a7302933cb<br />
458fbee0a5e52414 c914beb97568a30c<br />
28364dc8fb03ae7c 76a2f324a9a1cee6<br />
71b74aa8906d0e03 39fb52a1bf2b1ef5<br />
ab5d883295db62af 20701cb3af42a09e<br />
c76cda585ab5644b 7250ef7b780537e5<br />
b3e784d37a118bd6 57a0fe29ec6e5cd3<br />
325be8e1d8a3dd71 b360ea266757e463<br />
ada6b0a7a85a8ac0 eed618d9f6ee91ab<br />
1d2f714f224d67db 46843c4e3339de15<br />
efe0297a45f9fe0d 6d768b5c589a290f<br />
11f03237192cc0a3 a02645a810d83bb1<br />
84d582bfb15d2393 3fa4805374da62c6<br />
a2c887b157285c6a 79b47156c9abe02e<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
newSRPv:<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
<br />
plaintext:<br />
4041424344454647 48494a4b4c4d4e4f<br />
5051525354555657 58595a5b5c5d5e5f<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
<br />
ciphertext:<br />
c2ac236057e45034 dd59c7367bd66e66<br />
2a02ceba87d348c2 43acf3e291e2ed6d<br />
ac7bab147df1d479 3d4b82872557a0a5<br />
6286d9d35e2b0c76 3b1ccd947565e4a0<br />
22ba995df22224d0 4469b0b6213822da<br />
549eaff1b4f43505 d805afa86479b21d<br />
39275cd9ea12bf6d 67b3e235b8b0dff7<br />
60a65bb9817c1f12 28ea43b0ae3a0fe4<br />
ba4c992384ca73be 31610da2be53b18f<br />
d67dcb494ba4755a 6341fe6a691426f4<br />
a2f695c26b009ac7 46b1ef38fd7f4dc2<br />
234af9f0c9b2cc60 a271fb377646f572<br />
bcb7a1b6b94b9bd1 ffc709c8e7ff80ba<br />
0c3e605e335c76ca 57952d5f2228cf04<br />
fef1386b54e8ef1c 7c679a4d498b381e<br />
00e12326083dd1b2 b13754b901c92aa0<br />
95c493aea04c3282 2eb5914265cb73d7<br />
b3d996a046394d7b 68a56047d8baf13f<br />
<br />
== /account/destroy ==<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
tokenID:<br />
b2512ff41c4e6d8a<br />
beb3bda37e326f51<br />
cf4efdbf90e50e77<br />
029be2563884b9fe<br />
<br />
reqHMACkey:<br />
75cfa782c19e41f9<br />
c7e125f3dc4c3bf1<br />
0a77c93a9999e06f<br />
b2646b3038e4ea44<br />
<br />
= Keyserver Protocol Summary =<br />
<br />
* POST /account/create (email,srpV,srpSalt) -> ok (server sends verification email)<br />
** creates a user account<br />
* GET /account/devices [sessionToken] () -> list of devices<br />
* GET /account/keys [keyFetchToken,needs-verf] () -> kA/wrap(kB)<br />
** single-use, only if email is verified, encrypted results<br />
* POST /account/reset [authed+encrypted by accountResetToken] (wrap(kB),srpV,srpSalt) -> ok<br />
** single-use, does not require email to be verified, revoke all tokens for account, send notification email to user<br />
* POST /account/delete [authToken] () -> ok, account deleted<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authToken] () -> keyFetchToken, sessionToken<br />
* POST /session/destroy [sessionToken] () -> ok<br />
** for detaching a device, destroy all tokens<br />
* POST /recovery_email/status [sessionToken] () -> "verified" status of email<br />
** use "Accept: text/event-stream" header for server-sent-events; server will send "update" event with the new content of the resource any time it changes.<br />
* POST /recovery_email/resend_code [sessionToken] () -> re-send verification email<br />
* POST /recovery_email/verify_code (code) -> set "verified" flag<br />
** this code will come from a clickable link and is an unauthenticated endpoint<br />
** this could maybe take the recovery method if that would be helpful<br />
** sets verified flag on recovery method<br />
* POST /certificate/sign [sessionToken,needs-verf] (pubkey) -> cert<br />
** only if recovery email is verified<br />
* POST /password/change/start [authToken,needs-verf] () -> accountResetToken, keyFetchToken<br />
* POST /password/forgot/send_code () -> forgotPasswordToken<br />
** sends code to recovery method (email for now, maybe SMS later)<br />
** this is a short code, not a clickable link<br />
* POST /password/forgot/resend_code (forgotPasswordToken) -> re-sends code<br />
* POST /password/forgot/verify_code (forgotPasswordToken, code) -> accountResetToken<br />
** sets verified flag on recovery method<br />
* POST /get_random_bytes<br />
<br />
<br />
== Typical Client Flows ==<br />
<br />
Create account<br />
<br />
* POST /account/create (email,srpV,srpSalt) -> ok (server sends verification email)<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authed with authToken]() -> keyFetchToken, sessionToken<br />
* GET /recovery_email/status [sessionToken] () -> "verified" status<br />
** (optional, only if user requests resend) POST /recovery_email/resend_code [sessionToken]() -> ok<br />
** POST /recovery_email/verify_code (code) -> ok<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
* POST /certificate/sign [sessionToken] (pubkey) -> cert<br />
<br />
<br />
Attach to new device<br />
<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authToken] () -> keyFetchToken, sessionToken<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
** (if unverified-error, do waitUntilEmailVerified, then try again)<br />
* POST /certificate/sign [sessionToken] (pubkey) -> cert<br />
<br />
<br />
Forgot password<br />
<br />
* POST /password/forgot/send_code (email) -> forgotPasswordToken<br />
* POST /password/forgot/verify_code (forgotPasswordToken, code) -> accountResetToken<br />
* POST /account/reset [authed+encrypted by accountResetToken] (0000,srpV,srpSalt) -> ok<br />
* GOTO "Attach to new device"<br />
<br />
<br />
Change Password<br />
<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /password/change/start [authToken] () -> accountResetToken, keyFetchToken<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
* POST /account/reset [authed+encrypted by accountResetToken] (wrap(kB),srpV,srpSalt) -> ok<br />
* GOTO "Attach to new device"</div>Warnerhttps://wiki.mozilla.org/index.php?title=File:PICL-IdPAuth-SRP-Server.png&diff=692296File:PICL-IdPAuth-SRP-Server.png2013-08-08T19:49:12Z<p>Warner: Warner uploaded a new version of &quot;File:PICL-IdPAuth-SRP-Server.png&quot;</p>
<hr />
<div></div>Warnerhttps://wiki.mozilla.org/index.php?title=File:PICL-IdPAuth-SRP-Client.png&diff=692295File:PICL-IdPAuth-SRP-Client.png2013-08-08T19:48:51Z<p>Warner: Warner uploaded a new version of &quot;File:PICL-IdPAuth-SRP-Client.png&quot;</p>
<hr />
<div></div>Warnerhttps://wiki.mozilla.org/index.php?title=File:PICL-IdPAuth-SRP-Server.png&diff=692292File:PICL-IdPAuth-SRP-Server.png2013-08-08T19:45:15Z<p>Warner: Warner uploaded a new version of &quot;File:PICL-IdPAuth-SRP-Server.png&quot;</p>
<hr />
<div></div>Warnerhttps://wiki.mozilla.org/index.php?title=Identity/AttachedServices/KeyServerProtocol&diff=692253Identity/AttachedServices/KeyServerProtocol2013-08-08T19:19:00Z<p>Warner: /* SRP Protocol Details */</p>
<hr />
<div>= PiCL Key Server / IdP Protocol =<br />
<br />
NOTE: This specification is slowly converging on stability (31-Jul-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.<br />
<br />
The server is being developed in https://github.com/mozilla/picl-idp . This repo currently includes a demonstration client (node.js CLI).<br />
<br />
Note that all messages are delivered over an HTTPS connection. The client browser may also implement cert-pinning to improve on the certificate validation process. The protections described below are in addition to those provided by TLS.<br />
<br />
Remaining TODO items:<br />
<br />
* decide on client-side key-stretching parameters: http://keywrapping.appspot.com can help<br />
* finalize SRP questions (definition of M1, generation of a/b)<br />
* finalize proof-of-work/DoS-prevention details<br />
* decide how to rate-limit account-creation calls<br />
* confirm this is actually implementable inside Firefox (especially w.r.t. NSS and Android/Java crypto)<br />
<br />
= Creating The Account =<br />
<br />
The first act performed by a user is to create the account. They enter email+password into their browser, which then does the following steps:<br />
<br />
* decide upon stretching parameters (perhaps consulting the keyserver for recommendations, but imposing a minimum strength requirement). For now we used a fixed {firstPBKDF:20000, scrypt: {N:65536, r:8, p:1}, secondPBKDF:20000}.<br />
* randomly choose a 32-byte mainSalt (this should be unique, but is not secret)<br />
* choose the SRP group parameters (fixed: use the 2048-bit group described below)<br />
* perform key-stretching (as described below), derive stretchedPW and srpPW<br />
* randomly choose a 32-byte srpSalt (unique, but not secret)<br />
* create srpVerifier from srpPW and srpSalt (as described below)<br />
* deliver (email, stretchParams, mainSalt, srpParams, srpSalt) to the keyserver's "POST /account/create" API<br />
<br />
<br />
To limit abuse, the createAccount() should also require a fresh "createToken". This should be created by some other API, outside the scope of this document, that perhaps requires a CAPTCHA or something. createAccount() might also require a proof-of-work token, as described below.<br />
<br />
The server, when creating a new account, creates both kA and wrap(kB) as randomly-generated 256-bit (32-byte) strings. It stores these, along with all the remaining values, indexed by email, in the account table where they can be retrieved by getToken later.<br />
<br />
== Email Verification ==<br />
<br />
To prevent fixation attacks, we require new accounts to verify their configured recovery email address before letting them learn the generated keys or obtain a signed certificate. Nevertheless, we wish clients to forget the user's password while they wait for email verification to complete. To achieve this, clients can obtain a sessionToken before verification, but most APIs that require it will raise errors until verification is finished.<br />
<br />
The server will send email with a URL that contains a long random "verification code" in the "fragment" hash. This URL points to a static page with some javascript that submits the code to the "POST /account/recovery_methods/verify_code" API. The URL can be clicked by any browser (it is not bound to anything), and when the API is hit, the account is marked as verified.<br />
<br />
After the client submits /account/create, it performs the "/session/auth" login sequence below to obtain a sessionToken. It then polls the "GET /account/recovery_methods" (which requires a sessionToken but not account verification) until the user clicks the email link and the API reports verification is complete. Then the client uses "GET /account/keys" and "POST /certificate/sign", described below, to obtain kA, kB, and a signed certificate to talk to the storage server.<br />
<br />
= Login: Obtaining the authToken =<br />
<br />
To connect a browser to an existing account, we use the following login protocol to transform an email+password pair into a single-use authToken. We will use this in the next section to create a session, from which we can obtain encryption keys and signed certificates.<br />
<br />
This protocol starts by using key-stretching to transform the email+password into a "stretchedPW", then feeds this into an SRP protocol to get the authToken.<br />
<br />
[[File:PICL-IdPAuth-auth-start.png|IdP Auth Protocol]]<br />
<br />
This authToken can be used (once) to do one of the following:<br />
<br />
* /session/create: obtain a sessionToken (and keyFetchToken), which enables storage server access<br />
* /password/change: obtain an accountResetToken, to safely reset the account password<br />
* /account/delete: to delete the entire account<br />
<br />
The protocol is designed to enable parallelism between key-stretching and the initial network messages, to reduce the time it takes to connect a browser to the account. In total, the browser requires five messages in four roundtrips (1: /auth/start, 2: /auth/finish, 3: /session/create, 4: /account/keys and /certificate/sign in parallel) before it is ready to talk to the storage server.<br />
<br />
== /auth/start ==<br />
<br />
As soon as the user finishes typing in the email address, the client should send it in the "/auth/start" 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 loginSRPToken that is used to associate this request with the subsequent /auth/finish 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.<br />
<br />
To mitigate DoS abuse, /auth/start may also require a proof-of-work string, described below.<br />
<br />
== Client-Side Key Stretching ==<br />
<br />
"Key Stretching" is the practice of running a password through a computationally-expensive one-way function before using it for encryption or authentication. The goal is to make brute-force dictionary attacks more expensive, by raising the cost of testing each guess.<br />
<br />
To protect the user's class-B data against compromises of our keyserver, we perform this key stretching on the client, rather than on the server. We use the memory-hard "scrypt" function (pronounced "ess-crypt") for this purpose, as motivated by the attacker-cost studies in [[Identity/CryptoIdeas/01-PBKDF-scrypt]]. Slower (low-RAM) devices can still participate by outsourcing the scrypt step to an "scrypt helper". To provide at least minimal protection against a compromise of this helper, we sandwich the scrypt step between two PBKDF2 steps. The complete protocol looks like this:<br />
<br />
[[File:PICL-IdPAuth-stretch-KDF.png|Stretching KDF]]<br />
<br />
Our initial parameter choices for this stretching are to use 20000 iterations of PBKDF2 on each side (40k in all), and to run scrypt with N/r/p set to 64k/8/1. This requires roughly 60MB of RAM for about 1.3 seconds on a 1.8GHz Intel Atom linux box (it will run faster, but use the same memory, on more modern CPUs). We are still studying performance on various platforms to decide what parameters to use in V1: lowering them speeds up the login process, but reduces security (by reducing the cost of an dictionary attack).<br />
<br />
Since the stretching is expected to take a second or two, the client can optimistically start this process (using default parameters) before receiving the auth/start 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 "mainSalt" is added *after* the stretching, to enable this parallelism (at a tiny cost in security).<br />
<br />
After "stretchedPW" is derived, a second HKDF call is used to derive "srpPW" and "unwrapBKey" which will be used later.<br />
<br />
[[File:PICL-IdPAuth-main-KDF.png|masterKey KDF]]<br />
<br />
== SRP Protocol Details ==<br />
<br />
The PiCL client uses the SRP protocol (http://srp.stanford.edu/) to prove that it knows the (stretched) account password without revealing the actual password (or information enabling a brute-force attack) to the server or any eavesdroppers. <br />
<br />
SRP is somewhat underspecified. We use SRP-6a, with SHA256 as the hash, and the 2048-bit modulus defined in RFC 5053 Appendix A. We consistently zero-pad all string values to 256 bytes (2048 bits), and use H(A+B+S) as the key-confirmation message "M1". These details, plus the SRP design papers and RFCs 2945 and 5054, should be enough to build a compatible implementation. The diagrams below and the test vectors at the end of this page can be used to verify compatibility.<br />
<br />
The server should use Jed's SRP module from https://github.com/jedp/node-srp . The client might use SJCL (http://crypto.stanford.edu/sjcl/) or native code (NSS).<br />
<br />
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).<br />
<br />
Note that SRP-6a uses a "k" value which basically encodes the group being used ("N" and "g"). Since all PICL accounts use the same 2048-bit group, they will all use the same "k" value (not to be confused with the per-session shared-secret "K" key that emerges from the protocol). This group's "k" integer is (as a base-10 number):<br />
2590038599070950300691544216303772122846747035652616593381637186118123578112<br />
<br />
=== SRP Verifier Calculation ===<br />
<br />
When the client first creates the account, it must combine the account email address, the stretched password (srpPW), and a randomly-generated srpSalt, to compute the srpVerifier. The server will use this verifier later, to check whether or not the client really knows the password.<br />
<br />
If the server is compromised and an attacker learns the srpVerifier for a given account, they cannot use this to directly log in (the verifier is not "password-equivalent"), but it does allow them to perform an offline brute-force attack against the user's password. In this respect, it is similar to a traditional hashed password. We make these attacks somewhat more expensive by performing the client-side stretching described above, instead of using the raw user password in the SRP calculation.<br />
<br />
[[File:PICL-IdPAuth-SRP-Verifier.png|client-side SRP Verifier calculation]]<br />
<br />
Test vectors are provided at the end of this page. The protocol requires that several values ("a", "b", "srpSalt") are chosen randomly, but for illustrative purposes, the test vectors use carefully-crafted non-random values. For the user's sake, please ensure implementations use proper random values instead of simply copying the test vectors.<br />
<br />
The client sends srpSalt and srpVerifier to the server when it creates the account. It will also re-compute the 'x' value (as an integer) during sign-in. The server will convert the srpVerifier string back into an integer ('v') for use during its own sign-in calculations.<br />
<br />
=== SRP Server-side Sign-In Flow ===<br />
<br />
When the user connects a new device to their account, they use the /auth/start API to start the SRP protocol. This sends the account email address to the server. The server looks up the stored srpVerifier for this account, creates a random 'b' integer, performs some math to compute the "B" number, then converts B into a string known as "srpB". "srpB" is returned to the client, along with srpSalt and the key-stretching parameters. "b" and "srpB" are retained for the subsequent /auth/finish call. '''Note that it is critical that the "b" integer remain secret on the server.''' The server also allocates a random loginSRPToken to connect the two calls.<br />
<br />
[[File:PICL-IdPAuth-SRP-Server.png|server-side SRP]]<br />
<br />
Later, /auth/start will be called with the client's srpA string and its M1 key-confirmation string. srpA is combined with srpB to calculate the "u" integer. srpA is also turned into an integer and used to compute the shared-secret "S" integer. S is then used to compute the shared key "srpK", which is the output of the SRP process.<br />
<br />
=== SRP Client Calculation ===<br />
<br />
While the client is waiting for the response to /auth/start, it begins its key-stretching calculations. Everything else must wait until the response to /auth/start arrives, which includes the key-stretching parameters (which are retroactively confirmed), srpSalt, and the server's generated srpB value.<br />
<br />
[[File:PICL-IdPAuth-SRP-Client.png|client-side SRP]]<br />
<br />
Once the client knows srpSalt, it computes the same "x" integer as it did in the middle of the srpVerifier calculation. It also converts srpB into an integer named "B". Then it creates a random "a" integer, uses it to compute the string "srpA", then combines srpA with the server's srpB to compute the "u" integer. It then combines the static "k", the password-derived "x", the combined "u", and the server's "B", together with some magic math, to derive the "S" integer. If everything went well, the client will compute the same "S" value as the server did. If not (the password was wrong, or the client is talking to a fake server that doesn't really know srpVerifier), then the two "S" values will not match.<br />
<br />
('''Again, it is critical that the client keep its "a" and "x" integers secret, both during and after the protocol run.''')<br />
<br />
To safely tell if the "S" values match, both client and server combine srpA, srpB, and their (independently) generated "S" strings to form a string named "M1". The client sends M1 (along with srpA) in the /session/auth/finish message. The server compares the client's copy of M1 against its own. If they match, the client knew the password and the server can safely respond with the encrypted account data. If they do not match, the client (or a man-in-the-middle attacker) did not know the password, and the client should increment a counter that can trigger defenses against online guessing attacks. The server must then return an error to the client, and '''not''' use or reveal srpK (or the correct M1) in any way.<br />
<br />
Both client and server also hash "S" into "srpK". This is the shared session key, from which specific message encryption and MAC keys are derived (as described below).<br />
<br />
=== SRP Notes ===<br />
<br />
The SRP "g" (generator) and "N" (prime modulus) should use the 2048-bit value from RFC 5054 Appendix A. 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.<br />
<br />
There are several places in SRP where integers (usually in the range 1..N-1) are converted into bytestrings, either for transmission over a wire, or to be passed into a hash function. The SRP spec is somewhat ambiguous about padding here: if the integer happens to be less than 2^2040, the simplest toString() approach will yield a '''255''' byte string, not a 256 byte string. PiCL consistently uses padding, so compatible implementations must prepend one or more NUL bytes to these short strings before transmission or hashing. The examples above were brute-forced to ensure that "srpVerifier", "srpA", "srpB", and "S" all wind up with leading zeros, to exercise the padding code in compatible implementations. If you are having problems getting your code to match these results, add some assertions to test that the stringified integers being put into hashes are exactly 256 bytes long.<br />
<br />
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 (/auth/finish).<br />
<br />
The server receives "A" in /auth/finish, 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 keyFetchToken+sessionToken as described below, returning the encrypted/MACed bundle in the response to /auth/finish.<br />
<br />
Outstanding crypto questions:<br />
<br />
* How exactly should the "a" and "b" integers be generated? The issue is of how much bias SRP can tolerate. Ideally these integers are uniformly distributed from 1 to N-1 (inclusive). The only way to obtain a purely uniform distribution from a source of random bytes is try-try-again: pick a (integral-number-of-bytes) number, compare it to the desired range, try again if it falls outside the range. If you get unlucky, this can take a lot of guesses, depending upon how close the range is to a power of two. ECDSA implementations tend to pick a number twice as long as the modulus and then modulo it down (rand(2^4096) % N), which yields a tiny fraction of a bit of bias. The cheaper approach is to do the same with a number of equal length (rand(2^2048) % N), which imposes more bias. Some SRP implementations appear to be satisfied by rand(2^256). We need more review here.<br />
* The original SRP papers defined M1=H(A+B+S) as we use here, but other implementation (in particular RFC2945) uses a curious construct that includes the username, the salt, and an odd XOR combination of N and g. We need to decide what to use for M1.<br />
<br />
== /auth/finish ==<br />
<br />
The client-side SRP calculation results in two values that are sent to the server in the /auth/finish 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.<br />
<br />
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.<br />
<br />
The server then allocates a single-use 32-byte random token named "authToken". It encrypts the token with the session key, and returns a success message with the encrypted bundle.<br />
<br />
All tokens have an associated tokenID, described below. The server needs to maintain a table that maps the tokenID to the token itself, so it can derive other values from the token later. The tokens are also associated with a specific account, so later API requests do not specify an email address or account ID.<br />
<br />
== Decrypting the /auth/finish Response ==<br />
<br />
The SRP session key ("srpK") is used to derive two other keys: respHMACkey and respXORkey.<br />
<br />
[[File:PICL-IdPAuth-encrypt-authToken.png|Decrypting the authToken]]<br />
<br />
The respXORkey is used to encrypt the authToken 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.<br />
<br />
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 returns the authToken value.<br />
<br />
= Creating a Session =<br />
<br />
For login, the single-use authToken is spent on a call to /session/create .<br />
This allocates two new (random 32-byte) tokens: a long-lived "sessionToken", and a single-use "keyFetchToken". The /session/create call returns an encrypted bundle containing the two tokens.<br />
<br />
[[File:PICL-IdPAuth-encrypt-sessionToken.png|Decrypting the sessionToken and keyFetchToken]]<br />
<br />
For calls which accept an authToken, the client uses authToken to derive three values:<br />
<br />
* tokenID<br />
* reqHMACkey<br />
* requestKey<br />
<br />
<br />
The client uses tokenID and reqHMACkey for a HAWK (https://github.com/hueniverse/hawk/) request to the "POST /session/create" API, using tokenID as "credentials.id" and reqHMACkey as "credentials.key". The server uses tokenID to look up the corresponding token, then derives reqHMACkey to validate the request.<br />
<br />
Each authToken-using call then derives additional API-specific values from requestKey. /session/create uses two derived values:<br />
<br />
* respHMACkey<br />
* respXORkey<br />
<br />
<br />
When the server receives a valid /session/create request, it allocates sessionToken and keyFetchToken, concatenates them, encrypts the pair by XORing it with the derived respXORkey, and attaches a MAC generated with respHMACkey. The encrypted MACed bundle is returned to the client.<br />
<br />
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 keyFetchToken and sessionToken values.<br />
<br />
Each authToken is single-use: once a successful request has been made with it, the authToken and its corresponding ID is removed from the server's memory, and subsequent attempts to use it will return a "no such token" error. The token is consumed even if the request fails (e.g. the MAC did not match).<br />
<br />
The authToken can be used (once) by multiple APIs. The server only needs to maintain one table mapping tokenID to authToken, because all these APIs share the same method of deriving a tokenID and reqHMACkey from the authToken.<br />
<br />
When a HAWK request appears, it should look up the tokenID in this table, retrieve the authToken, recompute reqHMACkey and requestKey, validate the request, delete the table entry, then hand the HTTP request and requestKey to the specific API handler. That handler should derive the API-specific values (respHMACkey, respXORkey, etc) to process the request or construct the response.<br />
<br />
The server can support multiple sessions per account (typically one per client device, plus perhaps others for account-management portals). There can also be multiple outstanding keyFetchTokens. The sessionToken lasts forever (until revoked by a password change or explicit revocation command), and can be used an unlimited number of times. The keyFetchToken expires after 60 seconds, and is single-use.<br />
<br />
= Obtaining keys kA and kB =<br />
<br />
Clients which have exchanged an authToken for either a sessionToken or an accountResetToken will also receive a keyFetchToken. This single-use token allows the client to retrieve kA and wrap(kB), which enables the client to encrypt and decrypt browser data (bookmarks, open-tabs, etc) correctly. As above, the keyFetchToken is used to derive tokenID, reqHMACkey, respHMACkey, and respXORkey, which are used in a HAWK request to the "GET /account/keys" API.<br />
<br />
The server pulls kA and wrap(kB) from the account table, concatenates them, encrypts the pair by XORing it with the derived respXORkey, and attaches a MAC generated with respHMACkey.<br />
<br />
[[File:PICL-IdPAuth-keys-server.png|keyFetchToken: server encrypts keys]]<br />
<br />
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 and wrap(kB) values.<br />
<br />
[[File:PICL-IdPAuth-keys-client.png|keyFetchToken: client decrypts keys]]<br />
<br />
Finally, the server-provided wrap(kB) value is simply XORed with the password-derived unwrapBKey (both are 32-byte strings) to obtain kB. There is no MAC on wrap(kB).<br />
<br />
[[File:PICL-IdPAuth-key-unwrap.png|unwrapping kB]]<br />
<br />
"kA" and "kB" enable the browser to encrypt/decrypt synchronized data records. They will be used to derive separate encryption and HMAC keys for each data collection (bookmarks, form-fill data, saved-password, open-tabs, etc). This will allow the user to share some data, but not everything, with a third party. The client may intentionally forget kA and kB (only retaining the derived keys) to reduce the power available to someone who steals their device.<br />
<br />
Note that /account/keys will not succeed until the account's email address has been verified. Also note that each keyFetchToken is single-use and short-lived. The token is consumed even if the request fails (e.g. the MAC does not match).<br />
<br />
= Signing Certificates =<br />
<br />
The sessionToken is used to derive two values:<br />
<br />
* tokenID<br />
* request HMAC key<br />
<br />
<br />
[[File:PICL-IdPAuth-use-session.png|Using the sessionToken, signing certificates]]<br />
<br />
The requestHMACkey is used in a HAWK request to provide integrity over many APIs, including /certificate/sign. requestHMACkey 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.<br />
<br />
For /certificate/sign, it is critical to enable payload verification by setting options.payload=true (on both client and server). Otherwise a man-in-the-middle could submit their own public key, get it signed, and then delete the user's data on the storage servers.<br />
<br />
The following keyserver APIs require a HAWK-protected request that uses the sessionToken. In addition, some require that the account be in the "verified" state:<br />
<br />
* GET /account/devices<br />
* POST /session/destroy<br />
* GET /recovery_email/status<br />
* POST /recovery_email/resend_code<br />
* POST /certificate/sign (requires "verified" account)<br />
<br />
= Resetting The Account =<br />
<br />
The account may be reset in two circumstances: when the user changes their password, and when the user forgets their password. In both cases, the client first obtains an "accountResetToken". This token is then used to change the SRP Verifier and either reset or replace the wrap(kB) value.<br />
<br />
== Changing the Password ==<br />
<br />
When the user wishes to change their password (i.e. they still know the old password), they first use the /auth/start+/auth/finish SRP protocol safely obtain an "authToken". They they use the "/password/change/start" API to exchange the authToken for an accountResetToken and a keyFetchToken.<br />
<br />
[[File:PICL-IdPAuth-encrypt-passwordChange.png|Server encrypts passwordChange response]]<br />
<br />
The accountResetToken will be used below to set the new password safely. The keyFetchToken should be used first, to obtain kB, so the subsequent account reset can replace wrap(kB) with a new value. This allows the password-changing client to retain their class-B data.<br />
<br />
Requiring an authToken proves that the user has provided the correct account password recently. When the account is reset, all active sessions and tokens will be cancelled (disconnecting all devices from the account). The client should immediately establish a new session as described above.<br />
<br />
This API is only used when the user knows their old password: if they have forgotten the password, use the "/password/forgot" APIs below.<br />
<br />
== Handling a Forgotten Password ==<br />
<br />
When the user has forgotten their password, they can use one of their "recovery methods" to obtain an accountResetToken. For now, this means we send a random code to the email address associated with their account. The user must copy this code from the email into their client, whereupon the client will get an accountResetToken that can be delivered to the API below.<br />
<br />
Note that, since the forgotten-password client never learns kB, any class-B data will be lost. This is necessary to protect class-B data from attackers who can read the users's email but do not know the account password (including those who compromise the IdP and the keyserver itself). When using /account/reset below, they will set wrap(kB) to a string of all zeros, which means the server should generate a new random wrap(kB) (just as it does during account creation).<br />
<br />
The /password/forgot/send_code API is used to ask the server to send a recovery code. This takes a recovery method, which for now is just an email address. This API is unauthenticated (after all, the user who has forgotten their password knows nothing but their email address). The server marks the corresponding account as "pending recovery", allocates a random forgotPasswordToken for the account, creates a recovery code, and sends the code (with instructions) via email. The API returns forgotPasswordToken to the client.<br />
<br />
The user must copy the recovery code into the same browser where they started the process. The client then submits the code to /password/forgot/verify_code along with the forgotPasswordToken they received. If they match, the server allocates a accountResetToken and returns it to the client. If they do not match, the server increments a counter (which is used to decide if an online guessing attack is happening).<br />
<br />
forgotPasswordToken can be used three times before it is exhausted. If the user guesses incorrectly this often, the client must call send_code again to get a new token and code. Each account has at most one token+code active at a time.<br />
<br />
The recovery code is initially a random 8-digit decimal number. If an attacker tries to sign in as someone else, hits the "forgot my password" button, then submits a guess to /password/forgot/verify_code, they will have a 1-in-100-million chance of success. If the server detects too many wrong guesses, it should increase the length of new codes. Another defensive technique is to require that users click an email link before being given the code: the server is told when the link is clicked, so the code will not be enabled until the email has been read. It remains to be seen whether this will be sufficient.<br />
<br />
The exact thresholds are TBD, but a nominal goal is to keep the chances of any attack succeeding to below 1-in-a-million per year. To achieve this, we can tolerate 100 verify_code failures in a single year before we must increase the length of the code.<br />
<br />
== Using accountResetToken ==<br />
<br />
An accountResetToken, obtained through either of the methods above, is used to invoke the /account/reset API. If the request is accepted, the server replaces the account password with a new one, updates the wrap(kB) value, cancels all active sessions and tokens (disconnecting all devices from the account), and sends a "your password has been changed" email to the user.<br />
<br />
If the client knew the old password, it can supply a new wrap(kB) value that will yield the same kB key as before, so no data will be lost. If the client did *not* know the old password, then it will supply a wrap(kB) of all-zeros, which tells the server to generate a new random wrap(kB) (just like during account creation), which means the class-B data will be lost.<br />
<br />
The client puts their new password through the same stretching procedure as described in the new-account section above, resulting in a new srpVerifier and unwrapBKey. If they knew the old kB, they XOR it with the new unwrapBKey to obtain a new wrap(kB).<br />
<br />
/account/reset needs request confidentiality, since the arguments include the newly wrapped kB value and the new SRP verifier, both of which enable a brute-force attack against the password. HAWK provides request integrity. The response is a single "ok" or "fail", conveyed by the HTTP headers, so we do not require response confidentiality, and can live without response integrity.<br />
<br />
So the single-use resetToken is used to derive three values:<br />
<br />
* tokenID<br />
* request HMAC key<br />
* request XOR key<br />
<br />
<br />
[[File:PICL-IdPAuth-encrypt-resetAccount.png|Client encrypts resetAccount request]]<br />
<br />
The request data will contain wrap(kB) and the new SRP verifier, concatenated together. Since we always pad the SRP verifier to the full (256-byte) group length, both pieces are fixed-length. We generate enough reqXORkey bytes to cover both values.<br />
<br />
The request data is XORed with requestXORkey, then delivered in the body of a HAWK request that uses tokenID as credentials.id and requestHMACkey as credentials.key . Note: it is critical to include the request body in the HAWK integrity check (options.payload=true, on both client and server), otherwise a man-in-the-middle could substitute their own SRP verifier, giving them control over the account (access to the user's class-A data, and a brute-force attack on their password).<br />
<br />
The client submits other values in the same request:<br />
<br />
* stretchParams<br />
* mainKDFSalt<br />
* srpSalt<br />
<br />
<br />
These values do not require confidentiality, so are not included in the encrypted bundle. They are still protected by the HAWK integrity check. Note that the server should assert that both salts are different than the previously stored values.<br />
<br />
After using /account/reset, clients should immediately perform the login protocol from above. If the old password was forgotten, this is necessary to fetch kA. In either case, a new sessionToken is required, since old sessions and tokens are revoked by /account/reset. Clients should retain the new srpPassword value during this process to avoid needing to run the lengthy key-stretching routine a second time.<br />
<br />
= Deleting The Account =<br />
<br />
When the user wishes to completely delete their account, the browser needs to perform two actions:<br />
<br />
* contact the storage servers and delete all records and collections<br />
* contact the keyserver and delete the account information<br />
<br />
The user should be prompted for their password as confirmation (i.e. a browser in the normal attached-and-synchronizing state should not be able to erase the account information: it must acquire a new authToken first).<br />
<br />
The device then obtains an authToken as described above, then spends it on a HAWK-protected request to the /account/destroy endpoint. This request contains no body and returns only a success code.<br />
<br />
[[File:PICL-IdPAuth-deleteAccount.png|Deleting the Account]]<br />
<br />
= Crypto Notes =<br />
<br />
Strong entropy is needed in the following places:<br />
<br />
* (client) creation of private "a" value inside SRP<br />
* (server) initial creation of kA and wrap(kB)<br />
* (server) creation of private "B" value inside SRP<br />
* (server) creation of signToken and resetToken<br />
<br />
<br />
On the server, code should get entropy from /dev/urandom via a function that uses it, like "crypto.randomBytes()" in node.js or "os.urandom()" in python. On the client, code should combine local entropy with some fetched from the keyserver via getEntropy(), to guard against failures in the local entropy pool. Something like HKDF(IKM=localEntropy+remoteEntropy, salt="", info=KW("mergeEntropy")).<br />
<br />
An HKDF-based stream cipher is used to protect the response for getToken2(), and the request for resetAccount(). HKDF is used to create a number of random bytes equal to the length of the message, then these are XORed with the plaintext to produce the ciphertext. An HMAC is then computed from the ciphertext, to protect the integrity of the message.<br />
<br />
HKDF, like any KDF, is defined to produce output that is indistinguishable from random data ("The HKDF Scheme", http://eprint.iacr.org/2010/264.pdf , by Hugo Krawczyk, section 3). XORing a plaintext with a random keystream to produce ciphertext is a simple and secure approach to data encryption, epitomized by AES-CTR or a stream cipher (http://cr.yp.to/snuffle/design.pdf). HKDF is not the fastest way to generate such a keystream, but it is safe, easy to specify, and easy to implement (just HMAC and XOR).<br />
<br />
Each keystream must be unique. SRP is defined to produce a random session key for each run (as long as at least one of the sides provides a random ephemeral key). We define resetToken to be a single-use randomly-generated value. Hence our two HKDF-XOR keystreams will be unique.<br />
<br />
A slightly more-traditional alternative would be to use AES-CTR (with the same HMAC-SHA256 used here), with a randomly-generated IV. This is equally secure, but requires implementors to obtain an AES library (with CTR mode, which does not seem to be universal). An even more traditional technique would be AES-CBC, which introduces the need for padding and a way to specify the length of the plaintext. The additional specification complexity, plus the library load, leads me to prefer HKDF+XOR.<br />
<br />
kB is equal to the XOR of wrapKey (which is a deterministic function of the user's email address, password, mainSalt, and the stretching parameters) and the server's randomly-generated wrap(kB) value, making kB a random value too. Using XOR as a wrapping function allows us to avoid sending kB or wrap(kB) in the initial createAccount arguments, which are not protected by SRP, and thus would enable an eavesdropper to mount a dictionary attack on the password (using wrap(kB) as their oracle).<br />
<br />
Likewise, allowing the server to generate kA avoids exposure during createAccount. The only point of vulnerability is a forgotten-password resetAccount() message, if an eavesdropper can learn the resetToken. In this case, the attacker might either use the resetToken themselves (then use signToken to learn kA and wrap(kB)), or passively decrypt the resetAccount() arguments to retrieve just wrap(kB). Given wrap(kB) and some encrypted browser data, the attacker can guess passwords (and derive kB, etc) until the data decrypts properly.<br />
<br />
To make this technique safe, any time kB or the password is changed, the mainSalt should be changed too. Otherwise knowledge of both wrap(old-kB) and old-kB would reveal wrapKey, making it easy to deduce the new kB. Changing mainSalt causes wrapKey to change too, preventing this.<br />
<br />
mainSalt is incorporated at the end of the stretching process to allow it to proceed in parallel with the getToken1() call that retrieves the salt. The inputs to the lengthy stretch come entirely from the user (email and password) or are optimistically (stretching parameters). This speedup seems more important than the minor security benefit of including the salt at the beginning of the stretch.<br />
<br />
There is no MAC on wrap(kB). If the keyserver chooses to deliver a bogus wrap(kB) or kA, the client will discover the problem a moment later when it talks to a storage server and attempts to retrieve data from an unrecognized collection-ID (since we intend to derive collection-IDs from the key used to encrypt their data, which will be derived from kA or kB as appropriate). It might be useful to add a checksum to kA and wrap(kB) to detect accidental corruption (e.g. store and deliver kA+SHA256(kA)), but this doesn't protect against intentional changes. We omit this checksum for now, assuming that disks will be reliable enough to let us never experience such failures.<br />
<br />
We use scrypt without a per-user salt. This is safe because the "password" input to scrypt is already diversified by the user's email address. The intention is to allow the scrypt-helper to run on anonymous data, so that an attacker who compromises this helper cannot easily learn the email addresses of the partially-stretched K1 values that it is receiving, confounding their attack.<br />
<br />
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. <br />
For /certificate/sign, we do not need request confidentiality or response confidentiality, since the client's pubkey and the resulting certificate will both be exposed over a similar SSL connection to the storage server later. And it is sufficient to rely on the response integrity provided by SSL, since the client can verify the returned certificate for itself. For the other keyserver APIs protected by HAWK, these properties are either unnecessary, or are provided by additional mechanisms.<br />
<br />
= Proof-Of-Work =<br />
<br />
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(). The server can control how much work is required.<br />
<br />
The getToken1() call looks for a "X-PiCL-PoW:" HTTP header. Most of the time, clients don't supply this header. But if the server responds to the getToken1() call with an error that indicates PoW is required, clients must create a valid PoW string and include it as the value of an "X-PiCL-PoW:" header in their next call to getToken1().<br />
<br />
The server's error message includes two parameters. The first is a "prefix string": the client's PoW string is required to begin with this prefix. The second is a "threshold hash". SHA256(PoWString) is required to be lexicographically earlier than the thresholdHashString (i.e. the numerical value of its hash must be closer to zero than the threshold). The client is expected to concatenate the prefix with a counter, then repeatedly increment the counter and hash the result until they meet the threshold, then re-submit their getToken1() request with the combined prefix+counter string in the header. If the client has spent more than e.g. 10 seconds doing this, the client should probably help the user cancel the operation and try again.<br />
<br />
When a server is under a DoS attack (either via some manual configuration tool or sensed automatically), it should start requiring valid unique X-PiCL-PoW headers. The server should initially require very little work, by using a threshold hash with just a few leading zero bits. If this is insufficient to reduce the attack volume, the threshold should be lowered, requiring even more work (from both the attacker and legitimate clients).<br />
<br />
The server should create a prefix string that contains a parseable timestamp and a random nonce (e.g. "%d-%d-" % (int(time.time()), b32encode(os.urandom(8)))). The server should also decide on a cutoff time (perhaps ten minutes ago). Each server must then maintain a table of "old PoW strings" to prevent replay attacks (these do not need to be shared among all servers: an in-RAM cache is fine).<br />
<br />
When the server receives a proposed PoW string, it first splits off the leading timestamp, and if the timestamp is older than the cutoff time, it rejects the string (either by dropping the connection, or returning a new "PoW required" error if it's feeling nice). Then it hashes the whole string and compares it against the threshold, rejecting those which fail to meet the threshold. Finally, for strings that pass the hash threshold, it checks the "old strings" table, and rejects any that appear on that list.<br />
<br />
If the PoW string makes it past all these checks, the server should add the string to the "old strings" table, then accept the request (i.e. compute an srpB value and add a session-id table entry for the request).<br />
<br />
The old-strings table check should be optimized to reject present strings quickly (i.e. if we are under attack, we should expect to see lots of duplicates of the same string, and must minimize the work we do when this occurs).<br />
<br />
The server can remove values from the old-strings table that have timestamps older than the cutoff time. The server can also discard values at other times (to avoid consuming too much memory), without losing anything but protection against resource consumption.<br />
<br />
Other notes:<br />
<br />
The server-side code for this can be deferred until we care to have a response to a DoS attack. However the client-side code for this must be present from day one, otherwise we won't be able to turn on the defense later without fear of disabling legitimate old clients.<br />
<br />
The server should perform as little work as possible before rejecting a token. Every extra CPU cycle it spends in this path is increasing the DoS attack amplification factor.<br />
<br />
The nonce in the prefix string exists to make sure that two successive clients get different prefixes, and thus do not come up with the same counter value (and inadvertently create identical strings, looking like a replay attack). If this proved annoying or expensive, we could instead obligate clients to produce their own nonce.<br />
<br />
TBD: Is this worth it? Should the PoW string go into an HTTP header? (I want it to be cheap to extract, and not clutter logs). Should the error response be a distinctive HTTP error code so our monitoring tools can easily count them? We can also use this feature to slow down online guessing attacks (i.e. trigger it either when getToken1 is called too much or when getToken2 produces too many errors). Since getToken1() includes an email address, we could also requires PoWs for some addresses (e.g. those we know to be under attack) but not others.<br />
<br />
= Glossary =<br />
<br />
This defines some of the jargon we've developed for this protocol.<br />
<br />
* data classes: each type of browser data (bookmarks, passwords, history, etc) can be assigned, by the user, to either class-A or class-B<br />
* class-A: data assigned to this class can be recovered, even if the user forgets their password, by proving control over an email address and resetting the account. It can also be read by Mozilla (since it runs the keyserver and knows kA), or by the user's IdP (by resetting the account without the user's permission).<br />
* class-B: data in this class cannot be recovered if the password is forgotten. It cannot be read by the IdP. Mozilla (via the keyserver) cannot read this data, but can attempt a brute-force dictionary attack against the password.<br />
* kA: the master key for data stored as "class-A", a 32-byte binary string. Individual encryption keys for different datatypes are derived from kA.<br />
* kB: the master key for data stored as "class-B", a 32-byte binary string.<br />
* wrap(kB): an encrypted copy of kB. The keyserver stores wrap(kB) and never sees kB itself. The client (browser) uses a key derived from the user's password to decrypt wrap(kB), obtaining the real kB.<br />
* sessionToken: a long-lived per-device token which allows the device to obtained signed BrowserID certificates for the account's identity (GUID@picl-something.org). This token remains valid until the user revokes it (either by changing their password, or triggering some kind of "revoke a specific device" or "revoke all devices" function).<br />
<br />
= Test Vectors =<br />
<br />
The following example uses a non-ASCII email address of "andré@example.org" (with an accented "e", UTF8 encoding is 616e6472c3a9406578616d706c652e6f7267) and a non-ascii password of "pässwörd" (with accents on "a" and "o", UTF8 encoding is 70c3a4737377c3b67264).<br />
<br />
These test vectors were produced by the python code in https://github.com/warner/picl-spec-crypto . The diagrams may lag behind the latest version of that code.<br />
<br />
== stretch-KDF ==<br />
<br />
email:<br />
616e6472c3a94065<br />
78616d706c652e6f<br />
7267<br />
<br />
password:<br />
70c3a4737377c3b6<br />
7264<br />
<br />
K1 (scrypt input):<br />
f84913e3d8e6d624<br />
689d0a3e9678ac8d<br />
cc79d2c2f3d96414<br />
88cd9d6ef6cd83dd<br />
<br />
K2 (scrypt output):<br />
5b82f146a6412692<br />
3e4167a0350bb181<br />
feba61f63cb17140<br />
12b19cb0be0119c5<br />
<br />
stretchedPW:<br />
c16d46c31bee242c<br />
b31f916e9e38d60b<br />
76431d3f5304549c<br />
c75ae4bc20c7108c<br />
<br />
== main-KDF ==<br />
<br />
mainSalt (normally random):<br />
00f0000000000000<br />
0000000000000000<br />
0000000000000000<br />
000000000000034d<br />
<br />
srpPW:<br />
00f9b71800ab5337<br />
d51177d8fbc682a3<br />
653fa6dae5b87628<br />
eeec43a18af59a9d<br />
<br />
unwrapBKey:<br />
6ea660be9c89ec35<br />
5397f89afb282ea0<br />
bf21095760c8c500<br />
9bbcc894155bbe2a<br />
<br />
internal x (base 10):<br />
8192518690918<br />
99580124814080709381476194749939<br />
03899664126296984459627523279550<br />
<br />
internal x (hex):<br />
b5200337cc3f3f92<br />
6cdddae0b2d31029<br />
c069936a844aff58<br />
779a545be89d0abe<br />
<br />
v (verifier as number) (base 10):<br />
114649<br />
57230405843056840989945621595830<br />
71784395917725741221739574165799<br />
54316134303691657140298181419198<br />
87853709633756255809680435884948<br />
69849281177012209169281795507853<br />
57610332070005048463659745521969<br />
83218225819721112680718485091921<br />
64608360806562626442477160609654<br />
43167308814558974899899506977051<br />
96721477608178869100211706638584<br />
53875100985456239693728258285562<br />
04889672594983678412848291529879<br />
88548996842770025110751388952323<br />
22170663943486107183421205517476<br />
84831590615660554713667726412525<br />
73641352721966728239512914666806<br />
49625530438034148797508015907639<br />
67594925530663571631035463732161<br />
30193328802116982288883318596822<br />
<br />
== SRP Verifier ==<br />
<br />
k (base 10):<br />
259003859907<br />
09503006915442163037721228467470<br />
35652616593381637186118123578112<br />
<br />
srpSalt (normally random):<br />
00f1000000000000<br />
0000000000000000<br />
0000000000000000<br />
0000000000000179<br />
<br />
srpVerifier:<br />
00173ffa0263e63c cfd6791b8ee2a40f<br />
048ec94cd95aa8a3 125726f9805e0c82<br />
83c658dc0b607fbb 25db68e68e93f265<br />
8483049c68af7e82 14c49fde2712a775<br />
b63e545160d64b00 189a86708c69657d<br />
a7a1678eda0cd79f 86b8560ebdb1ffc2<br />
21db360eab901d64 3a75bf1205070a57<br />
91230ae56466b8c3 c1eb656e19b794f1<br />
ea0d2a077b3a7553 50208ea0118fec8c<br />
4b2ec344a05c66ae 1449b32609ca7189<br />
451c259d65bd15b3 4d8729afdb5faff8<br />
af1f3437bbdc0c3d 0b069a8ab2a959c9<br />
0c5a43d42082c774 90f3afcc10ef5648<br />
625c0605cdaace6c 6fdc9e9a7e6635d6<br />
19f50af773452247 0502cab26a52a198<br />
f5b00a2798589165 07b0b4e9ef9524d6<br />
<br />
== SRP B ==<br />
<br />
private b (normally random) (base 10):<br />
1198277<br />
66042000957856349411550091527197<br />
08125226960544768993643095227800<br />
29105361555030352774505625606029<br />
71778328003253459733139844872578<br />
33596964143417216389157582554932<br />
02841499373672188315534280693274<br />
23189198736863575142046053414928<br />
39548731879043135718309539706489<br />
29157321423483527294767988835942<br />
53343430842313006332606344714480<br />
99439808861069316482621424231409<br />
08830704769167700098392968117727<br />
43420990997238759832829219109897<br />
32876428831985487823417312772399<br />
92628295469389578458363237146486<br />
38545526799188280210660508721582<br />
00403102624831815596140094933216<br />
29832845626116777080504444704039<br />
04739431335617585333671378812943<br />
<br />
private b (hex):<br />
00f3000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 000000000000000f<br />
<br />
transmitted srpB:<br />
0022ce5a7b9d8127 7172caa20b0f1efb<br />
4643b3becc535664 73959b07b790d3c3<br />
f08650d5531c19ad 30ebb67bdb481d1d<br />
9cf61bf272f84398 48fdda58a4e6abc5<br />
abb2ac496da5098d 5cbf90e29b4b110e<br />
4e2c033c70af7392 5fa37457ee13ea3e<br />
8fde4ab516dff1c2 ae8e57a6b264fb9d<br />
b637eeeae9b5e43d faba9b329d3b8770<br />
ce89888709e02627 0e474eef822436e6<br />
397562f284778673 a1a7bc12b6883d1c<br />
21fbc27ffb3dbeb8 5efda279a69a1941<br />
4969113f10451603 065f0a0126666456<br />
51dde44a52f4d8de 113e2131321df1bf<br />
4369d2585364f9e5 36c39a4dce33221b<br />
e57d50ddccb4384e 3612bbfd03a268a3<br />
6e4f7e01de651401 e108cc247db50392<br />
<br />
== SRP A ==<br />
<br />
private a (normally random) (base 10):<br />
1193346<br />
47663227291363113405741243413916<br />
43482736314616601220006703889414<br />
28162541137108417166380088052095<br />
43910927476491099816542561560345<br />
50331133015255005622124012256352<br />
06121987030570656676375703406470<br />
63422988042473190059156975005813<br />
46381864669664357382020200036915<br />
26156674010218162984912976536206<br />
14440782978764393137821956464627<br />
16314542157937343986808167341567<br />
89864323268060014089757606109012<br />
50649711198896213496068605039486<br />
22864591676298304745954690086093<br />
75374681084741884719851454277570<br />
80362211874088739962880012800917<br />
05751238004976540634839106888223<br />
63866455314898189520502368799907<br />
19946264951520393624479315579863<br />
<br />
private a (hex):<br />
00f2000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 000000000000d3d7<br />
<br />
transmitted srpA:<br />
007da76cb7e77af5 ab61f334dbd5a958<br />
513afcdf0f47ab99 271fc5f7860fe213<br />
2e5802ca79d2e5c0 64bb80a38ee08771<br />
c98a937696698d87 8d78571568c98a1c<br />
40cc6e7cb101988a 2f9ba3d65679027d<br />
4d9068cb8aad6ebf f0101bab6d52b5fd<br />
fa81d2ed48bba119 d4ecdb7f3f478bd2<br />
36d5749f2275e948 4f2d0a9259d05e49<br />
d78a23dd26c60bfb a04fd346e5146469<br />
a8c3f010a627be81 c58ded1caaef2363<br />
635a45f97ca0d895 cc92ace1d09a99d6<br />
beb6b0dc0829535c 857a419e834db128<br />
64cd6ee8a843563b 0240520ff0195735<br />
cd9d316842d5d3f8 ef7209a0bb4b54ad<br />
7374d73e79be2c39 75632de562c59647<br />
0bb27bad79c3e2fc ddf194e1666cb9fc<br />
<br />
== SRP key-agreement ==<br />
<br />
u:<br />
b284aa1064e87751 50da6b5e2147b47c<br />
a7df505bed94a6f4 bb2ad873332ad732<br />
<br />
S:<br />
0092aaf0f527906a a5e8601f5d707907<br />
a03137e1b601e04b 5a1deb02a981f4be<br />
037b39829a27dba5 0f1b27545ff2e287<br />
29c2b79dcbdd32c9 d6b20d340affab91<br />
a626a8075806c26f e39df91d0ad979f9<br />
b2ee8aad1bc783e7 097407b63bfe58d9<br />
118b9b0b2a7c5c4c debaf8e9a460f4bf<br />
6247b0da34b760a5 9fac891757ddedca<br />
f08eed823b090586 c63009b2d740cc9f<br />
5397be89a2c32cdc fe6d6251ce11e44e<br />
6ecbdd9b6d93f30e 90896d2527564c7e<br />
b9ff70aa91acc0ba c1740a11cd184ffb<br />
989554ab58117c21 96b353d70c356160<br />
100ef5f4c28d19f6 e59ea2508e8e8aac<br />
6001497c27f362ed bafb25e0f045bfdf<br />
9fb02db9c908f103 40a639fe84c31b27<br />
<br />
M1:<br />
27949ec1e0f16256<br />
33436865edb037e2<br />
3eb6bf5cb91873f2<br />
a2729373c2039008<br />
<br />
srpK:<br />
e68fd0112bfa31dc<br />
ffc8e9c96a1cbadb<br />
4c3145978ff35c73<br />
e5bf8d30bbc7499a<br />
<br />
== /auth ==<br />
<br />
srpK:<br />
e68fd0112bfa31dc<br />
ffc8e9c96a1cbadb<br />
4c3145978ff35c73<br />
e5bf8d30bbc7499a<br />
<br />
respHMACkey:<br />
6584613597ef012f<br />
f1752b7869f01d03<br />
c72547a7b7199681<br />
531d9df1991edf23<br />
<br />
respXORkey:<br />
455835926ae37a1b<br />
627bd16affbeeab6<br />
27ecc737121826ca<br />
4a2bac2c100bf417<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
plaintext:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
ciphertext:<br />
253957f10e861c7c<br />
0a12bb0193d384d9<br />
579db544666d50bd<br />
3252d6576c768a68<br />
<br />
MAC:<br />
a98c87f5769ab4cc<br />
ca3df863faeb217e<br />
b16ddc29d712b301<br />
12b446324ee806d6<br />
<br />
response:<br />
253957f10e861c7c<br />
0a12bb0193d384d9<br />
579db544666d50bd<br />
3252d6576c768a68<br />
a98c87f5769ab4cc<br />
ca3df863faeb217e<br />
b16ddc29d712b301<br />
12b446324ee806d6<br />
<br />
== /session ==<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
tokenID:<br />
6dcae8ff8f55a793<br />
a0fa1ed31115451b<br />
4df233b3a0641cc6<br />
18ecadfd1fe4a691<br />
<br />
reqHMACkey:<br />
1640a4e6bc8c8e54<br />
858be9960a8b0740<br />
fa06effdf169246f<br />
52012ae868fc6c48<br />
<br />
respHMACkey:<br />
7f3e075e74523ced<br />
fa817c2fa4ae97e1<br />
e51da38d7a992b66<br />
8a35c86af946b155<br />
<br />
respXORkey:<br />
02977a9167830705<br />
74b610cc25320262<br />
175b45fbd7b26438<br />
f9e200abc029f14e<br />
f38399314b172f1e<br />
e928fcdcd194ab19<br />
92433cab0e94569d<br />
bf623b46dd9fbf55<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
sessionToken:<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
plaintext:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
ciphertext:<br />
8216f812e3068182<br />
fc3f9a47a9bf8ced<br />
87cad7684327f2af<br />
617b9a305cb46fd1<br />
53223b92efb289b9<br />
418156777d3905b6<br />
22f28e18ba21e02a<br />
07db81fd612201ea<br />
<br />
MAC:<br />
639fd132f637abd3<br />
ecd2482ccf11ed76<br />
8cfd6979e1954046<br />
1e8ef5204e66c542<br />
<br />
response:<br />
8216f812e3068182<br />
fc3f9a47a9bf8ced<br />
87cad7684327f2af<br />
617b9a305cb46fd1<br />
53223b92efb289b9<br />
418156777d3905b6<br />
22f28e18ba21e02a<br />
07db81fd612201ea<br />
639fd132f637abd3<br />
ecd2482ccf11ed76<br />
8cfd6979e1954046<br />
1e8ef5204e66c542<br />
<br />
== /account/keys ==<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
tokenID:<br />
d010c94c753c012c<br />
d6801e8beb1aa6cc<br />
3da9ea3de3de1dee<br />
32785dbd99a579e8<br />
<br />
reqHMACkey:<br />
1707b05908acc4dc<br />
cda5b8304d9500d0<br />
8c53e00c31672a53<br />
490dfb5ef2934060<br />
<br />
respHMACkey:<br />
31d0c12186b76897<br />
c3351878a65097cf<br />
d595da4ce48e69a2<br />
485ff1a77c71b0d0<br />
<br />
respXORkey:<br />
eed35591e1f1c43b<br />
7cd604e371b9cfb7<br />
a980c9a36fa737c6<br />
a48c5d60a89fc291<br />
4ec1a2150a0777b7<br />
9a1e8499058cd17a<br />
ebc1441db8b3bf18<br />
2cd0aefa92482692<br />
<br />
kA:<br />
2021222324252627<br />
28292a2b2c2d2e2f<br />
3031323334353637<br />
38393a3b3c3d3e3f<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
plaintext:<br />
2021222324252627<br />
28292a2b2c2d2e2f<br />
3031323334353637<br />
38393a3b3c3d3e3f<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
ciphertext:<br />
cef277b2c5d4e21c<br />
54ff2ec85d94e198<br />
99b1fb905b9201f1<br />
9cb5675b94a2fcae<br />
0e80e0564e4231f0<br />
d257ced249c19f35<br />
bb90164eece6e94f<br />
7489f4a1ce1578cd<br />
<br />
MAC:<br />
86f1c57d2e7f6c97<br />
8181684e189b710f<br />
dd26a3f34e3aaed8<br />
64be9577ae81a256<br />
<br />
response:<br />
cef277b2c5d4e21c<br />
54ff2ec85d94e198<br />
99b1fb905b9201f1<br />
9cb5675b94a2fcae<br />
0e80e0564e4231f0<br />
d257ced249c19f35<br />
bb90164eece6e94f<br />
7489f4a1ce1578cd<br />
86f1c57d2e7f6c97<br />
8181684e189b710f<br />
dd26a3f34e3aaed8<br />
64be9577ae81a256<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
unwrapBKey:<br />
6ea660be9c89ec35<br />
5397f89afb282ea0<br />
bf21095760c8c500<br />
9bbcc894155bbe2a<br />
<br />
kB:<br />
2ee722fdd8ccaa72<br />
1bdeb2d1b76560ef<br />
ef705b04349d9357<br />
c3e592cf4906e075<br />
<br />
== use session (certificate/sign, etc) ==<br />
<br />
sessionToken:<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
tokenID:<br />
639503a218ffbb62<br />
983e9628be5cd64a<br />
0438d0ae81b2b9da<br />
deb900a83470bc6b<br />
<br />
reqHMACkey:<br />
3a0188943837ab22<br />
8fe74e759566d0e4<br />
837cbcc7494157aa<br />
c4da82025b2811b2<br />
<br />
== /password/change ==<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
tokenID:<br />
cafc36360afd92de<br />
5ca21800022a9af1<br />
3a5766b91bd82fd4<br />
0eaa5b6e01489796<br />
<br />
reqHMACkey:<br />
b07c0cf4553e44ff<br />
fe991caa2546b50d<br />
895fb9ac8f8746d2<br />
d29119d9616de193<br />
<br />
respHMACkey:<br />
d2ddfefd1913fa34<br />
48e18abda9b54c92<br />
43fd51bf14dc9091<br />
2179269c0e958a04<br />
<br />
respXORkey:<br />
dcc5425e13b876ea<br />
f1d3aa95a4735622<br />
46994088d86adb5a<br />
526d9f1f5d170254<br />
456dd26dcc54483e<br />
f489d55097b69028<br />
8826f0cf1985a6ad<br />
e3e83461517c8d49<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
accountResetToken:<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
plaintext:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
ciphertext:<br />
5c44c0dd973df06d<br />
795a201e28fed8ad<br />
d608d21b4cff4dcd<br />
caf40584c18a9ccb<br />
85ac10ae08918ef9<br />
3c401f9b5b7b5ee7<br />
58f7221ccd50707a<br />
3b31eeba8da15396<br />
<br />
MAC:<br />
cc3053fe922268d7<br />
9c0dd6eb74bd40f5<br />
07ae2d587483b864<br />
8ef771b699dd39d9<br />
<br />
response:<br />
5c44c0dd973df06d<br />
795a201e28fed8ad<br />
d608d21b4cff4dcd<br />
caf40584c18a9ccb<br />
85ac10ae08918ef9<br />
3c401f9b5b7b5ee7<br />
58f7221ccd50707a<br />
3b31eeba8da15396<br />
cc3053fe922268d7<br />
9c0dd6eb74bd40f5<br />
07ae2d587483b864<br />
8ef771b699dd39d9<br />
<br />
== /account/reset ==<br />
<br />
accountResetToken:<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
tokenID:<br />
a6857e5d53d35073<br />
d50ef2ce2c4dd747<br />
32bb2eae1af5bf79<br />
618ed945e1310792<br />
<br />
reqHMACkey:<br />
47fab27352ee6b48<br />
33938d76519bbdb8<br />
ac7293f8b5e74335<br />
6fdd1d5edf39f52d<br />
<br />
reqXORkey:<br />
82ed612313a11673 95108d7d379b2029<br />
7a539ce9d3861e95 1bf5a9b9cdbfb332<br />
bd6aba056ce0c568 2c5a93963446b1b4<br />
7397c8c24f3a1d67 2a0ddc856474f5b1<br />
33ab884ce33335c1 5578a1a7302933cb<br />
458fbee0a5e52414 c914beb97568a30c<br />
28364dc8fb03ae7c 76a2f324a9a1cee6<br />
71b74aa8906d0e03 39fb52a1bf2b1ef5<br />
ab5d883295db62af 20701cb3af42a09e<br />
c76cda585ab5644b 7250ef7b780537e5<br />
b3e784d37a118bd6 57a0fe29ec6e5cd3<br />
325be8e1d8a3dd71 b360ea266757e463<br />
ada6b0a7a85a8ac0 eed618d9f6ee91ab<br />
1d2f714f224d67db 46843c4e3339de15<br />
efe0297a45f9fe0d 6d768b5c589a290f<br />
11f03237192cc0a3 a02645a810d83bb1<br />
84d582bfb15d2393 3fa4805374da62c6<br />
a2c887b157285c6a 79b47156c9abe02e<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
newSRPv:<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
<br />
plaintext:<br />
4041424344454647 48494a4b4c4d4e4f<br />
5051525354555657 58595a5b5c5d5e5f<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
<br />
ciphertext:<br />
c2ac236057e45034 dd59c7367bd66e66<br />
2a02ceba87d348c2 43acf3e291e2ed6d<br />
ac7bab147df1d479 3d4b82872557a0a5<br />
6286d9d35e2b0c76 3b1ccd947565e4a0<br />
22ba995df22224d0 4469b0b6213822da<br />
549eaff1b4f43505 d805afa86479b21d<br />
39275cd9ea12bf6d 67b3e235b8b0dff7<br />
60a65bb9817c1f12 28ea43b0ae3a0fe4<br />
ba4c992384ca73be 31610da2be53b18f<br />
d67dcb494ba4755a 6341fe6a691426f4<br />
a2f695c26b009ac7 46b1ef38fd7f4dc2<br />
234af9f0c9b2cc60 a271fb377646f572<br />
bcb7a1b6b94b9bd1 ffc709c8e7ff80ba<br />
0c3e605e335c76ca 57952d5f2228cf04<br />
fef1386b54e8ef1c 7c679a4d498b381e<br />
00e12326083dd1b2 b13754b901c92aa0<br />
95c493aea04c3282 2eb5914265cb73d7<br />
b3d996a046394d7b 68a56047d8baf13f<br />
<br />
== /account/destroy ==<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
tokenID:<br />
b2512ff41c4e6d8a<br />
beb3bda37e326f51<br />
cf4efdbf90e50e77<br />
029be2563884b9fe<br />
<br />
reqHMACkey:<br />
75cfa782c19e41f9<br />
c7e125f3dc4c3bf1<br />
0a77c93a9999e06f<br />
b2646b3038e4ea44<br />
<br />
= Keyserver Protocol Summary =<br />
<br />
* POST /account/create (email,srpV,srpSalt) -> ok (server sends verification email)<br />
** creates a user account<br />
* GET /account/devices [sessionToken] () -> list of devices<br />
* GET /account/keys [keyFetchToken,needs-verf] () -> kA/wrap(kB)<br />
** single-use, only if email is verified, encrypted results<br />
* POST /account/reset [authed+encrypted by accountResetToken] (wrap(kB),srpV,srpSalt) -> ok<br />
** single-use, does not require email to be verified, revoke all tokens for account, send notification email to user<br />
* POST /account/delete [authToken] () -> ok, account deleted<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authToken] () -> keyFetchToken, sessionToken<br />
* POST /session/destroy [sessionToken] () -> ok<br />
** for detaching a device, destroy all tokens<br />
* POST /recovery_email/status [sessionToken] () -> "verified" status of email<br />
** use "Accept: text/event-stream" header for server-sent-events; server will send "update" event with the new content of the resource any time it changes.<br />
* POST /recovery_email/resend_code [sessionToken] () -> re-send verification email<br />
* POST /recovery_email/verify_code (code) -> set "verified" flag<br />
** this code will come from a clickable link and is an unauthenticated endpoint<br />
** this could maybe take the recovery method if that would be helpful<br />
** sets verified flag on recovery method<br />
* POST /certificate/sign [sessionToken,needs-verf] (pubkey) -> cert<br />
** only if recovery email is verified<br />
* POST /password/change/start [authToken,needs-verf] () -> accountResetToken, keyFetchToken<br />
* POST /password/forgot/send_code () -> forgotPasswordToken<br />
** sends code to recovery method (email for now, maybe SMS later)<br />
** this is a short code, not a clickable link<br />
* POST /password/forgot/resend_code (forgotPasswordToken) -> re-sends code<br />
* POST /password/forgot/verify_code (forgotPasswordToken, code) -> accountResetToken<br />
** sets verified flag on recovery method<br />
* POST /get_random_bytes<br />
<br />
<br />
== Typical Client Flows ==<br />
<br />
Create account<br />
<br />
* POST /account/create (email,srpV,srpSalt) -> ok (server sends verification email)<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authed with authToken]() -> keyFetchToken, sessionToken<br />
* GET /recovery_email/status [sessionToken] () -> "verified" status<br />
** (optional, only if user requests resend) POST /recovery_email/resend_code [sessionToken]() -> ok<br />
** POST /recovery_email/verify_code (code) -> ok<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
* POST /certificate/sign [sessionToken] (pubkey) -> cert<br />
<br />
<br />
Attach to new device<br />
<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authToken] () -> keyFetchToken, sessionToken<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
** (if unverified-error, do waitUntilEmailVerified, then try again)<br />
* POST /certificate/sign [sessionToken] (pubkey) -> cert<br />
<br />
<br />
Forgot password<br />
<br />
* POST /password/forgot/send_code (email) -> forgotPasswordToken<br />
* POST /password/forgot/verify_code (forgotPasswordToken, code) -> accountResetToken<br />
* POST /account/reset [authed+encrypted by accountResetToken] (0000,srpV,srpSalt) -> ok<br />
* GOTO "Attach to new device"<br />
<br />
<br />
Change Password<br />
<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /password/change/start [authToken] () -> accountResetToken, keyFetchToken<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
* POST /account/reset [authed+encrypted by accountResetToken] (wrap(kB),srpV,srpSalt) -> ok<br />
* GOTO "Attach to new device"</div>Warnerhttps://wiki.mozilla.org/index.php?title=Identity/AttachedServices/KeyServerProtocol&diff=692247Identity/AttachedServices/KeyServerProtocol2013-08-08T19:17:51Z<p>Warner: /* Client-Side Key Stretching */</p>
<hr />
<div>= PiCL Key Server / IdP Protocol =<br />
<br />
NOTE: This specification is slowly converging on stability (31-Jul-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.<br />
<br />
The server is being developed in https://github.com/mozilla/picl-idp . This repo currently includes a demonstration client (node.js CLI).<br />
<br />
Note that all messages are delivered over an HTTPS connection. The client browser may also implement cert-pinning to improve on the certificate validation process. The protections described below are in addition to those provided by TLS.<br />
<br />
Remaining TODO items:<br />
<br />
* decide on client-side key-stretching parameters: http://keywrapping.appspot.com can help<br />
* finalize SRP questions (definition of M1, generation of a/b)<br />
* finalize proof-of-work/DoS-prevention details<br />
* decide how to rate-limit account-creation calls<br />
* confirm this is actually implementable inside Firefox (especially w.r.t. NSS and Android/Java crypto)<br />
<br />
= Creating The Account =<br />
<br />
The first act performed by a user is to create the account. They enter email+password into their browser, which then does the following steps:<br />
<br />
* decide upon stretching parameters (perhaps consulting the keyserver for recommendations, but imposing a minimum strength requirement). For now we used a fixed {firstPBKDF:20000, scrypt: {N:65536, r:8, p:1}, secondPBKDF:20000}.<br />
* randomly choose a 32-byte mainSalt (this should be unique, but is not secret)<br />
* choose the SRP group parameters (fixed: use the 2048-bit group described below)<br />
* perform key-stretching (as described below), derive stretchedPW and srpPW<br />
* randomly choose a 32-byte srpSalt (unique, but not secret)<br />
* create srpVerifier from srpPW and srpSalt (as described below)<br />
* deliver (email, stretchParams, mainSalt, srpParams, srpSalt) to the keyserver's "POST /account/create" API<br />
<br />
<br />
To limit abuse, the createAccount() should also require a fresh "createToken". This should be created by some other API, outside the scope of this document, that perhaps requires a CAPTCHA or something. createAccount() might also require a proof-of-work token, as described below.<br />
<br />
The server, when creating a new account, creates both kA and wrap(kB) as randomly-generated 256-bit (32-byte) strings. It stores these, along with all the remaining values, indexed by email, in the account table where they can be retrieved by getToken later.<br />
<br />
== Email Verification ==<br />
<br />
To prevent fixation attacks, we require new accounts to verify their configured recovery email address before letting them learn the generated keys or obtain a signed certificate. Nevertheless, we wish clients to forget the user's password while they wait for email verification to complete. To achieve this, clients can obtain a sessionToken before verification, but most APIs that require it will raise errors until verification is finished.<br />
<br />
The server will send email with a URL that contains a long random "verification code" in the "fragment" hash. This URL points to a static page with some javascript that submits the code to the "POST /account/recovery_methods/verify_code" API. The URL can be clicked by any browser (it is not bound to anything), and when the API is hit, the account is marked as verified.<br />
<br />
After the client submits /account/create, it performs the "/session/auth" login sequence below to obtain a sessionToken. It then polls the "GET /account/recovery_methods" (which requires a sessionToken but not account verification) until the user clicks the email link and the API reports verification is complete. Then the client uses "GET /account/keys" and "POST /certificate/sign", described below, to obtain kA, kB, and a signed certificate to talk to the storage server.<br />
<br />
= Login: Obtaining the authToken =<br />
<br />
To connect a browser to an existing account, we use the following login protocol to transform an email+password pair into a single-use authToken. We will use this in the next section to create a session, from which we can obtain encryption keys and signed certificates.<br />
<br />
This protocol starts by using key-stretching to transform the email+password into a "stretchedPW", then feeds this into an SRP protocol to get the authToken.<br />
<br />
[[File:PICL-IdPAuth-auth-start.png|IdP Auth Protocol]]<br />
<br />
This authToken can be used (once) to do one of the following:<br />
<br />
* /session/create: obtain a sessionToken (and keyFetchToken), which enables storage server access<br />
* /password/change: obtain an accountResetToken, to safely reset the account password<br />
* /account/delete: to delete the entire account<br />
<br />
The protocol is designed to enable parallelism between key-stretching and the initial network messages, to reduce the time it takes to connect a browser to the account. In total, the browser requires five messages in four roundtrips (1: /auth/start, 2: /auth/finish, 3: /session/create, 4: /account/keys and /certificate/sign in parallel) before it is ready to talk to the storage server.<br />
<br />
== /auth/start ==<br />
<br />
As soon as the user finishes typing in the email address, the client should send it in the "/auth/start" 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 loginSRPToken that is used to associate this request with the subsequent /auth/finish 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.<br />
<br />
To mitigate DoS abuse, /auth/start may also require a proof-of-work string, described below.<br />
<br />
== Client-Side Key Stretching ==<br />
<br />
"Key Stretching" is the practice of running a password through a computationally-expensive one-way function before using it for encryption or authentication. The goal is to make brute-force dictionary attacks more expensive, by raising the cost of testing each guess.<br />
<br />
To protect the user's class-B data against compromises of our keyserver, we perform this key stretching on the client, rather than on the server. We use the memory-hard "scrypt" function (pronounced "ess-crypt") for this purpose, as motivated by the attacker-cost studies in [[Identity/CryptoIdeas/01-PBKDF-scrypt]]. Slower (low-RAM) devices can still participate by outsourcing the scrypt step to an "scrypt helper". To provide at least minimal protection against a compromise of this helper, we sandwich the scrypt step between two PBKDF2 steps. The complete protocol looks like this:<br />
<br />
[[File:PICL-IdPAuth-stretch-KDF.png|Stretching KDF]]<br />
<br />
Our initial parameter choices for this stretching are to use 20000 iterations of PBKDF2 on each side (40k in all), and to run scrypt with N/r/p set to 64k/8/1. This requires roughly 60MB of RAM for about 1.3 seconds on a 1.8GHz Intel Atom linux box (it will run faster, but use the same memory, on more modern CPUs). We are still studying performance on various platforms to decide what parameters to use in V1: lowering them speeds up the login process, but reduces security (by reducing the cost of an dictionary attack).<br />
<br />
Since the stretching is expected to take a second or two, the client can optimistically start this process (using default parameters) before receiving the auth/start 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 "mainSalt" is added *after* the stretching, to enable this parallelism (at a tiny cost in security).<br />
<br />
After "stretchedPW" is derived, a second HKDF call is used to derive "srpPW" and "unwrapBKey" which will be used later.<br />
<br />
[[File:PICL-IdPAuth-main-KDF.png|masterKey KDF]]<br />
<br />
== SRP Protocol Details ==<br />
<br />
The PiCL client uses the SRP protocol (http://srp.stanford.edu/) to prove that it knows the (stretched) account password without revealing the actual password (or information enabling a brute-force attack) to the server or any eavesdroppers. <br />
<br />
SRP is somewhat underspecified. We use SRP-6a, with SHA256 as the hash, and the 2048-bit modulus defined in RFC 5053 Appendix A. We consistently zero-pad all string values to 256 bytes (2048 bits), and use H(A+B+S) as the key-confirmation message "M1". These details, plus the SRP design papers and RFCs 2945 and 5054, should be enough to build a compatible implementation. The diagrams below and the test vectors at the end of this page can be used to verify compatibility.<br />
<br />
The server should use Jed's SRP module from https://github.com/jedp/node-srp . The client might use SJCL (http://crypto.stanford.edu/sjcl/) or native code (NSS).<br />
<br />
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).<br />
<br />
Note that SRP-6a uses a "k" value which basically encodes the group being used ("N" and "g"). Since all PICL accounts use the same 2048-bit group, they will all use the same "k" value (not to be confused with the per-session shared-secret "K" key that emerges from the protocol). This group's "k" integer is (as a base-10 number):<br />
259003859907<br />
09503006915442163037721228467470<br />
35652616593381637186118123578112<br />
<br />
=== SRP Verifier Calculation ===<br />
<br />
When the client first creates the account, it must combine the account email address, the stretched password (srpPW), and a randomly-generated srpSalt, to compute the srpVerifier. The server will use this verifier later, to check whether or not the client really knows the password.<br />
<br />
If the server is compromised and an attacker learns the srpVerifier for a given account, they cannot use this to directly log in (the verifier is not "password-equivalent"), but it does allow them to perform an offline brute-force attack against the user's password. In this respect, it is similar to a traditional hashed password. We make these attacks somewhat more expensive by performing the client-side stretching described above, instead of using the raw user password in the SRP calculation.<br />
<br />
[[File:PICL-IdPAuth-SRP-Verifier.png|client-side SRP Verifier calculation]]<br />
<br />
Test vectors are provided at the end of this page. The protocol requires that several values ("a", "b", "srpSalt") are chosen randomly, but for illustrative purposes, the test vectors use carefully-crafted non-random values. For the user's sake, please ensure implementations use proper random values instead of simply copying the test vectors.<br />
<br />
The client sends srpSalt and srpVerifier to the server when it creates the account. It will also re-compute the 'x' value (as an integer) during sign-in. The server will convert the srpVerifier string back into an integer ('v') for use during its own sign-in calculations.<br />
<br />
=== SRP Server-side Sign-In Flow ===<br />
<br />
When the user connects a new device to their account, they use the /auth/start API to start the SRP protocol. This sends the account email address to the server. The server looks up the stored srpVerifier for this account, creates a random 'b' integer, performs some math to compute the "B" number, then converts B into a string known as "srpB". "srpB" is returned to the client, along with srpSalt and the key-stretching parameters. "b" and "srpB" are retained for the subsequent /auth/finish call. '''Note that it is critical that the "b" integer remain secret on the server.''' The server also allocates a random loginSRPToken to connect the two calls.<br />
<br />
[[File:PICL-IdPAuth-SRP-Server.png|server-side SRP]]<br />
<br />
Later, /auth/start will be called with the client's srpA string and its M1 key-confirmation string. srpA is combined with srpB to calculate the "u" integer. srpA is also turned into an integer and used to compute the shared-secret "S" integer. S is then used to compute the shared key "srpK", which is the output of the SRP process.<br />
<br />
=== SRP Client Calculation ===<br />
<br />
While the client is waiting for the response to /auth/start, it begins its key-stretching calculations. Everything else must wait until the response to /auth/start arrives, which includes the key-stretching parameters (which are retroactively confirmed), srpSalt, and the server's generated srpB value.<br />
<br />
[[File:PICL-IdPAuth-SRP-Client.png|client-side SRP]]<br />
<br />
Once the client knows srpSalt, it computes the same "x" integer as it did in the middle of the srpVerifier calculation. It also converts srpB into an integer named "B". Then it creates a random "a" integer, uses it to compute the string "srpA", then combines srpA with the server's srpB to compute the "u" integer. It then combines the static "k", the password-derived "x", the combined "u", and the server's "B", together with some magic math, to derive the "S" integer. If everything went well, the client will compute the same "S" value as the server did. If not (the password was wrong, or the client is talking to a fake server that doesn't really know srpVerifier), then the two "S" values will not match.<br />
<br />
('''Again, it is critical that the client keep its "a" and "x" integers secret, both during and after the protocol run.''')<br />
<br />
To safely tell if the "S" values match, both client and server combine srpA, srpB, and their (independently) generated "S" strings to form a string named "M1". The client sends M1 (along with srpA) in the /session/auth/finish message. The server compares the client's copy of M1 against its own. If they match, the client knew the password and the server can safely respond with the encrypted account data. If they do not match, the client (or a man-in-the-middle attacker) did not know the password, and the client should increment a counter that can trigger defenses against online guessing attacks. The server must then return an error to the client, and '''not''' use or reveal srpK (or the correct M1) in any way.<br />
<br />
Both client and server also hash "S" into "srpK". This is the shared session key, from which specific message encryption and MAC keys are derived (as described below).<br />
<br />
=== SRP Notes ===<br />
<br />
The SRP "g" (generator) and "N" (prime modulus) should use the 2048-bit value from RFC 5054 Appendix A. 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.<br />
<br />
There are several places in SRP where integers (usually in the range 1..N-1) are converted into bytestrings, either for transmission over a wire, or to be passed into a hash function. The SRP spec is somewhat ambiguous about padding here: if the integer happens to be less than 2^2040, the simplest toString() approach will yield a '''255''' byte string, not a 256 byte string. PiCL consistently uses padding, so compatible implementations must prepend one or more NUL bytes to these short strings before transmission or hashing. The examples above were brute-forced to ensure that "srpVerifier", "srpA", "srpB", and "S" all wind up with leading zeros, to exercise the padding code in compatible implementations. If you are having problems getting your code to match these results, add some assertions to test that the stringified integers being put into hashes are exactly 256 bytes long.<br />
<br />
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 (/auth/finish).<br />
<br />
The server receives "A" in /auth/finish, 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 keyFetchToken+sessionToken as described below, returning the encrypted/MACed bundle in the response to /auth/finish.<br />
<br />
Outstanding crypto questions:<br />
<br />
* How exactly should the "a" and "b" integers be generated? The issue is of how much bias SRP can tolerate. Ideally these integers are uniformly distributed from 1 to N-1 (inclusive). The only way to obtain a purely uniform distribution from a source of random bytes is try-try-again: pick a (integral-number-of-bytes) number, compare it to the desired range, try again if it falls outside the range. If you get unlucky, this can take a lot of guesses, depending upon how close the range is to a power of two. ECDSA implementations tend to pick a number twice as long as the modulus and then modulo it down (rand(2^4096) % N), which yields a tiny fraction of a bit of bias. The cheaper approach is to do the same with a number of equal length (rand(2^2048) % N), which imposes more bias. Some SRP implementations appear to be satisfied by rand(2^256). We need more review here.<br />
* The original SRP papers defined M1=H(A+B+S) as we use here, but other implementation (in particular RFC2945) uses a curious construct that includes the username, the salt, and an odd XOR combination of N and g. We need to decide what to use for M1.<br />
<br />
== /auth/finish ==<br />
<br />
The client-side SRP calculation results in two values that are sent to the server in the /auth/finish 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.<br />
<br />
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.<br />
<br />
The server then allocates a single-use 32-byte random token named "authToken". It encrypts the token with the session key, and returns a success message with the encrypted bundle.<br />
<br />
All tokens have an associated tokenID, described below. The server needs to maintain a table that maps the tokenID to the token itself, so it can derive other values from the token later. The tokens are also associated with a specific account, so later API requests do not specify an email address or account ID.<br />
<br />
== Decrypting the /auth/finish Response ==<br />
<br />
The SRP session key ("srpK") is used to derive two other keys: respHMACkey and respXORkey.<br />
<br />
[[File:PICL-IdPAuth-encrypt-authToken.png|Decrypting the authToken]]<br />
<br />
The respXORkey is used to encrypt the authToken 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.<br />
<br />
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 returns the authToken value.<br />
<br />
= Creating a Session =<br />
<br />
For login, the single-use authToken is spent on a call to /session/create .<br />
This allocates two new (random 32-byte) tokens: a long-lived "sessionToken", and a single-use "keyFetchToken". The /session/create call returns an encrypted bundle containing the two tokens.<br />
<br />
[[File:PICL-IdPAuth-encrypt-sessionToken.png|Decrypting the sessionToken and keyFetchToken]]<br />
<br />
For calls which accept an authToken, the client uses authToken to derive three values:<br />
<br />
* tokenID<br />
* reqHMACkey<br />
* requestKey<br />
<br />
<br />
The client uses tokenID and reqHMACkey for a HAWK (https://github.com/hueniverse/hawk/) request to the "POST /session/create" API, using tokenID as "credentials.id" and reqHMACkey as "credentials.key". The server uses tokenID to look up the corresponding token, then derives reqHMACkey to validate the request.<br />
<br />
Each authToken-using call then derives additional API-specific values from requestKey. /session/create uses two derived values:<br />
<br />
* respHMACkey<br />
* respXORkey<br />
<br />
<br />
When the server receives a valid /session/create request, it allocates sessionToken and keyFetchToken, concatenates them, encrypts the pair by XORing it with the derived respXORkey, and attaches a MAC generated with respHMACkey. The encrypted MACed bundle is returned to the client.<br />
<br />
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 keyFetchToken and sessionToken values.<br />
<br />
Each authToken is single-use: once a successful request has been made with it, the authToken and its corresponding ID is removed from the server's memory, and subsequent attempts to use it will return a "no such token" error. The token is consumed even if the request fails (e.g. the MAC did not match).<br />
<br />
The authToken can be used (once) by multiple APIs. The server only needs to maintain one table mapping tokenID to authToken, because all these APIs share the same method of deriving a tokenID and reqHMACkey from the authToken.<br />
<br />
When a HAWK request appears, it should look up the tokenID in this table, retrieve the authToken, recompute reqHMACkey and requestKey, validate the request, delete the table entry, then hand the HTTP request and requestKey to the specific API handler. That handler should derive the API-specific values (respHMACkey, respXORkey, etc) to process the request or construct the response.<br />
<br />
The server can support multiple sessions per account (typically one per client device, plus perhaps others for account-management portals). There can also be multiple outstanding keyFetchTokens. The sessionToken lasts forever (until revoked by a password change or explicit revocation command), and can be used an unlimited number of times. The keyFetchToken expires after 60 seconds, and is single-use.<br />
<br />
= Obtaining keys kA and kB =<br />
<br />
Clients which have exchanged an authToken for either a sessionToken or an accountResetToken will also receive a keyFetchToken. This single-use token allows the client to retrieve kA and wrap(kB), which enables the client to encrypt and decrypt browser data (bookmarks, open-tabs, etc) correctly. As above, the keyFetchToken is used to derive tokenID, reqHMACkey, respHMACkey, and respXORkey, which are used in a HAWK request to the "GET /account/keys" API.<br />
<br />
The server pulls kA and wrap(kB) from the account table, concatenates them, encrypts the pair by XORing it with the derived respXORkey, and attaches a MAC generated with respHMACkey.<br />
<br />
[[File:PICL-IdPAuth-keys-server.png|keyFetchToken: server encrypts keys]]<br />
<br />
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 and wrap(kB) values.<br />
<br />
[[File:PICL-IdPAuth-keys-client.png|keyFetchToken: client decrypts keys]]<br />
<br />
Finally, the server-provided wrap(kB) value is simply XORed with the password-derived unwrapBKey (both are 32-byte strings) to obtain kB. There is no MAC on wrap(kB).<br />
<br />
[[File:PICL-IdPAuth-key-unwrap.png|unwrapping kB]]<br />
<br />
"kA" and "kB" enable the browser to encrypt/decrypt synchronized data records. They will be used to derive separate encryption and HMAC keys for each data collection (bookmarks, form-fill data, saved-password, open-tabs, etc). This will allow the user to share some data, but not everything, with a third party. The client may intentionally forget kA and kB (only retaining the derived keys) to reduce the power available to someone who steals their device.<br />
<br />
Note that /account/keys will not succeed until the account's email address has been verified. Also note that each keyFetchToken is single-use and short-lived. The token is consumed even if the request fails (e.g. the MAC does not match).<br />
<br />
= Signing Certificates =<br />
<br />
The sessionToken is used to derive two values:<br />
<br />
* tokenID<br />
* request HMAC key<br />
<br />
<br />
[[File:PICL-IdPAuth-use-session.png|Using the sessionToken, signing certificates]]<br />
<br />
The requestHMACkey is used in a HAWK request to provide integrity over many APIs, including /certificate/sign. requestHMACkey 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.<br />
<br />
For /certificate/sign, it is critical to enable payload verification by setting options.payload=true (on both client and server). Otherwise a man-in-the-middle could submit their own public key, get it signed, and then delete the user's data on the storage servers.<br />
<br />
The following keyserver APIs require a HAWK-protected request that uses the sessionToken. In addition, some require that the account be in the "verified" state:<br />
<br />
* GET /account/devices<br />
* POST /session/destroy<br />
* GET /recovery_email/status<br />
* POST /recovery_email/resend_code<br />
* POST /certificate/sign (requires "verified" account)<br />
<br />
= Resetting The Account =<br />
<br />
The account may be reset in two circumstances: when the user changes their password, and when the user forgets their password. In both cases, the client first obtains an "accountResetToken". This token is then used to change the SRP Verifier and either reset or replace the wrap(kB) value.<br />
<br />
== Changing the Password ==<br />
<br />
When the user wishes to change their password (i.e. they still know the old password), they first use the /auth/start+/auth/finish SRP protocol safely obtain an "authToken". They they use the "/password/change/start" API to exchange the authToken for an accountResetToken and a keyFetchToken.<br />
<br />
[[File:PICL-IdPAuth-encrypt-passwordChange.png|Server encrypts passwordChange response]]<br />
<br />
The accountResetToken will be used below to set the new password safely. The keyFetchToken should be used first, to obtain kB, so the subsequent account reset can replace wrap(kB) with a new value. This allows the password-changing client to retain their class-B data.<br />
<br />
Requiring an authToken proves that the user has provided the correct account password recently. When the account is reset, all active sessions and tokens will be cancelled (disconnecting all devices from the account). The client should immediately establish a new session as described above.<br />
<br />
This API is only used when the user knows their old password: if they have forgotten the password, use the "/password/forgot" APIs below.<br />
<br />
== Handling a Forgotten Password ==<br />
<br />
When the user has forgotten their password, they can use one of their "recovery methods" to obtain an accountResetToken. For now, this means we send a random code to the email address associated with their account. The user must copy this code from the email into their client, whereupon the client will get an accountResetToken that can be delivered to the API below.<br />
<br />
Note that, since the forgotten-password client never learns kB, any class-B data will be lost. This is necessary to protect class-B data from attackers who can read the users's email but do not know the account password (including those who compromise the IdP and the keyserver itself). When using /account/reset below, they will set wrap(kB) to a string of all zeros, which means the server should generate a new random wrap(kB) (just as it does during account creation).<br />
<br />
The /password/forgot/send_code API is used to ask the server to send a recovery code. This takes a recovery method, which for now is just an email address. This API is unauthenticated (after all, the user who has forgotten their password knows nothing but their email address). The server marks the corresponding account as "pending recovery", allocates a random forgotPasswordToken for the account, creates a recovery code, and sends the code (with instructions) via email. The API returns forgotPasswordToken to the client.<br />
<br />
The user must copy the recovery code into the same browser where they started the process. The client then submits the code to /password/forgot/verify_code along with the forgotPasswordToken they received. If they match, the server allocates a accountResetToken and returns it to the client. If they do not match, the server increments a counter (which is used to decide if an online guessing attack is happening).<br />
<br />
forgotPasswordToken can be used three times before it is exhausted. If the user guesses incorrectly this often, the client must call send_code again to get a new token and code. Each account has at most one token+code active at a time.<br />
<br />
The recovery code is initially a random 8-digit decimal number. If an attacker tries to sign in as someone else, hits the "forgot my password" button, then submits a guess to /password/forgot/verify_code, they will have a 1-in-100-million chance of success. If the server detects too many wrong guesses, it should increase the length of new codes. Another defensive technique is to require that users click an email link before being given the code: the server is told when the link is clicked, so the code will not be enabled until the email has been read. It remains to be seen whether this will be sufficient.<br />
<br />
The exact thresholds are TBD, but a nominal goal is to keep the chances of any attack succeeding to below 1-in-a-million per year. To achieve this, we can tolerate 100 verify_code failures in a single year before we must increase the length of the code.<br />
<br />
== Using accountResetToken ==<br />
<br />
An accountResetToken, obtained through either of the methods above, is used to invoke the /account/reset API. If the request is accepted, the server replaces the account password with a new one, updates the wrap(kB) value, cancels all active sessions and tokens (disconnecting all devices from the account), and sends a "your password has been changed" email to the user.<br />
<br />
If the client knew the old password, it can supply a new wrap(kB) value that will yield the same kB key as before, so no data will be lost. If the client did *not* know the old password, then it will supply a wrap(kB) of all-zeros, which tells the server to generate a new random wrap(kB) (just like during account creation), which means the class-B data will be lost.<br />
<br />
The client puts their new password through the same stretching procedure as described in the new-account section above, resulting in a new srpVerifier and unwrapBKey. If they knew the old kB, they XOR it with the new unwrapBKey to obtain a new wrap(kB).<br />
<br />
/account/reset needs request confidentiality, since the arguments include the newly wrapped kB value and the new SRP verifier, both of which enable a brute-force attack against the password. HAWK provides request integrity. The response is a single "ok" or "fail", conveyed by the HTTP headers, so we do not require response confidentiality, and can live without response integrity.<br />
<br />
So the single-use resetToken is used to derive three values:<br />
<br />
* tokenID<br />
* request HMAC key<br />
* request XOR key<br />
<br />
<br />
[[File:PICL-IdPAuth-encrypt-resetAccount.png|Client encrypts resetAccount request]]<br />
<br />
The request data will contain wrap(kB) and the new SRP verifier, concatenated together. Since we always pad the SRP verifier to the full (256-byte) group length, both pieces are fixed-length. We generate enough reqXORkey bytes to cover both values.<br />
<br />
The request data is XORed with requestXORkey, then delivered in the body of a HAWK request that uses tokenID as credentials.id and requestHMACkey as credentials.key . Note: it is critical to include the request body in the HAWK integrity check (options.payload=true, on both client and server), otherwise a man-in-the-middle could substitute their own SRP verifier, giving them control over the account (access to the user's class-A data, and a brute-force attack on their password).<br />
<br />
The client submits other values in the same request:<br />
<br />
* stretchParams<br />
* mainKDFSalt<br />
* srpSalt<br />
<br />
<br />
These values do not require confidentiality, so are not included in the encrypted bundle. They are still protected by the HAWK integrity check. Note that the server should assert that both salts are different than the previously stored values.<br />
<br />
After using /account/reset, clients should immediately perform the login protocol from above. If the old password was forgotten, this is necessary to fetch kA. In either case, a new sessionToken is required, since old sessions and tokens are revoked by /account/reset. Clients should retain the new srpPassword value during this process to avoid needing to run the lengthy key-stretching routine a second time.<br />
<br />
= Deleting The Account =<br />
<br />
When the user wishes to completely delete their account, the browser needs to perform two actions:<br />
<br />
* contact the storage servers and delete all records and collections<br />
* contact the keyserver and delete the account information<br />
<br />
The user should be prompted for their password as confirmation (i.e. a browser in the normal attached-and-synchronizing state should not be able to erase the account information: it must acquire a new authToken first).<br />
<br />
The device then obtains an authToken as described above, then spends it on a HAWK-protected request to the /account/destroy endpoint. This request contains no body and returns only a success code.<br />
<br />
[[File:PICL-IdPAuth-deleteAccount.png|Deleting the Account]]<br />
<br />
= Crypto Notes =<br />
<br />
Strong entropy is needed in the following places:<br />
<br />
* (client) creation of private "a" value inside SRP<br />
* (server) initial creation of kA and wrap(kB)<br />
* (server) creation of private "B" value inside SRP<br />
* (server) creation of signToken and resetToken<br />
<br />
<br />
On the server, code should get entropy from /dev/urandom via a function that uses it, like "crypto.randomBytes()" in node.js or "os.urandom()" in python. On the client, code should combine local entropy with some fetched from the keyserver via getEntropy(), to guard against failures in the local entropy pool. Something like HKDF(IKM=localEntropy+remoteEntropy, salt="", info=KW("mergeEntropy")).<br />
<br />
An HKDF-based stream cipher is used to protect the response for getToken2(), and the request for resetAccount(). HKDF is used to create a number of random bytes equal to the length of the message, then these are XORed with the plaintext to produce the ciphertext. An HMAC is then computed from the ciphertext, to protect the integrity of the message.<br />
<br />
HKDF, like any KDF, is defined to produce output that is indistinguishable from random data ("The HKDF Scheme", http://eprint.iacr.org/2010/264.pdf , by Hugo Krawczyk, section 3). XORing a plaintext with a random keystream to produce ciphertext is a simple and secure approach to data encryption, epitomized by AES-CTR or a stream cipher (http://cr.yp.to/snuffle/design.pdf). HKDF is not the fastest way to generate such a keystream, but it is safe, easy to specify, and easy to implement (just HMAC and XOR).<br />
<br />
Each keystream must be unique. SRP is defined to produce a random session key for each run (as long as at least one of the sides provides a random ephemeral key). We define resetToken to be a single-use randomly-generated value. Hence our two HKDF-XOR keystreams will be unique.<br />
<br />
A slightly more-traditional alternative would be to use AES-CTR (with the same HMAC-SHA256 used here), with a randomly-generated IV. This is equally secure, but requires implementors to obtain an AES library (with CTR mode, which does not seem to be universal). An even more traditional technique would be AES-CBC, which introduces the need for padding and a way to specify the length of the plaintext. The additional specification complexity, plus the library load, leads me to prefer HKDF+XOR.<br />
<br />
kB is equal to the XOR of wrapKey (which is a deterministic function of the user's email address, password, mainSalt, and the stretching parameters) and the server's randomly-generated wrap(kB) value, making kB a random value too. Using XOR as a wrapping function allows us to avoid sending kB or wrap(kB) in the initial createAccount arguments, which are not protected by SRP, and thus would enable an eavesdropper to mount a dictionary attack on the password (using wrap(kB) as their oracle).<br />
<br />
Likewise, allowing the server to generate kA avoids exposure during createAccount. The only point of vulnerability is a forgotten-password resetAccount() message, if an eavesdropper can learn the resetToken. In this case, the attacker might either use the resetToken themselves (then use signToken to learn kA and wrap(kB)), or passively decrypt the resetAccount() arguments to retrieve just wrap(kB). Given wrap(kB) and some encrypted browser data, the attacker can guess passwords (and derive kB, etc) until the data decrypts properly.<br />
<br />
To make this technique safe, any time kB or the password is changed, the mainSalt should be changed too. Otherwise knowledge of both wrap(old-kB) and old-kB would reveal wrapKey, making it easy to deduce the new kB. Changing mainSalt causes wrapKey to change too, preventing this.<br />
<br />
mainSalt is incorporated at the end of the stretching process to allow it to proceed in parallel with the getToken1() call that retrieves the salt. The inputs to the lengthy stretch come entirely from the user (email and password) or are optimistically (stretching parameters). This speedup seems more important than the minor security benefit of including the salt at the beginning of the stretch.<br />
<br />
There is no MAC on wrap(kB). If the keyserver chooses to deliver a bogus wrap(kB) or kA, the client will discover the problem a moment later when it talks to a storage server and attempts to retrieve data from an unrecognized collection-ID (since we intend to derive collection-IDs from the key used to encrypt their data, which will be derived from kA or kB as appropriate). It might be useful to add a checksum to kA and wrap(kB) to detect accidental corruption (e.g. store and deliver kA+SHA256(kA)), but this doesn't protect against intentional changes. We omit this checksum for now, assuming that disks will be reliable enough to let us never experience such failures.<br />
<br />
We use scrypt without a per-user salt. This is safe because the "password" input to scrypt is already diversified by the user's email address. The intention is to allow the scrypt-helper to run on anonymous data, so that an attacker who compromises this helper cannot easily learn the email addresses of the partially-stretched K1 values that it is receiving, confounding their attack.<br />
<br />
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. <br />
For /certificate/sign, we do not need request confidentiality or response confidentiality, since the client's pubkey and the resulting certificate will both be exposed over a similar SSL connection to the storage server later. And it is sufficient to rely on the response integrity provided by SSL, since the client can verify the returned certificate for itself. For the other keyserver APIs protected by HAWK, these properties are either unnecessary, or are provided by additional mechanisms.<br />
<br />
= Proof-Of-Work =<br />
<br />
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(). The server can control how much work is required.<br />
<br />
The getToken1() call looks for a "X-PiCL-PoW:" HTTP header. Most of the time, clients don't supply this header. But if the server responds to the getToken1() call with an error that indicates PoW is required, clients must create a valid PoW string and include it as the value of an "X-PiCL-PoW:" header in their next call to getToken1().<br />
<br />
The server's error message includes two parameters. The first is a "prefix string": the client's PoW string is required to begin with this prefix. The second is a "threshold hash". SHA256(PoWString) is required to be lexicographically earlier than the thresholdHashString (i.e. the numerical value of its hash must be closer to zero than the threshold). The client is expected to concatenate the prefix with a counter, then repeatedly increment the counter and hash the result until they meet the threshold, then re-submit their getToken1() request with the combined prefix+counter string in the header. If the client has spent more than e.g. 10 seconds doing this, the client should probably help the user cancel the operation and try again.<br />
<br />
When a server is under a DoS attack (either via some manual configuration tool or sensed automatically), it should start requiring valid unique X-PiCL-PoW headers. The server should initially require very little work, by using a threshold hash with just a few leading zero bits. If this is insufficient to reduce the attack volume, the threshold should be lowered, requiring even more work (from both the attacker and legitimate clients).<br />
<br />
The server should create a prefix string that contains a parseable timestamp and a random nonce (e.g. "%d-%d-" % (int(time.time()), b32encode(os.urandom(8)))). The server should also decide on a cutoff time (perhaps ten minutes ago). Each server must then maintain a table of "old PoW strings" to prevent replay attacks (these do not need to be shared among all servers: an in-RAM cache is fine).<br />
<br />
When the server receives a proposed PoW string, it first splits off the leading timestamp, and if the timestamp is older than the cutoff time, it rejects the string (either by dropping the connection, or returning a new "PoW required" error if it's feeling nice). Then it hashes the whole string and compares it against the threshold, rejecting those which fail to meet the threshold. Finally, for strings that pass the hash threshold, it checks the "old strings" table, and rejects any that appear on that list.<br />
<br />
If the PoW string makes it past all these checks, the server should add the string to the "old strings" table, then accept the request (i.e. compute an srpB value and add a session-id table entry for the request).<br />
<br />
The old-strings table check should be optimized to reject present strings quickly (i.e. if we are under attack, we should expect to see lots of duplicates of the same string, and must minimize the work we do when this occurs).<br />
<br />
The server can remove values from the old-strings table that have timestamps older than the cutoff time. The server can also discard values at other times (to avoid consuming too much memory), without losing anything but protection against resource consumption.<br />
<br />
Other notes:<br />
<br />
The server-side code for this can be deferred until we care to have a response to a DoS attack. However the client-side code for this must be present from day one, otherwise we won't be able to turn on the defense later without fear of disabling legitimate old clients.<br />
<br />
The server should perform as little work as possible before rejecting a token. Every extra CPU cycle it spends in this path is increasing the DoS attack amplification factor.<br />
<br />
The nonce in the prefix string exists to make sure that two successive clients get different prefixes, and thus do not come up with the same counter value (and inadvertently create identical strings, looking like a replay attack). If this proved annoying or expensive, we could instead obligate clients to produce their own nonce.<br />
<br />
TBD: Is this worth it? Should the PoW string go into an HTTP header? (I want it to be cheap to extract, and not clutter logs). Should the error response be a distinctive HTTP error code so our monitoring tools can easily count them? We can also use this feature to slow down online guessing attacks (i.e. trigger it either when getToken1 is called too much or when getToken2 produces too many errors). Since getToken1() includes an email address, we could also requires PoWs for some addresses (e.g. those we know to be under attack) but not others.<br />
<br />
= Glossary =<br />
<br />
This defines some of the jargon we've developed for this protocol.<br />
<br />
* data classes: each type of browser data (bookmarks, passwords, history, etc) can be assigned, by the user, to either class-A or class-B<br />
* class-A: data assigned to this class can be recovered, even if the user forgets their password, by proving control over an email address and resetting the account. It can also be read by Mozilla (since it runs the keyserver and knows kA), or by the user's IdP (by resetting the account without the user's permission).<br />
* class-B: data in this class cannot be recovered if the password is forgotten. It cannot be read by the IdP. Mozilla (via the keyserver) cannot read this data, but can attempt a brute-force dictionary attack against the password.<br />
* kA: the master key for data stored as "class-A", a 32-byte binary string. Individual encryption keys for different datatypes are derived from kA.<br />
* kB: the master key for data stored as "class-B", a 32-byte binary string.<br />
* wrap(kB): an encrypted copy of kB. The keyserver stores wrap(kB) and never sees kB itself. The client (browser) uses a key derived from the user's password to decrypt wrap(kB), obtaining the real kB.<br />
* sessionToken: a long-lived per-device token which allows the device to obtained signed BrowserID certificates for the account's identity (GUID@picl-something.org). This token remains valid until the user revokes it (either by changing their password, or triggering some kind of "revoke a specific device" or "revoke all devices" function).<br />
<br />
= Test Vectors =<br />
<br />
The following example uses a non-ASCII email address of "andré@example.org" (with an accented "e", UTF8 encoding is 616e6472c3a9406578616d706c652e6f7267) and a non-ascii password of "pässwörd" (with accents on "a" and "o", UTF8 encoding is 70c3a4737377c3b67264).<br />
<br />
These test vectors were produced by the python code in https://github.com/warner/picl-spec-crypto . The diagrams may lag behind the latest version of that code.<br />
<br />
== stretch-KDF ==<br />
<br />
email:<br />
616e6472c3a94065<br />
78616d706c652e6f<br />
7267<br />
<br />
password:<br />
70c3a4737377c3b6<br />
7264<br />
<br />
K1 (scrypt input):<br />
f84913e3d8e6d624<br />
689d0a3e9678ac8d<br />
cc79d2c2f3d96414<br />
88cd9d6ef6cd83dd<br />
<br />
K2 (scrypt output):<br />
5b82f146a6412692<br />
3e4167a0350bb181<br />
feba61f63cb17140<br />
12b19cb0be0119c5<br />
<br />
stretchedPW:<br />
c16d46c31bee242c<br />
b31f916e9e38d60b<br />
76431d3f5304549c<br />
c75ae4bc20c7108c<br />
<br />
== main-KDF ==<br />
<br />
mainSalt (normally random):<br />
00f0000000000000<br />
0000000000000000<br />
0000000000000000<br />
000000000000034d<br />
<br />
srpPW:<br />
00f9b71800ab5337<br />
d51177d8fbc682a3<br />
653fa6dae5b87628<br />
eeec43a18af59a9d<br />
<br />
unwrapBKey:<br />
6ea660be9c89ec35<br />
5397f89afb282ea0<br />
bf21095760c8c500<br />
9bbcc894155bbe2a<br />
<br />
internal x (base 10):<br />
8192518690918<br />
99580124814080709381476194749939<br />
03899664126296984459627523279550<br />
<br />
internal x (hex):<br />
b5200337cc3f3f92<br />
6cdddae0b2d31029<br />
c069936a844aff58<br />
779a545be89d0abe<br />
<br />
v (verifier as number) (base 10):<br />
114649<br />
57230405843056840989945621595830<br />
71784395917725741221739574165799<br />
54316134303691657140298181419198<br />
87853709633756255809680435884948<br />
69849281177012209169281795507853<br />
57610332070005048463659745521969<br />
83218225819721112680718485091921<br />
64608360806562626442477160609654<br />
43167308814558974899899506977051<br />
96721477608178869100211706638584<br />
53875100985456239693728258285562<br />
04889672594983678412848291529879<br />
88548996842770025110751388952323<br />
22170663943486107183421205517476<br />
84831590615660554713667726412525<br />
73641352721966728239512914666806<br />
49625530438034148797508015907639<br />
67594925530663571631035463732161<br />
30193328802116982288883318596822<br />
<br />
== SRP Verifier ==<br />
<br />
k (base 10):<br />
259003859907<br />
09503006915442163037721228467470<br />
35652616593381637186118123578112<br />
<br />
srpSalt (normally random):<br />
00f1000000000000<br />
0000000000000000<br />
0000000000000000<br />
0000000000000179<br />
<br />
srpVerifier:<br />
00173ffa0263e63c cfd6791b8ee2a40f<br />
048ec94cd95aa8a3 125726f9805e0c82<br />
83c658dc0b607fbb 25db68e68e93f265<br />
8483049c68af7e82 14c49fde2712a775<br />
b63e545160d64b00 189a86708c69657d<br />
a7a1678eda0cd79f 86b8560ebdb1ffc2<br />
21db360eab901d64 3a75bf1205070a57<br />
91230ae56466b8c3 c1eb656e19b794f1<br />
ea0d2a077b3a7553 50208ea0118fec8c<br />
4b2ec344a05c66ae 1449b32609ca7189<br />
451c259d65bd15b3 4d8729afdb5faff8<br />
af1f3437bbdc0c3d 0b069a8ab2a959c9<br />
0c5a43d42082c774 90f3afcc10ef5648<br />
625c0605cdaace6c 6fdc9e9a7e6635d6<br />
19f50af773452247 0502cab26a52a198<br />
f5b00a2798589165 07b0b4e9ef9524d6<br />
<br />
== SRP B ==<br />
<br />
private b (normally random) (base 10):<br />
1198277<br />
66042000957856349411550091527197<br />
08125226960544768993643095227800<br />
29105361555030352774505625606029<br />
71778328003253459733139844872578<br />
33596964143417216389157582554932<br />
02841499373672188315534280693274<br />
23189198736863575142046053414928<br />
39548731879043135718309539706489<br />
29157321423483527294767988835942<br />
53343430842313006332606344714480<br />
99439808861069316482621424231409<br />
08830704769167700098392968117727<br />
43420990997238759832829219109897<br />
32876428831985487823417312772399<br />
92628295469389578458363237146486<br />
38545526799188280210660508721582<br />
00403102624831815596140094933216<br />
29832845626116777080504444704039<br />
04739431335617585333671378812943<br />
<br />
private b (hex):<br />
00f3000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 000000000000000f<br />
<br />
transmitted srpB:<br />
0022ce5a7b9d8127 7172caa20b0f1efb<br />
4643b3becc535664 73959b07b790d3c3<br />
f08650d5531c19ad 30ebb67bdb481d1d<br />
9cf61bf272f84398 48fdda58a4e6abc5<br />
abb2ac496da5098d 5cbf90e29b4b110e<br />
4e2c033c70af7392 5fa37457ee13ea3e<br />
8fde4ab516dff1c2 ae8e57a6b264fb9d<br />
b637eeeae9b5e43d faba9b329d3b8770<br />
ce89888709e02627 0e474eef822436e6<br />
397562f284778673 a1a7bc12b6883d1c<br />
21fbc27ffb3dbeb8 5efda279a69a1941<br />
4969113f10451603 065f0a0126666456<br />
51dde44a52f4d8de 113e2131321df1bf<br />
4369d2585364f9e5 36c39a4dce33221b<br />
e57d50ddccb4384e 3612bbfd03a268a3<br />
6e4f7e01de651401 e108cc247db50392<br />
<br />
== SRP A ==<br />
<br />
private a (normally random) (base 10):<br />
1193346<br />
47663227291363113405741243413916<br />
43482736314616601220006703889414<br />
28162541137108417166380088052095<br />
43910927476491099816542561560345<br />
50331133015255005622124012256352<br />
06121987030570656676375703406470<br />
63422988042473190059156975005813<br />
46381864669664357382020200036915<br />
26156674010218162984912976536206<br />
14440782978764393137821956464627<br />
16314542157937343986808167341567<br />
89864323268060014089757606109012<br />
50649711198896213496068605039486<br />
22864591676298304745954690086093<br />
75374681084741884719851454277570<br />
80362211874088739962880012800917<br />
05751238004976540634839106888223<br />
63866455314898189520502368799907<br />
19946264951520393624479315579863<br />
<br />
private a (hex):<br />
00f2000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 000000000000d3d7<br />
<br />
transmitted srpA:<br />
007da76cb7e77af5 ab61f334dbd5a958<br />
513afcdf0f47ab99 271fc5f7860fe213<br />
2e5802ca79d2e5c0 64bb80a38ee08771<br />
c98a937696698d87 8d78571568c98a1c<br />
40cc6e7cb101988a 2f9ba3d65679027d<br />
4d9068cb8aad6ebf f0101bab6d52b5fd<br />
fa81d2ed48bba119 d4ecdb7f3f478bd2<br />
36d5749f2275e948 4f2d0a9259d05e49<br />
d78a23dd26c60bfb a04fd346e5146469<br />
a8c3f010a627be81 c58ded1caaef2363<br />
635a45f97ca0d895 cc92ace1d09a99d6<br />
beb6b0dc0829535c 857a419e834db128<br />
64cd6ee8a843563b 0240520ff0195735<br />
cd9d316842d5d3f8 ef7209a0bb4b54ad<br />
7374d73e79be2c39 75632de562c59647<br />
0bb27bad79c3e2fc ddf194e1666cb9fc<br />
<br />
== SRP key-agreement ==<br />
<br />
u:<br />
b284aa1064e87751 50da6b5e2147b47c<br />
a7df505bed94a6f4 bb2ad873332ad732<br />
<br />
S:<br />
0092aaf0f527906a a5e8601f5d707907<br />
a03137e1b601e04b 5a1deb02a981f4be<br />
037b39829a27dba5 0f1b27545ff2e287<br />
29c2b79dcbdd32c9 d6b20d340affab91<br />
a626a8075806c26f e39df91d0ad979f9<br />
b2ee8aad1bc783e7 097407b63bfe58d9<br />
118b9b0b2a7c5c4c debaf8e9a460f4bf<br />
6247b0da34b760a5 9fac891757ddedca<br />
f08eed823b090586 c63009b2d740cc9f<br />
5397be89a2c32cdc fe6d6251ce11e44e<br />
6ecbdd9b6d93f30e 90896d2527564c7e<br />
b9ff70aa91acc0ba c1740a11cd184ffb<br />
989554ab58117c21 96b353d70c356160<br />
100ef5f4c28d19f6 e59ea2508e8e8aac<br />
6001497c27f362ed bafb25e0f045bfdf<br />
9fb02db9c908f103 40a639fe84c31b27<br />
<br />
M1:<br />
27949ec1e0f16256<br />
33436865edb037e2<br />
3eb6bf5cb91873f2<br />
a2729373c2039008<br />
<br />
srpK:<br />
e68fd0112bfa31dc<br />
ffc8e9c96a1cbadb<br />
4c3145978ff35c73<br />
e5bf8d30bbc7499a<br />
<br />
== /auth ==<br />
<br />
srpK:<br />
e68fd0112bfa31dc<br />
ffc8e9c96a1cbadb<br />
4c3145978ff35c73<br />
e5bf8d30bbc7499a<br />
<br />
respHMACkey:<br />
6584613597ef012f<br />
f1752b7869f01d03<br />
c72547a7b7199681<br />
531d9df1991edf23<br />
<br />
respXORkey:<br />
455835926ae37a1b<br />
627bd16affbeeab6<br />
27ecc737121826ca<br />
4a2bac2c100bf417<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
plaintext:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
ciphertext:<br />
253957f10e861c7c<br />
0a12bb0193d384d9<br />
579db544666d50bd<br />
3252d6576c768a68<br />
<br />
MAC:<br />
a98c87f5769ab4cc<br />
ca3df863faeb217e<br />
b16ddc29d712b301<br />
12b446324ee806d6<br />
<br />
response:<br />
253957f10e861c7c<br />
0a12bb0193d384d9<br />
579db544666d50bd<br />
3252d6576c768a68<br />
a98c87f5769ab4cc<br />
ca3df863faeb217e<br />
b16ddc29d712b301<br />
12b446324ee806d6<br />
<br />
== /session ==<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
tokenID:<br />
6dcae8ff8f55a793<br />
a0fa1ed31115451b<br />
4df233b3a0641cc6<br />
18ecadfd1fe4a691<br />
<br />
reqHMACkey:<br />
1640a4e6bc8c8e54<br />
858be9960a8b0740<br />
fa06effdf169246f<br />
52012ae868fc6c48<br />
<br />
respHMACkey:<br />
7f3e075e74523ced<br />
fa817c2fa4ae97e1<br />
e51da38d7a992b66<br />
8a35c86af946b155<br />
<br />
respXORkey:<br />
02977a9167830705<br />
74b610cc25320262<br />
175b45fbd7b26438<br />
f9e200abc029f14e<br />
f38399314b172f1e<br />
e928fcdcd194ab19<br />
92433cab0e94569d<br />
bf623b46dd9fbf55<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
sessionToken:<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
plaintext:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
ciphertext:<br />
8216f812e3068182<br />
fc3f9a47a9bf8ced<br />
87cad7684327f2af<br />
617b9a305cb46fd1<br />
53223b92efb289b9<br />
418156777d3905b6<br />
22f28e18ba21e02a<br />
07db81fd612201ea<br />
<br />
MAC:<br />
639fd132f637abd3<br />
ecd2482ccf11ed76<br />
8cfd6979e1954046<br />
1e8ef5204e66c542<br />
<br />
response:<br />
8216f812e3068182<br />
fc3f9a47a9bf8ced<br />
87cad7684327f2af<br />
617b9a305cb46fd1<br />
53223b92efb289b9<br />
418156777d3905b6<br />
22f28e18ba21e02a<br />
07db81fd612201ea<br />
639fd132f637abd3<br />
ecd2482ccf11ed76<br />
8cfd6979e1954046<br />
1e8ef5204e66c542<br />
<br />
== /account/keys ==<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
tokenID:<br />
d010c94c753c012c<br />
d6801e8beb1aa6cc<br />
3da9ea3de3de1dee<br />
32785dbd99a579e8<br />
<br />
reqHMACkey:<br />
1707b05908acc4dc<br />
cda5b8304d9500d0<br />
8c53e00c31672a53<br />
490dfb5ef2934060<br />
<br />
respHMACkey:<br />
31d0c12186b76897<br />
c3351878a65097cf<br />
d595da4ce48e69a2<br />
485ff1a77c71b0d0<br />
<br />
respXORkey:<br />
eed35591e1f1c43b<br />
7cd604e371b9cfb7<br />
a980c9a36fa737c6<br />
a48c5d60a89fc291<br />
4ec1a2150a0777b7<br />
9a1e8499058cd17a<br />
ebc1441db8b3bf18<br />
2cd0aefa92482692<br />
<br />
kA:<br />
2021222324252627<br />
28292a2b2c2d2e2f<br />
3031323334353637<br />
38393a3b3c3d3e3f<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
plaintext:<br />
2021222324252627<br />
28292a2b2c2d2e2f<br />
3031323334353637<br />
38393a3b3c3d3e3f<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
ciphertext:<br />
cef277b2c5d4e21c<br />
54ff2ec85d94e198<br />
99b1fb905b9201f1<br />
9cb5675b94a2fcae<br />
0e80e0564e4231f0<br />
d257ced249c19f35<br />
bb90164eece6e94f<br />
7489f4a1ce1578cd<br />
<br />
MAC:<br />
86f1c57d2e7f6c97<br />
8181684e189b710f<br />
dd26a3f34e3aaed8<br />
64be9577ae81a256<br />
<br />
response:<br />
cef277b2c5d4e21c<br />
54ff2ec85d94e198<br />
99b1fb905b9201f1<br />
9cb5675b94a2fcae<br />
0e80e0564e4231f0<br />
d257ced249c19f35<br />
bb90164eece6e94f<br />
7489f4a1ce1578cd<br />
86f1c57d2e7f6c97<br />
8181684e189b710f<br />
dd26a3f34e3aaed8<br />
64be9577ae81a256<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
unwrapBKey:<br />
6ea660be9c89ec35<br />
5397f89afb282ea0<br />
bf21095760c8c500<br />
9bbcc894155bbe2a<br />
<br />
kB:<br />
2ee722fdd8ccaa72<br />
1bdeb2d1b76560ef<br />
ef705b04349d9357<br />
c3e592cf4906e075<br />
<br />
== use session (certificate/sign, etc) ==<br />
<br />
sessionToken:<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
tokenID:<br />
639503a218ffbb62<br />
983e9628be5cd64a<br />
0438d0ae81b2b9da<br />
deb900a83470bc6b<br />
<br />
reqHMACkey:<br />
3a0188943837ab22<br />
8fe74e759566d0e4<br />
837cbcc7494157aa<br />
c4da82025b2811b2<br />
<br />
== /password/change ==<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
tokenID:<br />
cafc36360afd92de<br />
5ca21800022a9af1<br />
3a5766b91bd82fd4<br />
0eaa5b6e01489796<br />
<br />
reqHMACkey:<br />
b07c0cf4553e44ff<br />
fe991caa2546b50d<br />
895fb9ac8f8746d2<br />
d29119d9616de193<br />
<br />
respHMACkey:<br />
d2ddfefd1913fa34<br />
48e18abda9b54c92<br />
43fd51bf14dc9091<br />
2179269c0e958a04<br />
<br />
respXORkey:<br />
dcc5425e13b876ea<br />
f1d3aa95a4735622<br />
46994088d86adb5a<br />
526d9f1f5d170254<br />
456dd26dcc54483e<br />
f489d55097b69028<br />
8826f0cf1985a6ad<br />
e3e83461517c8d49<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
accountResetToken:<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
plaintext:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
ciphertext:<br />
5c44c0dd973df06d<br />
795a201e28fed8ad<br />
d608d21b4cff4dcd<br />
caf40584c18a9ccb<br />
85ac10ae08918ef9<br />
3c401f9b5b7b5ee7<br />
58f7221ccd50707a<br />
3b31eeba8da15396<br />
<br />
MAC:<br />
cc3053fe922268d7<br />
9c0dd6eb74bd40f5<br />
07ae2d587483b864<br />
8ef771b699dd39d9<br />
<br />
response:<br />
5c44c0dd973df06d<br />
795a201e28fed8ad<br />
d608d21b4cff4dcd<br />
caf40584c18a9ccb<br />
85ac10ae08918ef9<br />
3c401f9b5b7b5ee7<br />
58f7221ccd50707a<br />
3b31eeba8da15396<br />
cc3053fe922268d7<br />
9c0dd6eb74bd40f5<br />
07ae2d587483b864<br />
8ef771b699dd39d9<br />
<br />
== /account/reset ==<br />
<br />
accountResetToken:<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
tokenID:<br />
a6857e5d53d35073<br />
d50ef2ce2c4dd747<br />
32bb2eae1af5bf79<br />
618ed945e1310792<br />
<br />
reqHMACkey:<br />
47fab27352ee6b48<br />
33938d76519bbdb8<br />
ac7293f8b5e74335<br />
6fdd1d5edf39f52d<br />
<br />
reqXORkey:<br />
82ed612313a11673 95108d7d379b2029<br />
7a539ce9d3861e95 1bf5a9b9cdbfb332<br />
bd6aba056ce0c568 2c5a93963446b1b4<br />
7397c8c24f3a1d67 2a0ddc856474f5b1<br />
33ab884ce33335c1 5578a1a7302933cb<br />
458fbee0a5e52414 c914beb97568a30c<br />
28364dc8fb03ae7c 76a2f324a9a1cee6<br />
71b74aa8906d0e03 39fb52a1bf2b1ef5<br />
ab5d883295db62af 20701cb3af42a09e<br />
c76cda585ab5644b 7250ef7b780537e5<br />
b3e784d37a118bd6 57a0fe29ec6e5cd3<br />
325be8e1d8a3dd71 b360ea266757e463<br />
ada6b0a7a85a8ac0 eed618d9f6ee91ab<br />
1d2f714f224d67db 46843c4e3339de15<br />
efe0297a45f9fe0d 6d768b5c589a290f<br />
11f03237192cc0a3 a02645a810d83bb1<br />
84d582bfb15d2393 3fa4805374da62c6<br />
a2c887b157285c6a 79b47156c9abe02e<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
newSRPv:<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
<br />
plaintext:<br />
4041424344454647 48494a4b4c4d4e4f<br />
5051525354555657 58595a5b5c5d5e5f<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
<br />
ciphertext:<br />
c2ac236057e45034 dd59c7367bd66e66<br />
2a02ceba87d348c2 43acf3e291e2ed6d<br />
ac7bab147df1d479 3d4b82872557a0a5<br />
6286d9d35e2b0c76 3b1ccd947565e4a0<br />
22ba995df22224d0 4469b0b6213822da<br />
549eaff1b4f43505 d805afa86479b21d<br />
39275cd9ea12bf6d 67b3e235b8b0dff7<br />
60a65bb9817c1f12 28ea43b0ae3a0fe4<br />
ba4c992384ca73be 31610da2be53b18f<br />
d67dcb494ba4755a 6341fe6a691426f4<br />
a2f695c26b009ac7 46b1ef38fd7f4dc2<br />
234af9f0c9b2cc60 a271fb377646f572<br />
bcb7a1b6b94b9bd1 ffc709c8e7ff80ba<br />
0c3e605e335c76ca 57952d5f2228cf04<br />
fef1386b54e8ef1c 7c679a4d498b381e<br />
00e12326083dd1b2 b13754b901c92aa0<br />
95c493aea04c3282 2eb5914265cb73d7<br />
b3d996a046394d7b 68a56047d8baf13f<br />
<br />
== /account/destroy ==<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
tokenID:<br />
b2512ff41c4e6d8a<br />
beb3bda37e326f51<br />
cf4efdbf90e50e77<br />
029be2563884b9fe<br />
<br />
reqHMACkey:<br />
75cfa782c19e41f9<br />
c7e125f3dc4c3bf1<br />
0a77c93a9999e06f<br />
b2646b3038e4ea44<br />
<br />
= Keyserver Protocol Summary =<br />
<br />
* POST /account/create (email,srpV,srpSalt) -> ok (server sends verification email)<br />
** creates a user account<br />
* GET /account/devices [sessionToken] () -> list of devices<br />
* GET /account/keys [keyFetchToken,needs-verf] () -> kA/wrap(kB)<br />
** single-use, only if email is verified, encrypted results<br />
* POST /account/reset [authed+encrypted by accountResetToken] (wrap(kB),srpV,srpSalt) -> ok<br />
** single-use, does not require email to be verified, revoke all tokens for account, send notification email to user<br />
* POST /account/delete [authToken] () -> ok, account deleted<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authToken] () -> keyFetchToken, sessionToken<br />
* POST /session/destroy [sessionToken] () -> ok<br />
** for detaching a device, destroy all tokens<br />
* POST /recovery_email/status [sessionToken] () -> "verified" status of email<br />
** use "Accept: text/event-stream" header for server-sent-events; server will send "update" event with the new content of the resource any time it changes.<br />
* POST /recovery_email/resend_code [sessionToken] () -> re-send verification email<br />
* POST /recovery_email/verify_code (code) -> set "verified" flag<br />
** this code will come from a clickable link and is an unauthenticated endpoint<br />
** this could maybe take the recovery method if that would be helpful<br />
** sets verified flag on recovery method<br />
* POST /certificate/sign [sessionToken,needs-verf] (pubkey) -> cert<br />
** only if recovery email is verified<br />
* POST /password/change/start [authToken,needs-verf] () -> accountResetToken, keyFetchToken<br />
* POST /password/forgot/send_code () -> forgotPasswordToken<br />
** sends code to recovery method (email for now, maybe SMS later)<br />
** this is a short code, not a clickable link<br />
* POST /password/forgot/resend_code (forgotPasswordToken) -> re-sends code<br />
* POST /password/forgot/verify_code (forgotPasswordToken, code) -> accountResetToken<br />
** sets verified flag on recovery method<br />
* POST /get_random_bytes<br />
<br />
<br />
== Typical Client Flows ==<br />
<br />
Create account<br />
<br />
* POST /account/create (email,srpV,srpSalt) -> ok (server sends verification email)<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authed with authToken]() -> keyFetchToken, sessionToken<br />
* GET /recovery_email/status [sessionToken] () -> "verified" status<br />
** (optional, only if user requests resend) POST /recovery_email/resend_code [sessionToken]() -> ok<br />
** POST /recovery_email/verify_code (code) -> ok<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
* POST /certificate/sign [sessionToken] (pubkey) -> cert<br />
<br />
<br />
Attach to new device<br />
<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authToken] () -> keyFetchToken, sessionToken<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
** (if unverified-error, do waitUntilEmailVerified, then try again)<br />
* POST /certificate/sign [sessionToken] (pubkey) -> cert<br />
<br />
<br />
Forgot password<br />
<br />
* POST /password/forgot/send_code (email) -> forgotPasswordToken<br />
* POST /password/forgot/verify_code (forgotPasswordToken, code) -> accountResetToken<br />
* POST /account/reset [authed+encrypted by accountResetToken] (0000,srpV,srpSalt) -> ok<br />
* GOTO "Attach to new device"<br />
<br />
<br />
Change Password<br />
<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /password/change/start [authToken] () -> accountResetToken, keyFetchToken<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
* POST /account/reset [authed+encrypted by accountResetToken] (wrap(kB),srpV,srpSalt) -> ok<br />
* GOTO "Attach to new device"</div>Warnerhttps://wiki.mozilla.org/index.php?title=Identity/AttachedServices/KeyServerProtocol&diff=692245Identity/AttachedServices/KeyServerProtocol2013-08-08T19:16:44Z<p>Warner: /* auth/start */</p>
<hr />
<div>= PiCL Key Server / IdP Protocol =<br />
<br />
NOTE: This specification is slowly converging on stability (31-Jul-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.<br />
<br />
The server is being developed in https://github.com/mozilla/picl-idp . This repo currently includes a demonstration client (node.js CLI).<br />
<br />
Note that all messages are delivered over an HTTPS connection. The client browser may also implement cert-pinning to improve on the certificate validation process. The protections described below are in addition to those provided by TLS.<br />
<br />
Remaining TODO items:<br />
<br />
* decide on client-side key-stretching parameters: http://keywrapping.appspot.com can help<br />
* finalize SRP questions (definition of M1, generation of a/b)<br />
* finalize proof-of-work/DoS-prevention details<br />
* decide how to rate-limit account-creation calls<br />
* confirm this is actually implementable inside Firefox (especially w.r.t. NSS and Android/Java crypto)<br />
<br />
= Creating The Account =<br />
<br />
The first act performed by a user is to create the account. They enter email+password into their browser, which then does the following steps:<br />
<br />
* decide upon stretching parameters (perhaps consulting the keyserver for recommendations, but imposing a minimum strength requirement). For now we used a fixed {firstPBKDF:20000, scrypt: {N:65536, r:8, p:1}, secondPBKDF:20000}.<br />
* randomly choose a 32-byte mainSalt (this should be unique, but is not secret)<br />
* choose the SRP group parameters (fixed: use the 2048-bit group described below)<br />
* perform key-stretching (as described below), derive stretchedPW and srpPW<br />
* randomly choose a 32-byte srpSalt (unique, but not secret)<br />
* create srpVerifier from srpPW and srpSalt (as described below)<br />
* deliver (email, stretchParams, mainSalt, srpParams, srpSalt) to the keyserver's "POST /account/create" API<br />
<br />
<br />
To limit abuse, the createAccount() should also require a fresh "createToken". This should be created by some other API, outside the scope of this document, that perhaps requires a CAPTCHA or something. createAccount() might also require a proof-of-work token, as described below.<br />
<br />
The server, when creating a new account, creates both kA and wrap(kB) as randomly-generated 256-bit (32-byte) strings. It stores these, along with all the remaining values, indexed by email, in the account table where they can be retrieved by getToken later.<br />
<br />
== Email Verification ==<br />
<br />
To prevent fixation attacks, we require new accounts to verify their configured recovery email address before letting them learn the generated keys or obtain a signed certificate. Nevertheless, we wish clients to forget the user's password while they wait for email verification to complete. To achieve this, clients can obtain a sessionToken before verification, but most APIs that require it will raise errors until verification is finished.<br />
<br />
The server will send email with a URL that contains a long random "verification code" in the "fragment" hash. This URL points to a static page with some javascript that submits the code to the "POST /account/recovery_methods/verify_code" API. The URL can be clicked by any browser (it is not bound to anything), and when the API is hit, the account is marked as verified.<br />
<br />
After the client submits /account/create, it performs the "/session/auth" login sequence below to obtain a sessionToken. It then polls the "GET /account/recovery_methods" (which requires a sessionToken but not account verification) until the user clicks the email link and the API reports verification is complete. Then the client uses "GET /account/keys" and "POST /certificate/sign", described below, to obtain kA, kB, and a signed certificate to talk to the storage server.<br />
<br />
= Login: Obtaining the authToken =<br />
<br />
To connect a browser to an existing account, we use the following login protocol to transform an email+password pair into a single-use authToken. We will use this in the next section to create a session, from which we can obtain encryption keys and signed certificates.<br />
<br />
This protocol starts by using key-stretching to transform the email+password into a "stretchedPW", then feeds this into an SRP protocol to get the authToken.<br />
<br />
[[File:PICL-IdPAuth-auth-start.png|IdP Auth Protocol]]<br />
<br />
This authToken can be used (once) to do one of the following:<br />
<br />
* /session/create: obtain a sessionToken (and keyFetchToken), which enables storage server access<br />
* /password/change: obtain an accountResetToken, to safely reset the account password<br />
* /account/delete: to delete the entire account<br />
<br />
The protocol is designed to enable parallelism between key-stretching and the initial network messages, to reduce the time it takes to connect a browser to the account. In total, the browser requires five messages in four roundtrips (1: /auth/start, 2: /auth/finish, 3: /session/create, 4: /account/keys and /certificate/sign in parallel) before it is ready to talk to the storage server.<br />
<br />
== /auth/start ==<br />
<br />
As soon as the user finishes typing in the email address, the client should send it in the "/auth/start" 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 loginSRPToken that is used to associate this request with the subsequent /auth/finish 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.<br />
<br />
To mitigate DoS abuse, /auth/start may also require a proof-of-work string, described below.<br />
<br />
== Client-Side Key Stretching ==<br />
<br />
"Key Stretching" is the practice of running a password through a computationally-expensive one-way function before using it for encryption or authentication. The goal is to make brute-force dictionary attacks more expensive, by raising the cost of testing each guess.<br />
<br />
To protect the user's class-B data against compromises of our keyserver, we perform this key stretching on the client, rather than on the server. We use the memory-hard "scrypt" function (pronounced "ess-crypt") for this purpose, as motivated by the attacker-cost studies in [[Identity/CryptoIdeas/01-PBKDF-scrypt]]. Slower (low-RAM) devices can still participate by outsourcing the scrypt step to an "scrypt helper". To provide at least minimal protection against a compromise of this helper, we sandwich the scrypt step between two PBKDF2 steps. The complete protocol looks like this:<br />
<br />
[[File:PICL-IdPAuth-stretch-KDF.png|Stretching KDF]]<br />
<br />
Our initial parameter choices for this stretching are to use 20000 iterations of PBKDF2 on each side (40k in all), and to run scrypt with N/r/p set to 64k/8/1. This requires roughly 60MB of RAM for about 1.3 seconds on a 1.8GHz Intel Atom linux box (it will run faster, but use the same memory, on more modern CPUs). We are still studying performance on various platforms to decide what parameters to use in V1: lowering them speeds up the login process, but reduces security (by reducing the cost of an dictionary attack).<br />
<br />
Since the stretching is expected to take a second or two, the client can optimistically start this process (using default parameters) before receiving the auth/start 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 "mainSalt is added *after* the stretching, to enable this parallelism (at a tiny cost in security).<br />
<br />
After "stretchedPW" is derived, a second HKDF call is used to derive "srpPW" and "unwrapBKey" which will be used later.<br />
<br />
[[File:PICL-IdPAuth-main-KDF.png|masterKey KDF]]<br />
<br />
== SRP Protocol Details ==<br />
<br />
The PiCL client uses the SRP protocol (http://srp.stanford.edu/) to prove that it knows the (stretched) account password without revealing the actual password (or information enabling a brute-force attack) to the server or any eavesdroppers. <br />
<br />
SRP is somewhat underspecified. We use SRP-6a, with SHA256 as the hash, and the 2048-bit modulus defined in RFC 5053 Appendix A. We consistently zero-pad all string values to 256 bytes (2048 bits), and use H(A+B+S) as the key-confirmation message "M1". These details, plus the SRP design papers and RFCs 2945 and 5054, should be enough to build a compatible implementation. The diagrams below and the test vectors at the end of this page can be used to verify compatibility.<br />
<br />
The server should use Jed's SRP module from https://github.com/jedp/node-srp . The client might use SJCL (http://crypto.stanford.edu/sjcl/) or native code (NSS).<br />
<br />
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).<br />
<br />
Note that SRP-6a uses a "k" value which basically encodes the group being used ("N" and "g"). Since all PICL accounts use the same 2048-bit group, they will all use the same "k" value (not to be confused with the per-session shared-secret "K" key that emerges from the protocol). This group's "k" integer is (as a base-10 number):<br />
259003859907<br />
09503006915442163037721228467470<br />
35652616593381637186118123578112<br />
<br />
=== SRP Verifier Calculation ===<br />
<br />
When the client first creates the account, it must combine the account email address, the stretched password (srpPW), and a randomly-generated srpSalt, to compute the srpVerifier. The server will use this verifier later, to check whether or not the client really knows the password.<br />
<br />
If the server is compromised and an attacker learns the srpVerifier for a given account, they cannot use this to directly log in (the verifier is not "password-equivalent"), but it does allow them to perform an offline brute-force attack against the user's password. In this respect, it is similar to a traditional hashed password. We make these attacks somewhat more expensive by performing the client-side stretching described above, instead of using the raw user password in the SRP calculation.<br />
<br />
[[File:PICL-IdPAuth-SRP-Verifier.png|client-side SRP Verifier calculation]]<br />
<br />
Test vectors are provided at the end of this page. The protocol requires that several values ("a", "b", "srpSalt") are chosen randomly, but for illustrative purposes, the test vectors use carefully-crafted non-random values. For the user's sake, please ensure implementations use proper random values instead of simply copying the test vectors.<br />
<br />
The client sends srpSalt and srpVerifier to the server when it creates the account. It will also re-compute the 'x' value (as an integer) during sign-in. The server will convert the srpVerifier string back into an integer ('v') for use during its own sign-in calculations.<br />
<br />
=== SRP Server-side Sign-In Flow ===<br />
<br />
When the user connects a new device to their account, they use the /auth/start API to start the SRP protocol. This sends the account email address to the server. The server looks up the stored srpVerifier for this account, creates a random 'b' integer, performs some math to compute the "B" number, then converts B into a string known as "srpB". "srpB" is returned to the client, along with srpSalt and the key-stretching parameters. "b" and "srpB" are retained for the subsequent /auth/finish call. '''Note that it is critical that the "b" integer remain secret on the server.''' The server also allocates a random loginSRPToken to connect the two calls.<br />
<br />
[[File:PICL-IdPAuth-SRP-Server.png|server-side SRP]]<br />
<br />
Later, /auth/start will be called with the client's srpA string and its M1 key-confirmation string. srpA is combined with srpB to calculate the "u" integer. srpA is also turned into an integer and used to compute the shared-secret "S" integer. S is then used to compute the shared key "srpK", which is the output of the SRP process.<br />
<br />
=== SRP Client Calculation ===<br />
<br />
While the client is waiting for the response to /auth/start, it begins its key-stretching calculations. Everything else must wait until the response to /auth/start arrives, which includes the key-stretching parameters (which are retroactively confirmed), srpSalt, and the server's generated srpB value.<br />
<br />
[[File:PICL-IdPAuth-SRP-Client.png|client-side SRP]]<br />
<br />
Once the client knows srpSalt, it computes the same "x" integer as it did in the middle of the srpVerifier calculation. It also converts srpB into an integer named "B". Then it creates a random "a" integer, uses it to compute the string "srpA", then combines srpA with the server's srpB to compute the "u" integer. It then combines the static "k", the password-derived "x", the combined "u", and the server's "B", together with some magic math, to derive the "S" integer. If everything went well, the client will compute the same "S" value as the server did. If not (the password was wrong, or the client is talking to a fake server that doesn't really know srpVerifier), then the two "S" values will not match.<br />
<br />
('''Again, it is critical that the client keep its "a" and "x" integers secret, both during and after the protocol run.''')<br />
<br />
To safely tell if the "S" values match, both client and server combine srpA, srpB, and their (independently) generated "S" strings to form a string named "M1". The client sends M1 (along with srpA) in the /session/auth/finish message. The server compares the client's copy of M1 against its own. If they match, the client knew the password and the server can safely respond with the encrypted account data. If they do not match, the client (or a man-in-the-middle attacker) did not know the password, and the client should increment a counter that can trigger defenses against online guessing attacks. The server must then return an error to the client, and '''not''' use or reveal srpK (or the correct M1) in any way.<br />
<br />
Both client and server also hash "S" into "srpK". This is the shared session key, from which specific message encryption and MAC keys are derived (as described below).<br />
<br />
=== SRP Notes ===<br />
<br />
The SRP "g" (generator) and "N" (prime modulus) should use the 2048-bit value from RFC 5054 Appendix A. 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.<br />
<br />
There are several places in SRP where integers (usually in the range 1..N-1) are converted into bytestrings, either for transmission over a wire, or to be passed into a hash function. The SRP spec is somewhat ambiguous about padding here: if the integer happens to be less than 2^2040, the simplest toString() approach will yield a '''255''' byte string, not a 256 byte string. PiCL consistently uses padding, so compatible implementations must prepend one or more NUL bytes to these short strings before transmission or hashing. The examples above were brute-forced to ensure that "srpVerifier", "srpA", "srpB", and "S" all wind up with leading zeros, to exercise the padding code in compatible implementations. If you are having problems getting your code to match these results, add some assertions to test that the stringified integers being put into hashes are exactly 256 bytes long.<br />
<br />
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 (/auth/finish).<br />
<br />
The server receives "A" in /auth/finish, 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 keyFetchToken+sessionToken as described below, returning the encrypted/MACed bundle in the response to /auth/finish.<br />
<br />
Outstanding crypto questions:<br />
<br />
* How exactly should the "a" and "b" integers be generated? The issue is of how much bias SRP can tolerate. Ideally these integers are uniformly distributed from 1 to N-1 (inclusive). The only way to obtain a purely uniform distribution from a source of random bytes is try-try-again: pick a (integral-number-of-bytes) number, compare it to the desired range, try again if it falls outside the range. If you get unlucky, this can take a lot of guesses, depending upon how close the range is to a power of two. ECDSA implementations tend to pick a number twice as long as the modulus and then modulo it down (rand(2^4096) % N), which yields a tiny fraction of a bit of bias. The cheaper approach is to do the same with a number of equal length (rand(2^2048) % N), which imposes more bias. Some SRP implementations appear to be satisfied by rand(2^256). We need more review here.<br />
* The original SRP papers defined M1=H(A+B+S) as we use here, but other implementation (in particular RFC2945) uses a curious construct that includes the username, the salt, and an odd XOR combination of N and g. We need to decide what to use for M1.<br />
<br />
== /auth/finish ==<br />
<br />
The client-side SRP calculation results in two values that are sent to the server in the /auth/finish 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.<br />
<br />
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.<br />
<br />
The server then allocates a single-use 32-byte random token named "authToken". It encrypts the token with the session key, and returns a success message with the encrypted bundle.<br />
<br />
All tokens have an associated tokenID, described below. The server needs to maintain a table that maps the tokenID to the token itself, so it can derive other values from the token later. The tokens are also associated with a specific account, so later API requests do not specify an email address or account ID.<br />
<br />
== Decrypting the /auth/finish Response ==<br />
<br />
The SRP session key ("srpK") is used to derive two other keys: respHMACkey and respXORkey.<br />
<br />
[[File:PICL-IdPAuth-encrypt-authToken.png|Decrypting the authToken]]<br />
<br />
The respXORkey is used to encrypt the authToken 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.<br />
<br />
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 returns the authToken value.<br />
<br />
= Creating a Session =<br />
<br />
For login, the single-use authToken is spent on a call to /session/create .<br />
This allocates two new (random 32-byte) tokens: a long-lived "sessionToken", and a single-use "keyFetchToken". The /session/create call returns an encrypted bundle containing the two tokens.<br />
<br />
[[File:PICL-IdPAuth-encrypt-sessionToken.png|Decrypting the sessionToken and keyFetchToken]]<br />
<br />
For calls which accept an authToken, the client uses authToken to derive three values:<br />
<br />
* tokenID<br />
* reqHMACkey<br />
* requestKey<br />
<br />
<br />
The client uses tokenID and reqHMACkey for a HAWK (https://github.com/hueniverse/hawk/) request to the "POST /session/create" API, using tokenID as "credentials.id" and reqHMACkey as "credentials.key". The server uses tokenID to look up the corresponding token, then derives reqHMACkey to validate the request.<br />
<br />
Each authToken-using call then derives additional API-specific values from requestKey. /session/create uses two derived values:<br />
<br />
* respHMACkey<br />
* respXORkey<br />
<br />
<br />
When the server receives a valid /session/create request, it allocates sessionToken and keyFetchToken, concatenates them, encrypts the pair by XORing it with the derived respXORkey, and attaches a MAC generated with respHMACkey. The encrypted MACed bundle is returned to the client.<br />
<br />
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 keyFetchToken and sessionToken values.<br />
<br />
Each authToken is single-use: once a successful request has been made with it, the authToken and its corresponding ID is removed from the server's memory, and subsequent attempts to use it will return a "no such token" error. The token is consumed even if the request fails (e.g. the MAC did not match).<br />
<br />
The authToken can be used (once) by multiple APIs. The server only needs to maintain one table mapping tokenID to authToken, because all these APIs share the same method of deriving a tokenID and reqHMACkey from the authToken.<br />
<br />
When a HAWK request appears, it should look up the tokenID in this table, retrieve the authToken, recompute reqHMACkey and requestKey, validate the request, delete the table entry, then hand the HTTP request and requestKey to the specific API handler. That handler should derive the API-specific values (respHMACkey, respXORkey, etc) to process the request or construct the response.<br />
<br />
The server can support multiple sessions per account (typically one per client device, plus perhaps others for account-management portals). There can also be multiple outstanding keyFetchTokens. The sessionToken lasts forever (until revoked by a password change or explicit revocation command), and can be used an unlimited number of times. The keyFetchToken expires after 60 seconds, and is single-use.<br />
<br />
= Obtaining keys kA and kB =<br />
<br />
Clients which have exchanged an authToken for either a sessionToken or an accountResetToken will also receive a keyFetchToken. This single-use token allows the client to retrieve kA and wrap(kB), which enables the client to encrypt and decrypt browser data (bookmarks, open-tabs, etc) correctly. As above, the keyFetchToken is used to derive tokenID, reqHMACkey, respHMACkey, and respXORkey, which are used in a HAWK request to the "GET /account/keys" API.<br />
<br />
The server pulls kA and wrap(kB) from the account table, concatenates them, encrypts the pair by XORing it with the derived respXORkey, and attaches a MAC generated with respHMACkey.<br />
<br />
[[File:PICL-IdPAuth-keys-server.png|keyFetchToken: server encrypts keys]]<br />
<br />
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 and wrap(kB) values.<br />
<br />
[[File:PICL-IdPAuth-keys-client.png|keyFetchToken: client decrypts keys]]<br />
<br />
Finally, the server-provided wrap(kB) value is simply XORed with the password-derived unwrapBKey (both are 32-byte strings) to obtain kB. There is no MAC on wrap(kB).<br />
<br />
[[File:PICL-IdPAuth-key-unwrap.png|unwrapping kB]]<br />
<br />
"kA" and "kB" enable the browser to encrypt/decrypt synchronized data records. They will be used to derive separate encryption and HMAC keys for each data collection (bookmarks, form-fill data, saved-password, open-tabs, etc). This will allow the user to share some data, but not everything, with a third party. The client may intentionally forget kA and kB (only retaining the derived keys) to reduce the power available to someone who steals their device.<br />
<br />
Note that /account/keys will not succeed until the account's email address has been verified. Also note that each keyFetchToken is single-use and short-lived. The token is consumed even if the request fails (e.g. the MAC does not match).<br />
<br />
= Signing Certificates =<br />
<br />
The sessionToken is used to derive two values:<br />
<br />
* tokenID<br />
* request HMAC key<br />
<br />
<br />
[[File:PICL-IdPAuth-use-session.png|Using the sessionToken, signing certificates]]<br />
<br />
The requestHMACkey is used in a HAWK request to provide integrity over many APIs, including /certificate/sign. requestHMACkey 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.<br />
<br />
For /certificate/sign, it is critical to enable payload verification by setting options.payload=true (on both client and server). Otherwise a man-in-the-middle could submit their own public key, get it signed, and then delete the user's data on the storage servers.<br />
<br />
The following keyserver APIs require a HAWK-protected request that uses the sessionToken. In addition, some require that the account be in the "verified" state:<br />
<br />
* GET /account/devices<br />
* POST /session/destroy<br />
* GET /recovery_email/status<br />
* POST /recovery_email/resend_code<br />
* POST /certificate/sign (requires "verified" account)<br />
<br />
= Resetting The Account =<br />
<br />
The account may be reset in two circumstances: when the user changes their password, and when the user forgets their password. In both cases, the client first obtains an "accountResetToken". This token is then used to change the SRP Verifier and either reset or replace the wrap(kB) value.<br />
<br />
== Changing the Password ==<br />
<br />
When the user wishes to change their password (i.e. they still know the old password), they first use the /auth/start+/auth/finish SRP protocol safely obtain an "authToken". They they use the "/password/change/start" API to exchange the authToken for an accountResetToken and a keyFetchToken.<br />
<br />
[[File:PICL-IdPAuth-encrypt-passwordChange.png|Server encrypts passwordChange response]]<br />
<br />
The accountResetToken will be used below to set the new password safely. The keyFetchToken should be used first, to obtain kB, so the subsequent account reset can replace wrap(kB) with a new value. This allows the password-changing client to retain their class-B data.<br />
<br />
Requiring an authToken proves that the user has provided the correct account password recently. When the account is reset, all active sessions and tokens will be cancelled (disconnecting all devices from the account). The client should immediately establish a new session as described above.<br />
<br />
This API is only used when the user knows their old password: if they have forgotten the password, use the "/password/forgot" APIs below.<br />
<br />
== Handling a Forgotten Password ==<br />
<br />
When the user has forgotten their password, they can use one of their "recovery methods" to obtain an accountResetToken. For now, this means we send a random code to the email address associated with their account. The user must copy this code from the email into their client, whereupon the client will get an accountResetToken that can be delivered to the API below.<br />
<br />
Note that, since the forgotten-password client never learns kB, any class-B data will be lost. This is necessary to protect class-B data from attackers who can read the users's email but do not know the account password (including those who compromise the IdP and the keyserver itself). When using /account/reset below, they will set wrap(kB) to a string of all zeros, which means the server should generate a new random wrap(kB) (just as it does during account creation).<br />
<br />
The /password/forgot/send_code API is used to ask the server to send a recovery code. This takes a recovery method, which for now is just an email address. This API is unauthenticated (after all, the user who has forgotten their password knows nothing but their email address). The server marks the corresponding account as "pending recovery", allocates a random forgotPasswordToken for the account, creates a recovery code, and sends the code (with instructions) via email. The API returns forgotPasswordToken to the client.<br />
<br />
The user must copy the recovery code into the same browser where they started the process. The client then submits the code to /password/forgot/verify_code along with the forgotPasswordToken they received. If they match, the server allocates a accountResetToken and returns it to the client. If they do not match, the server increments a counter (which is used to decide if an online guessing attack is happening).<br />
<br />
forgotPasswordToken can be used three times before it is exhausted. If the user guesses incorrectly this often, the client must call send_code again to get a new token and code. Each account has at most one token+code active at a time.<br />
<br />
The recovery code is initially a random 8-digit decimal number. If an attacker tries to sign in as someone else, hits the "forgot my password" button, then submits a guess to /password/forgot/verify_code, they will have a 1-in-100-million chance of success. If the server detects too many wrong guesses, it should increase the length of new codes. Another defensive technique is to require that users click an email link before being given the code: the server is told when the link is clicked, so the code will not be enabled until the email has been read. It remains to be seen whether this will be sufficient.<br />
<br />
The exact thresholds are TBD, but a nominal goal is to keep the chances of any attack succeeding to below 1-in-a-million per year. To achieve this, we can tolerate 100 verify_code failures in a single year before we must increase the length of the code.<br />
<br />
== Using accountResetToken ==<br />
<br />
An accountResetToken, obtained through either of the methods above, is used to invoke the /account/reset API. If the request is accepted, the server replaces the account password with a new one, updates the wrap(kB) value, cancels all active sessions and tokens (disconnecting all devices from the account), and sends a "your password has been changed" email to the user.<br />
<br />
If the client knew the old password, it can supply a new wrap(kB) value that will yield the same kB key as before, so no data will be lost. If the client did *not* know the old password, then it will supply a wrap(kB) of all-zeros, which tells the server to generate a new random wrap(kB) (just like during account creation), which means the class-B data will be lost.<br />
<br />
The client puts their new password through the same stretching procedure as described in the new-account section above, resulting in a new srpVerifier and unwrapBKey. If they knew the old kB, they XOR it with the new unwrapBKey to obtain a new wrap(kB).<br />
<br />
/account/reset needs request confidentiality, since the arguments include the newly wrapped kB value and the new SRP verifier, both of which enable a brute-force attack against the password. HAWK provides request integrity. The response is a single "ok" or "fail", conveyed by the HTTP headers, so we do not require response confidentiality, and can live without response integrity.<br />
<br />
So the single-use resetToken is used to derive three values:<br />
<br />
* tokenID<br />
* request HMAC key<br />
* request XOR key<br />
<br />
<br />
[[File:PICL-IdPAuth-encrypt-resetAccount.png|Client encrypts resetAccount request]]<br />
<br />
The request data will contain wrap(kB) and the new SRP verifier, concatenated together. Since we always pad the SRP verifier to the full (256-byte) group length, both pieces are fixed-length. We generate enough reqXORkey bytes to cover both values.<br />
<br />
The request data is XORed with requestXORkey, then delivered in the body of a HAWK request that uses tokenID as credentials.id and requestHMACkey as credentials.key . Note: it is critical to include the request body in the HAWK integrity check (options.payload=true, on both client and server), otherwise a man-in-the-middle could substitute their own SRP verifier, giving them control over the account (access to the user's class-A data, and a brute-force attack on their password).<br />
<br />
The client submits other values in the same request:<br />
<br />
* stretchParams<br />
* mainKDFSalt<br />
* srpSalt<br />
<br />
<br />
These values do not require confidentiality, so are not included in the encrypted bundle. They are still protected by the HAWK integrity check. Note that the server should assert that both salts are different than the previously stored values.<br />
<br />
After using /account/reset, clients should immediately perform the login protocol from above. If the old password was forgotten, this is necessary to fetch kA. In either case, a new sessionToken is required, since old sessions and tokens are revoked by /account/reset. Clients should retain the new srpPassword value during this process to avoid needing to run the lengthy key-stretching routine a second time.<br />
<br />
= Deleting The Account =<br />
<br />
When the user wishes to completely delete their account, the browser needs to perform two actions:<br />
<br />
* contact the storage servers and delete all records and collections<br />
* contact the keyserver and delete the account information<br />
<br />
The user should be prompted for their password as confirmation (i.e. a browser in the normal attached-and-synchronizing state should not be able to erase the account information: it must acquire a new authToken first).<br />
<br />
The device then obtains an authToken as described above, then spends it on a HAWK-protected request to the /account/destroy endpoint. This request contains no body and returns only a success code.<br />
<br />
[[File:PICL-IdPAuth-deleteAccount.png|Deleting the Account]]<br />
<br />
= Crypto Notes =<br />
<br />
Strong entropy is needed in the following places:<br />
<br />
* (client) creation of private "a" value inside SRP<br />
* (server) initial creation of kA and wrap(kB)<br />
* (server) creation of private "B" value inside SRP<br />
* (server) creation of signToken and resetToken<br />
<br />
<br />
On the server, code should get entropy from /dev/urandom via a function that uses it, like "crypto.randomBytes()" in node.js or "os.urandom()" in python. On the client, code should combine local entropy with some fetched from the keyserver via getEntropy(), to guard against failures in the local entropy pool. Something like HKDF(IKM=localEntropy+remoteEntropy, salt="", info=KW("mergeEntropy")).<br />
<br />
An HKDF-based stream cipher is used to protect the response for getToken2(), and the request for resetAccount(). HKDF is used to create a number of random bytes equal to the length of the message, then these are XORed with the plaintext to produce the ciphertext. An HMAC is then computed from the ciphertext, to protect the integrity of the message.<br />
<br />
HKDF, like any KDF, is defined to produce output that is indistinguishable from random data ("The HKDF Scheme", http://eprint.iacr.org/2010/264.pdf , by Hugo Krawczyk, section 3). XORing a plaintext with a random keystream to produce ciphertext is a simple and secure approach to data encryption, epitomized by AES-CTR or a stream cipher (http://cr.yp.to/snuffle/design.pdf). HKDF is not the fastest way to generate such a keystream, but it is safe, easy to specify, and easy to implement (just HMAC and XOR).<br />
<br />
Each keystream must be unique. SRP is defined to produce a random session key for each run (as long as at least one of the sides provides a random ephemeral key). We define resetToken to be a single-use randomly-generated value. Hence our two HKDF-XOR keystreams will be unique.<br />
<br />
A slightly more-traditional alternative would be to use AES-CTR (with the same HMAC-SHA256 used here), with a randomly-generated IV. This is equally secure, but requires implementors to obtain an AES library (with CTR mode, which does not seem to be universal). An even more traditional technique would be AES-CBC, which introduces the need for padding and a way to specify the length of the plaintext. The additional specification complexity, plus the library load, leads me to prefer HKDF+XOR.<br />
<br />
kB is equal to the XOR of wrapKey (which is a deterministic function of the user's email address, password, mainSalt, and the stretching parameters) and the server's randomly-generated wrap(kB) value, making kB a random value too. Using XOR as a wrapping function allows us to avoid sending kB or wrap(kB) in the initial createAccount arguments, which are not protected by SRP, and thus would enable an eavesdropper to mount a dictionary attack on the password (using wrap(kB) as their oracle).<br />
<br />
Likewise, allowing the server to generate kA avoids exposure during createAccount. The only point of vulnerability is a forgotten-password resetAccount() message, if an eavesdropper can learn the resetToken. In this case, the attacker might either use the resetToken themselves (then use signToken to learn kA and wrap(kB)), or passively decrypt the resetAccount() arguments to retrieve just wrap(kB). Given wrap(kB) and some encrypted browser data, the attacker can guess passwords (and derive kB, etc) until the data decrypts properly.<br />
<br />
To make this technique safe, any time kB or the password is changed, the mainSalt should be changed too. Otherwise knowledge of both wrap(old-kB) and old-kB would reveal wrapKey, making it easy to deduce the new kB. Changing mainSalt causes wrapKey to change too, preventing this.<br />
<br />
mainSalt is incorporated at the end of the stretching process to allow it to proceed in parallel with the getToken1() call that retrieves the salt. The inputs to the lengthy stretch come entirely from the user (email and password) or are optimistically (stretching parameters). This speedup seems more important than the minor security benefit of including the salt at the beginning of the stretch.<br />
<br />
There is no MAC on wrap(kB). If the keyserver chooses to deliver a bogus wrap(kB) or kA, the client will discover the problem a moment later when it talks to a storage server and attempts to retrieve data from an unrecognized collection-ID (since we intend to derive collection-IDs from the key used to encrypt their data, which will be derived from kA or kB as appropriate). It might be useful to add a checksum to kA and wrap(kB) to detect accidental corruption (e.g. store and deliver kA+SHA256(kA)), but this doesn't protect against intentional changes. We omit this checksum for now, assuming that disks will be reliable enough to let us never experience such failures.<br />
<br />
We use scrypt without a per-user salt. This is safe because the "password" input to scrypt is already diversified by the user's email address. The intention is to allow the scrypt-helper to run on anonymous data, so that an attacker who compromises this helper cannot easily learn the email addresses of the partially-stretched K1 values that it is receiving, confounding their attack.<br />
<br />
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. <br />
For /certificate/sign, we do not need request confidentiality or response confidentiality, since the client's pubkey and the resulting certificate will both be exposed over a similar SSL connection to the storage server later. And it is sufficient to rely on the response integrity provided by SSL, since the client can verify the returned certificate for itself. For the other keyserver APIs protected by HAWK, these properties are either unnecessary, or are provided by additional mechanisms.<br />
<br />
= Proof-Of-Work =<br />
<br />
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(). The server can control how much work is required.<br />
<br />
The getToken1() call looks for a "X-PiCL-PoW:" HTTP header. Most of the time, clients don't supply this header. But if the server responds to the getToken1() call with an error that indicates PoW is required, clients must create a valid PoW string and include it as the value of an "X-PiCL-PoW:" header in their next call to getToken1().<br />
<br />
The server's error message includes two parameters. The first is a "prefix string": the client's PoW string is required to begin with this prefix. The second is a "threshold hash". SHA256(PoWString) is required to be lexicographically earlier than the thresholdHashString (i.e. the numerical value of its hash must be closer to zero than the threshold). The client is expected to concatenate the prefix with a counter, then repeatedly increment the counter and hash the result until they meet the threshold, then re-submit their getToken1() request with the combined prefix+counter string in the header. If the client has spent more than e.g. 10 seconds doing this, the client should probably help the user cancel the operation and try again.<br />
<br />
When a server is under a DoS attack (either via some manual configuration tool or sensed automatically), it should start requiring valid unique X-PiCL-PoW headers. The server should initially require very little work, by using a threshold hash with just a few leading zero bits. If this is insufficient to reduce the attack volume, the threshold should be lowered, requiring even more work (from both the attacker and legitimate clients).<br />
<br />
The server should create a prefix string that contains a parseable timestamp and a random nonce (e.g. "%d-%d-" % (int(time.time()), b32encode(os.urandom(8)))). The server should also decide on a cutoff time (perhaps ten minutes ago). Each server must then maintain a table of "old PoW strings" to prevent replay attacks (these do not need to be shared among all servers: an in-RAM cache is fine).<br />
<br />
When the server receives a proposed PoW string, it first splits off the leading timestamp, and if the timestamp is older than the cutoff time, it rejects the string (either by dropping the connection, or returning a new "PoW required" error if it's feeling nice). Then it hashes the whole string and compares it against the threshold, rejecting those which fail to meet the threshold. Finally, for strings that pass the hash threshold, it checks the "old strings" table, and rejects any that appear on that list.<br />
<br />
If the PoW string makes it past all these checks, the server should add the string to the "old strings" table, then accept the request (i.e. compute an srpB value and add a session-id table entry for the request).<br />
<br />
The old-strings table check should be optimized to reject present strings quickly (i.e. if we are under attack, we should expect to see lots of duplicates of the same string, and must minimize the work we do when this occurs).<br />
<br />
The server can remove values from the old-strings table that have timestamps older than the cutoff time. The server can also discard values at other times (to avoid consuming too much memory), without losing anything but protection against resource consumption.<br />
<br />
Other notes:<br />
<br />
The server-side code for this can be deferred until we care to have a response to a DoS attack. However the client-side code for this must be present from day one, otherwise we won't be able to turn on the defense later without fear of disabling legitimate old clients.<br />
<br />
The server should perform as little work as possible before rejecting a token. Every extra CPU cycle it spends in this path is increasing the DoS attack amplification factor.<br />
<br />
The nonce in the prefix string exists to make sure that two successive clients get different prefixes, and thus do not come up with the same counter value (and inadvertently create identical strings, looking like a replay attack). If this proved annoying or expensive, we could instead obligate clients to produce their own nonce.<br />
<br />
TBD: Is this worth it? Should the PoW string go into an HTTP header? (I want it to be cheap to extract, and not clutter logs). Should the error response be a distinctive HTTP error code so our monitoring tools can easily count them? We can also use this feature to slow down online guessing attacks (i.e. trigger it either when getToken1 is called too much or when getToken2 produces too many errors). Since getToken1() includes an email address, we could also requires PoWs for some addresses (e.g. those we know to be under attack) but not others.<br />
<br />
= Glossary =<br />
<br />
This defines some of the jargon we've developed for this protocol.<br />
<br />
* data classes: each type of browser data (bookmarks, passwords, history, etc) can be assigned, by the user, to either class-A or class-B<br />
* class-A: data assigned to this class can be recovered, even if the user forgets their password, by proving control over an email address and resetting the account. It can also be read by Mozilla (since it runs the keyserver and knows kA), or by the user's IdP (by resetting the account without the user's permission).<br />
* class-B: data in this class cannot be recovered if the password is forgotten. It cannot be read by the IdP. Mozilla (via the keyserver) cannot read this data, but can attempt a brute-force dictionary attack against the password.<br />
* kA: the master key for data stored as "class-A", a 32-byte binary string. Individual encryption keys for different datatypes are derived from kA.<br />
* kB: the master key for data stored as "class-B", a 32-byte binary string.<br />
* wrap(kB): an encrypted copy of kB. The keyserver stores wrap(kB) and never sees kB itself. The client (browser) uses a key derived from the user's password to decrypt wrap(kB), obtaining the real kB.<br />
* sessionToken: a long-lived per-device token which allows the device to obtained signed BrowserID certificates for the account's identity (GUID@picl-something.org). This token remains valid until the user revokes it (either by changing their password, or triggering some kind of "revoke a specific device" or "revoke all devices" function).<br />
<br />
= Test Vectors =<br />
<br />
The following example uses a non-ASCII email address of "andré@example.org" (with an accented "e", UTF8 encoding is 616e6472c3a9406578616d706c652e6f7267) and a non-ascii password of "pässwörd" (with accents on "a" and "o", UTF8 encoding is 70c3a4737377c3b67264).<br />
<br />
These test vectors were produced by the python code in https://github.com/warner/picl-spec-crypto . The diagrams may lag behind the latest version of that code.<br />
<br />
== stretch-KDF ==<br />
<br />
email:<br />
616e6472c3a94065<br />
78616d706c652e6f<br />
7267<br />
<br />
password:<br />
70c3a4737377c3b6<br />
7264<br />
<br />
K1 (scrypt input):<br />
f84913e3d8e6d624<br />
689d0a3e9678ac8d<br />
cc79d2c2f3d96414<br />
88cd9d6ef6cd83dd<br />
<br />
K2 (scrypt output):<br />
5b82f146a6412692<br />
3e4167a0350bb181<br />
feba61f63cb17140<br />
12b19cb0be0119c5<br />
<br />
stretchedPW:<br />
c16d46c31bee242c<br />
b31f916e9e38d60b<br />
76431d3f5304549c<br />
c75ae4bc20c7108c<br />
<br />
== main-KDF ==<br />
<br />
mainSalt (normally random):<br />
00f0000000000000<br />
0000000000000000<br />
0000000000000000<br />
000000000000034d<br />
<br />
srpPW:<br />
00f9b71800ab5337<br />
d51177d8fbc682a3<br />
653fa6dae5b87628<br />
eeec43a18af59a9d<br />
<br />
unwrapBKey:<br />
6ea660be9c89ec35<br />
5397f89afb282ea0<br />
bf21095760c8c500<br />
9bbcc894155bbe2a<br />
<br />
internal x (base 10):<br />
8192518690918<br />
99580124814080709381476194749939<br />
03899664126296984459627523279550<br />
<br />
internal x (hex):<br />
b5200337cc3f3f92<br />
6cdddae0b2d31029<br />
c069936a844aff58<br />
779a545be89d0abe<br />
<br />
v (verifier as number) (base 10):<br />
114649<br />
57230405843056840989945621595830<br />
71784395917725741221739574165799<br />
54316134303691657140298181419198<br />
87853709633756255809680435884948<br />
69849281177012209169281795507853<br />
57610332070005048463659745521969<br />
83218225819721112680718485091921<br />
64608360806562626442477160609654<br />
43167308814558974899899506977051<br />
96721477608178869100211706638584<br />
53875100985456239693728258285562<br />
04889672594983678412848291529879<br />
88548996842770025110751388952323<br />
22170663943486107183421205517476<br />
84831590615660554713667726412525<br />
73641352721966728239512914666806<br />
49625530438034148797508015907639<br />
67594925530663571631035463732161<br />
30193328802116982288883318596822<br />
<br />
== SRP Verifier ==<br />
<br />
k (base 10):<br />
259003859907<br />
09503006915442163037721228467470<br />
35652616593381637186118123578112<br />
<br />
srpSalt (normally random):<br />
00f1000000000000<br />
0000000000000000<br />
0000000000000000<br />
0000000000000179<br />
<br />
srpVerifier:<br />
00173ffa0263e63c cfd6791b8ee2a40f<br />
048ec94cd95aa8a3 125726f9805e0c82<br />
83c658dc0b607fbb 25db68e68e93f265<br />
8483049c68af7e82 14c49fde2712a775<br />
b63e545160d64b00 189a86708c69657d<br />
a7a1678eda0cd79f 86b8560ebdb1ffc2<br />
21db360eab901d64 3a75bf1205070a57<br />
91230ae56466b8c3 c1eb656e19b794f1<br />
ea0d2a077b3a7553 50208ea0118fec8c<br />
4b2ec344a05c66ae 1449b32609ca7189<br />
451c259d65bd15b3 4d8729afdb5faff8<br />
af1f3437bbdc0c3d 0b069a8ab2a959c9<br />
0c5a43d42082c774 90f3afcc10ef5648<br />
625c0605cdaace6c 6fdc9e9a7e6635d6<br />
19f50af773452247 0502cab26a52a198<br />
f5b00a2798589165 07b0b4e9ef9524d6<br />
<br />
== SRP B ==<br />
<br />
private b (normally random) (base 10):<br />
1198277<br />
66042000957856349411550091527197<br />
08125226960544768993643095227800<br />
29105361555030352774505625606029<br />
71778328003253459733139844872578<br />
33596964143417216389157582554932<br />
02841499373672188315534280693274<br />
23189198736863575142046053414928<br />
39548731879043135718309539706489<br />
29157321423483527294767988835942<br />
53343430842313006332606344714480<br />
99439808861069316482621424231409<br />
08830704769167700098392968117727<br />
43420990997238759832829219109897<br />
32876428831985487823417312772399<br />
92628295469389578458363237146486<br />
38545526799188280210660508721582<br />
00403102624831815596140094933216<br />
29832845626116777080504444704039<br />
04739431335617585333671378812943<br />
<br />
private b (hex):<br />
00f3000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 000000000000000f<br />
<br />
transmitted srpB:<br />
0022ce5a7b9d8127 7172caa20b0f1efb<br />
4643b3becc535664 73959b07b790d3c3<br />
f08650d5531c19ad 30ebb67bdb481d1d<br />
9cf61bf272f84398 48fdda58a4e6abc5<br />
abb2ac496da5098d 5cbf90e29b4b110e<br />
4e2c033c70af7392 5fa37457ee13ea3e<br />
8fde4ab516dff1c2 ae8e57a6b264fb9d<br />
b637eeeae9b5e43d faba9b329d3b8770<br />
ce89888709e02627 0e474eef822436e6<br />
397562f284778673 a1a7bc12b6883d1c<br />
21fbc27ffb3dbeb8 5efda279a69a1941<br />
4969113f10451603 065f0a0126666456<br />
51dde44a52f4d8de 113e2131321df1bf<br />
4369d2585364f9e5 36c39a4dce33221b<br />
e57d50ddccb4384e 3612bbfd03a268a3<br />
6e4f7e01de651401 e108cc247db50392<br />
<br />
== SRP A ==<br />
<br />
private a (normally random) (base 10):<br />
1193346<br />
47663227291363113405741243413916<br />
43482736314616601220006703889414<br />
28162541137108417166380088052095<br />
43910927476491099816542561560345<br />
50331133015255005622124012256352<br />
06121987030570656676375703406470<br />
63422988042473190059156975005813<br />
46381864669664357382020200036915<br />
26156674010218162984912976536206<br />
14440782978764393137821956464627<br />
16314542157937343986808167341567<br />
89864323268060014089757606109012<br />
50649711198896213496068605039486<br />
22864591676298304745954690086093<br />
75374681084741884719851454277570<br />
80362211874088739962880012800917<br />
05751238004976540634839106888223<br />
63866455314898189520502368799907<br />
19946264951520393624479315579863<br />
<br />
private a (hex):<br />
00f2000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 000000000000d3d7<br />
<br />
transmitted srpA:<br />
007da76cb7e77af5 ab61f334dbd5a958<br />
513afcdf0f47ab99 271fc5f7860fe213<br />
2e5802ca79d2e5c0 64bb80a38ee08771<br />
c98a937696698d87 8d78571568c98a1c<br />
40cc6e7cb101988a 2f9ba3d65679027d<br />
4d9068cb8aad6ebf f0101bab6d52b5fd<br />
fa81d2ed48bba119 d4ecdb7f3f478bd2<br />
36d5749f2275e948 4f2d0a9259d05e49<br />
d78a23dd26c60bfb a04fd346e5146469<br />
a8c3f010a627be81 c58ded1caaef2363<br />
635a45f97ca0d895 cc92ace1d09a99d6<br />
beb6b0dc0829535c 857a419e834db128<br />
64cd6ee8a843563b 0240520ff0195735<br />
cd9d316842d5d3f8 ef7209a0bb4b54ad<br />
7374d73e79be2c39 75632de562c59647<br />
0bb27bad79c3e2fc ddf194e1666cb9fc<br />
<br />
== SRP key-agreement ==<br />
<br />
u:<br />
b284aa1064e87751 50da6b5e2147b47c<br />
a7df505bed94a6f4 bb2ad873332ad732<br />
<br />
S:<br />
0092aaf0f527906a a5e8601f5d707907<br />
a03137e1b601e04b 5a1deb02a981f4be<br />
037b39829a27dba5 0f1b27545ff2e287<br />
29c2b79dcbdd32c9 d6b20d340affab91<br />
a626a8075806c26f e39df91d0ad979f9<br />
b2ee8aad1bc783e7 097407b63bfe58d9<br />
118b9b0b2a7c5c4c debaf8e9a460f4bf<br />
6247b0da34b760a5 9fac891757ddedca<br />
f08eed823b090586 c63009b2d740cc9f<br />
5397be89a2c32cdc fe6d6251ce11e44e<br />
6ecbdd9b6d93f30e 90896d2527564c7e<br />
b9ff70aa91acc0ba c1740a11cd184ffb<br />
989554ab58117c21 96b353d70c356160<br />
100ef5f4c28d19f6 e59ea2508e8e8aac<br />
6001497c27f362ed bafb25e0f045bfdf<br />
9fb02db9c908f103 40a639fe84c31b27<br />
<br />
M1:<br />
27949ec1e0f16256<br />
33436865edb037e2<br />
3eb6bf5cb91873f2<br />
a2729373c2039008<br />
<br />
srpK:<br />
e68fd0112bfa31dc<br />
ffc8e9c96a1cbadb<br />
4c3145978ff35c73<br />
e5bf8d30bbc7499a<br />
<br />
== /auth ==<br />
<br />
srpK:<br />
e68fd0112bfa31dc<br />
ffc8e9c96a1cbadb<br />
4c3145978ff35c73<br />
e5bf8d30bbc7499a<br />
<br />
respHMACkey:<br />
6584613597ef012f<br />
f1752b7869f01d03<br />
c72547a7b7199681<br />
531d9df1991edf23<br />
<br />
respXORkey:<br />
455835926ae37a1b<br />
627bd16affbeeab6<br />
27ecc737121826ca<br />
4a2bac2c100bf417<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
plaintext:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
ciphertext:<br />
253957f10e861c7c<br />
0a12bb0193d384d9<br />
579db544666d50bd<br />
3252d6576c768a68<br />
<br />
MAC:<br />
a98c87f5769ab4cc<br />
ca3df863faeb217e<br />
b16ddc29d712b301<br />
12b446324ee806d6<br />
<br />
response:<br />
253957f10e861c7c<br />
0a12bb0193d384d9<br />
579db544666d50bd<br />
3252d6576c768a68<br />
a98c87f5769ab4cc<br />
ca3df863faeb217e<br />
b16ddc29d712b301<br />
12b446324ee806d6<br />
<br />
== /session ==<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
tokenID:<br />
6dcae8ff8f55a793<br />
a0fa1ed31115451b<br />
4df233b3a0641cc6<br />
18ecadfd1fe4a691<br />
<br />
reqHMACkey:<br />
1640a4e6bc8c8e54<br />
858be9960a8b0740<br />
fa06effdf169246f<br />
52012ae868fc6c48<br />
<br />
respHMACkey:<br />
7f3e075e74523ced<br />
fa817c2fa4ae97e1<br />
e51da38d7a992b66<br />
8a35c86af946b155<br />
<br />
respXORkey:<br />
02977a9167830705<br />
74b610cc25320262<br />
175b45fbd7b26438<br />
f9e200abc029f14e<br />
f38399314b172f1e<br />
e928fcdcd194ab19<br />
92433cab0e94569d<br />
bf623b46dd9fbf55<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
sessionToken:<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
plaintext:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
ciphertext:<br />
8216f812e3068182<br />
fc3f9a47a9bf8ced<br />
87cad7684327f2af<br />
617b9a305cb46fd1<br />
53223b92efb289b9<br />
418156777d3905b6<br />
22f28e18ba21e02a<br />
07db81fd612201ea<br />
<br />
MAC:<br />
639fd132f637abd3<br />
ecd2482ccf11ed76<br />
8cfd6979e1954046<br />
1e8ef5204e66c542<br />
<br />
response:<br />
8216f812e3068182<br />
fc3f9a47a9bf8ced<br />
87cad7684327f2af<br />
617b9a305cb46fd1<br />
53223b92efb289b9<br />
418156777d3905b6<br />
22f28e18ba21e02a<br />
07db81fd612201ea<br />
639fd132f637abd3<br />
ecd2482ccf11ed76<br />
8cfd6979e1954046<br />
1e8ef5204e66c542<br />
<br />
== /account/keys ==<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
tokenID:<br />
d010c94c753c012c<br />
d6801e8beb1aa6cc<br />
3da9ea3de3de1dee<br />
32785dbd99a579e8<br />
<br />
reqHMACkey:<br />
1707b05908acc4dc<br />
cda5b8304d9500d0<br />
8c53e00c31672a53<br />
490dfb5ef2934060<br />
<br />
respHMACkey:<br />
31d0c12186b76897<br />
c3351878a65097cf<br />
d595da4ce48e69a2<br />
485ff1a77c71b0d0<br />
<br />
respXORkey:<br />
eed35591e1f1c43b<br />
7cd604e371b9cfb7<br />
a980c9a36fa737c6<br />
a48c5d60a89fc291<br />
4ec1a2150a0777b7<br />
9a1e8499058cd17a<br />
ebc1441db8b3bf18<br />
2cd0aefa92482692<br />
<br />
kA:<br />
2021222324252627<br />
28292a2b2c2d2e2f<br />
3031323334353637<br />
38393a3b3c3d3e3f<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
plaintext:<br />
2021222324252627<br />
28292a2b2c2d2e2f<br />
3031323334353637<br />
38393a3b3c3d3e3f<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
ciphertext:<br />
cef277b2c5d4e21c<br />
54ff2ec85d94e198<br />
99b1fb905b9201f1<br />
9cb5675b94a2fcae<br />
0e80e0564e4231f0<br />
d257ced249c19f35<br />
bb90164eece6e94f<br />
7489f4a1ce1578cd<br />
<br />
MAC:<br />
86f1c57d2e7f6c97<br />
8181684e189b710f<br />
dd26a3f34e3aaed8<br />
64be9577ae81a256<br />
<br />
response:<br />
cef277b2c5d4e21c<br />
54ff2ec85d94e198<br />
99b1fb905b9201f1<br />
9cb5675b94a2fcae<br />
0e80e0564e4231f0<br />
d257ced249c19f35<br />
bb90164eece6e94f<br />
7489f4a1ce1578cd<br />
86f1c57d2e7f6c97<br />
8181684e189b710f<br />
dd26a3f34e3aaed8<br />
64be9577ae81a256<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
unwrapBKey:<br />
6ea660be9c89ec35<br />
5397f89afb282ea0<br />
bf21095760c8c500<br />
9bbcc894155bbe2a<br />
<br />
kB:<br />
2ee722fdd8ccaa72<br />
1bdeb2d1b76560ef<br />
ef705b04349d9357<br />
c3e592cf4906e075<br />
<br />
== use session (certificate/sign, etc) ==<br />
<br />
sessionToken:<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
tokenID:<br />
639503a218ffbb62<br />
983e9628be5cd64a<br />
0438d0ae81b2b9da<br />
deb900a83470bc6b<br />
<br />
reqHMACkey:<br />
3a0188943837ab22<br />
8fe74e759566d0e4<br />
837cbcc7494157aa<br />
c4da82025b2811b2<br />
<br />
== /password/change ==<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
tokenID:<br />
cafc36360afd92de<br />
5ca21800022a9af1<br />
3a5766b91bd82fd4<br />
0eaa5b6e01489796<br />
<br />
reqHMACkey:<br />
b07c0cf4553e44ff<br />
fe991caa2546b50d<br />
895fb9ac8f8746d2<br />
d29119d9616de193<br />
<br />
respHMACkey:<br />
d2ddfefd1913fa34<br />
48e18abda9b54c92<br />
43fd51bf14dc9091<br />
2179269c0e958a04<br />
<br />
respXORkey:<br />
dcc5425e13b876ea<br />
f1d3aa95a4735622<br />
46994088d86adb5a<br />
526d9f1f5d170254<br />
456dd26dcc54483e<br />
f489d55097b69028<br />
8826f0cf1985a6ad<br />
e3e83461517c8d49<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
accountResetToken:<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
plaintext:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
ciphertext:<br />
5c44c0dd973df06d<br />
795a201e28fed8ad<br />
d608d21b4cff4dcd<br />
caf40584c18a9ccb<br />
85ac10ae08918ef9<br />
3c401f9b5b7b5ee7<br />
58f7221ccd50707a<br />
3b31eeba8da15396<br />
<br />
MAC:<br />
cc3053fe922268d7<br />
9c0dd6eb74bd40f5<br />
07ae2d587483b864<br />
8ef771b699dd39d9<br />
<br />
response:<br />
5c44c0dd973df06d<br />
795a201e28fed8ad<br />
d608d21b4cff4dcd<br />
caf40584c18a9ccb<br />
85ac10ae08918ef9<br />
3c401f9b5b7b5ee7<br />
58f7221ccd50707a<br />
3b31eeba8da15396<br />
cc3053fe922268d7<br />
9c0dd6eb74bd40f5<br />
07ae2d587483b864<br />
8ef771b699dd39d9<br />
<br />
== /account/reset ==<br />
<br />
accountResetToken:<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
tokenID:<br />
a6857e5d53d35073<br />
d50ef2ce2c4dd747<br />
32bb2eae1af5bf79<br />
618ed945e1310792<br />
<br />
reqHMACkey:<br />
47fab27352ee6b48<br />
33938d76519bbdb8<br />
ac7293f8b5e74335<br />
6fdd1d5edf39f52d<br />
<br />
reqXORkey:<br />
82ed612313a11673 95108d7d379b2029<br />
7a539ce9d3861e95 1bf5a9b9cdbfb332<br />
bd6aba056ce0c568 2c5a93963446b1b4<br />
7397c8c24f3a1d67 2a0ddc856474f5b1<br />
33ab884ce33335c1 5578a1a7302933cb<br />
458fbee0a5e52414 c914beb97568a30c<br />
28364dc8fb03ae7c 76a2f324a9a1cee6<br />
71b74aa8906d0e03 39fb52a1bf2b1ef5<br />
ab5d883295db62af 20701cb3af42a09e<br />
c76cda585ab5644b 7250ef7b780537e5<br />
b3e784d37a118bd6 57a0fe29ec6e5cd3<br />
325be8e1d8a3dd71 b360ea266757e463<br />
ada6b0a7a85a8ac0 eed618d9f6ee91ab<br />
1d2f714f224d67db 46843c4e3339de15<br />
efe0297a45f9fe0d 6d768b5c589a290f<br />
11f03237192cc0a3 a02645a810d83bb1<br />
84d582bfb15d2393 3fa4805374da62c6<br />
a2c887b157285c6a 79b47156c9abe02e<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
newSRPv:<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
<br />
plaintext:<br />
4041424344454647 48494a4b4c4d4e4f<br />
5051525354555657 58595a5b5c5d5e5f<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
<br />
ciphertext:<br />
c2ac236057e45034 dd59c7367bd66e66<br />
2a02ceba87d348c2 43acf3e291e2ed6d<br />
ac7bab147df1d479 3d4b82872557a0a5<br />
6286d9d35e2b0c76 3b1ccd947565e4a0<br />
22ba995df22224d0 4469b0b6213822da<br />
549eaff1b4f43505 d805afa86479b21d<br />
39275cd9ea12bf6d 67b3e235b8b0dff7<br />
60a65bb9817c1f12 28ea43b0ae3a0fe4<br />
ba4c992384ca73be 31610da2be53b18f<br />
d67dcb494ba4755a 6341fe6a691426f4<br />
a2f695c26b009ac7 46b1ef38fd7f4dc2<br />
234af9f0c9b2cc60 a271fb377646f572<br />
bcb7a1b6b94b9bd1 ffc709c8e7ff80ba<br />
0c3e605e335c76ca 57952d5f2228cf04<br />
fef1386b54e8ef1c 7c679a4d498b381e<br />
00e12326083dd1b2 b13754b901c92aa0<br />
95c493aea04c3282 2eb5914265cb73d7<br />
b3d996a046394d7b 68a56047d8baf13f<br />
<br />
== /account/destroy ==<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
tokenID:<br />
b2512ff41c4e6d8a<br />
beb3bda37e326f51<br />
cf4efdbf90e50e77<br />
029be2563884b9fe<br />
<br />
reqHMACkey:<br />
75cfa782c19e41f9<br />
c7e125f3dc4c3bf1<br />
0a77c93a9999e06f<br />
b2646b3038e4ea44<br />
<br />
= Keyserver Protocol Summary =<br />
<br />
* POST /account/create (email,srpV,srpSalt) -> ok (server sends verification email)<br />
** creates a user account<br />
* GET /account/devices [sessionToken] () -> list of devices<br />
* GET /account/keys [keyFetchToken,needs-verf] () -> kA/wrap(kB)<br />
** single-use, only if email is verified, encrypted results<br />
* POST /account/reset [authed+encrypted by accountResetToken] (wrap(kB),srpV,srpSalt) -> ok<br />
** single-use, does not require email to be verified, revoke all tokens for account, send notification email to user<br />
* POST /account/delete [authToken] () -> ok, account deleted<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authToken] () -> keyFetchToken, sessionToken<br />
* POST /session/destroy [sessionToken] () -> ok<br />
** for detaching a device, destroy all tokens<br />
* POST /recovery_email/status [sessionToken] () -> "verified" status of email<br />
** use "Accept: text/event-stream" header for server-sent-events; server will send "update" event with the new content of the resource any time it changes.<br />
* POST /recovery_email/resend_code [sessionToken] () -> re-send verification email<br />
* POST /recovery_email/verify_code (code) -> set "verified" flag<br />
** this code will come from a clickable link and is an unauthenticated endpoint<br />
** this could maybe take the recovery method if that would be helpful<br />
** sets verified flag on recovery method<br />
* POST /certificate/sign [sessionToken,needs-verf] (pubkey) -> cert<br />
** only if recovery email is verified<br />
* POST /password/change/start [authToken,needs-verf] () -> accountResetToken, keyFetchToken<br />
* POST /password/forgot/send_code () -> forgotPasswordToken<br />
** sends code to recovery method (email for now, maybe SMS later)<br />
** this is a short code, not a clickable link<br />
* POST /password/forgot/resend_code (forgotPasswordToken) -> re-sends code<br />
* POST /password/forgot/verify_code (forgotPasswordToken, code) -> accountResetToken<br />
** sets verified flag on recovery method<br />
* POST /get_random_bytes<br />
<br />
<br />
== Typical Client Flows ==<br />
<br />
Create account<br />
<br />
* POST /account/create (email,srpV,srpSalt) -> ok (server sends verification email)<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authed with authToken]() -> keyFetchToken, sessionToken<br />
* GET /recovery_email/status [sessionToken] () -> "verified" status<br />
** (optional, only if user requests resend) POST /recovery_email/resend_code [sessionToken]() -> ok<br />
** POST /recovery_email/verify_code (code) -> ok<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
* POST /certificate/sign [sessionToken] (pubkey) -> cert<br />
<br />
<br />
Attach to new device<br />
<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authToken] () -> keyFetchToken, sessionToken<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
** (if unverified-error, do waitUntilEmailVerified, then try again)<br />
* POST /certificate/sign [sessionToken] (pubkey) -> cert<br />
<br />
<br />
Forgot password<br />
<br />
* POST /password/forgot/send_code (email) -> forgotPasswordToken<br />
* POST /password/forgot/verify_code (forgotPasswordToken, code) -> accountResetToken<br />
* POST /account/reset [authed+encrypted by accountResetToken] (0000,srpV,srpSalt) -> ok<br />
* GOTO "Attach to new device"<br />
<br />
<br />
Change Password<br />
<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /password/change/start [authToken] () -> accountResetToken, keyFetchToken<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
* POST /account/reset [authed+encrypted by accountResetToken] (wrap(kB),srpV,srpSalt) -> ok<br />
* GOTO "Attach to new device"</div>Warnerhttps://wiki.mozilla.org/index.php?title=Identity/AttachedServices/KeyServerProtocol&diff=692243Identity/AttachedServices/KeyServerProtocol2013-08-08T19:15:56Z<p>Warner: /* Crypto Notes */ update HKDF to use RFC5869 terminology: IKM/salt/info instead of SKM/XTS/CTXinfo</p>
<hr />
<div>= PiCL Key Server / IdP Protocol =<br />
<br />
NOTE: This specification is slowly converging on stability (31-Jul-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.<br />
<br />
The server is being developed in https://github.com/mozilla/picl-idp . This repo currently includes a demonstration client (node.js CLI).<br />
<br />
Note that all messages are delivered over an HTTPS connection. The client browser may also implement cert-pinning to improve on the certificate validation process. The protections described below are in addition to those provided by TLS.<br />
<br />
Remaining TODO items:<br />
<br />
* decide on client-side key-stretching parameters: http://keywrapping.appspot.com can help<br />
* finalize SRP questions (definition of M1, generation of a/b)<br />
* finalize proof-of-work/DoS-prevention details<br />
* decide how to rate-limit account-creation calls<br />
* confirm this is actually implementable inside Firefox (especially w.r.t. NSS and Android/Java crypto)<br />
<br />
= Creating The Account =<br />
<br />
The first act performed by a user is to create the account. They enter email+password into their browser, which then does the following steps:<br />
<br />
* decide upon stretching parameters (perhaps consulting the keyserver for recommendations, but imposing a minimum strength requirement). For now we used a fixed {firstPBKDF:20000, scrypt: {N:65536, r:8, p:1}, secondPBKDF:20000}.<br />
* randomly choose a 32-byte mainSalt (this should be unique, but is not secret)<br />
* choose the SRP group parameters (fixed: use the 2048-bit group described below)<br />
* perform key-stretching (as described below), derive stretchedPW and srpPW<br />
* randomly choose a 32-byte srpSalt (unique, but not secret)<br />
* create srpVerifier from srpPW and srpSalt (as described below)<br />
* deliver (email, stretchParams, mainSalt, srpParams, srpSalt) to the keyserver's "POST /account/create" API<br />
<br />
<br />
To limit abuse, the createAccount() should also require a fresh "createToken". This should be created by some other API, outside the scope of this document, that perhaps requires a CAPTCHA or something. createAccount() might also require a proof-of-work token, as described below.<br />
<br />
The server, when creating a new account, creates both kA and wrap(kB) as randomly-generated 256-bit (32-byte) strings. It stores these, along with all the remaining values, indexed by email, in the account table where they can be retrieved by getToken later.<br />
<br />
== Email Verification ==<br />
<br />
To prevent fixation attacks, we require new accounts to verify their configured recovery email address before letting them learn the generated keys or obtain a signed certificate. Nevertheless, we wish clients to forget the user's password while they wait for email verification to complete. To achieve this, clients can obtain a sessionToken before verification, but most APIs that require it will raise errors until verification is finished.<br />
<br />
The server will send email with a URL that contains a long random "verification code" in the "fragment" hash. This URL points to a static page with some javascript that submits the code to the "POST /account/recovery_methods/verify_code" API. The URL can be clicked by any browser (it is not bound to anything), and when the API is hit, the account is marked as verified.<br />
<br />
After the client submits /account/create, it performs the "/session/auth" login sequence below to obtain a sessionToken. It then polls the "GET /account/recovery_methods" (which requires a sessionToken but not account verification) until the user clicks the email link and the API reports verification is complete. Then the client uses "GET /account/keys" and "POST /certificate/sign", described below, to obtain kA, kB, and a signed certificate to talk to the storage server.<br />
<br />
= Login: Obtaining the authToken =<br />
<br />
To connect a browser to an existing account, we use the following login protocol to transform an email+password pair into a single-use authToken. We will use this in the next section to create a session, from which we can obtain encryption keys and signed certificates.<br />
<br />
This protocol starts by using key-stretching to transform the email+password into a "stretchedPW", then feeds this into an SRP protocol to get the authToken.<br />
<br />
[[File:PICL-IdPAuth-auth-start.png|IdP Auth Protocol]]<br />
<br />
This authToken can be used (once) to do one of the following:<br />
<br />
* /session/create: obtain a sessionToken (and keyFetchToken), which enables storage server access<br />
* /password/change: obtain an accountResetToken, to safely reset the account password<br />
* /account/delete: to delete the entire account<br />
<br />
The protocol is designed to enable parallelism between key-stretching and the initial network messages, to reduce the time it takes to connect a browser to the account. In total, the browser requires five messages in four roundtrips (1: /auth/start, 2: /auth/finish, 3: /session/create, 4: /account/keys and /certificate/sign in parallel) before it is ready to talk to the storage server.<br />
<br />
== auth/start ==<br />
<br />
As soon as the user finishes typing in the email address, the client should send it in the "/auth/start" 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 loginSRPToken that is used to associate this request with the subsequent /auth/finish 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.<br />
<br />
To mitigate DoS abuse, /auth/start may also require a proof-of-work string, described below.<br />
<br />
== Client-Side Key Stretching ==<br />
<br />
"Key Stretching" is the practice of running a password through a computationally-expensive one-way function before using it for encryption or authentication. The goal is to make brute-force dictionary attacks more expensive, by raising the cost of testing each guess.<br />
<br />
To protect the user's class-B data against compromises of our keyserver, we perform this key stretching on the client, rather than on the server. We use the memory-hard "scrypt" function (pronounced "ess-crypt") for this purpose, as motivated by the attacker-cost studies in [[Identity/CryptoIdeas/01-PBKDF-scrypt]]. Slower (low-RAM) devices can still participate by outsourcing the scrypt step to an "scrypt helper". To provide at least minimal protection against a compromise of this helper, we sandwich the scrypt step between two PBKDF2 steps. The complete protocol looks like this:<br />
<br />
[[File:PICL-IdPAuth-stretch-KDF.png|Stretching KDF]]<br />
<br />
Our initial parameter choices for this stretching are to use 20000 iterations of PBKDF2 on each side (40k in all), and to run scrypt with N/r/p set to 64k/8/1. This requires roughly 60MB of RAM for about 1.3 seconds on a 1.8GHz Intel Atom linux box (it will run faster, but use the same memory, on more modern CPUs). We are still studying performance on various platforms to decide what parameters to use in V1: lowering them speeds up the login process, but reduces security (by reducing the cost of an dictionary attack).<br />
<br />
Since the stretching is expected to take a second or two, the client can optimistically start this process (using default parameters) before receiving the auth/start 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 "mainSalt is added *after* the stretching, to enable this parallelism (at a tiny cost in security).<br />
<br />
After "stretchedPW" is derived, a second HKDF call is used to derive "srpPW" and "unwrapBKey" which will be used later.<br />
<br />
[[File:PICL-IdPAuth-main-KDF.png|masterKey KDF]]<br />
<br />
== SRP Protocol Details ==<br />
<br />
The PiCL client uses the SRP protocol (http://srp.stanford.edu/) to prove that it knows the (stretched) account password without revealing the actual password (or information enabling a brute-force attack) to the server or any eavesdroppers. <br />
<br />
SRP is somewhat underspecified. We use SRP-6a, with SHA256 as the hash, and the 2048-bit modulus defined in RFC 5053 Appendix A. We consistently zero-pad all string values to 256 bytes (2048 bits), and use H(A+B+S) as the key-confirmation message "M1". These details, plus the SRP design papers and RFCs 2945 and 5054, should be enough to build a compatible implementation. The diagrams below and the test vectors at the end of this page can be used to verify compatibility.<br />
<br />
The server should use Jed's SRP module from https://github.com/jedp/node-srp . The client might use SJCL (http://crypto.stanford.edu/sjcl/) or native code (NSS).<br />
<br />
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).<br />
<br />
Note that SRP-6a uses a "k" value which basically encodes the group being used ("N" and "g"). Since all PICL accounts use the same 2048-bit group, they will all use the same "k" value (not to be confused with the per-session shared-secret "K" key that emerges from the protocol). This group's "k" integer is (as a base-10 number):<br />
259003859907<br />
09503006915442163037721228467470<br />
35652616593381637186118123578112<br />
<br />
=== SRP Verifier Calculation ===<br />
<br />
When the client first creates the account, it must combine the account email address, the stretched password (srpPW), and a randomly-generated srpSalt, to compute the srpVerifier. The server will use this verifier later, to check whether or not the client really knows the password.<br />
<br />
If the server is compromised and an attacker learns the srpVerifier for a given account, they cannot use this to directly log in (the verifier is not "password-equivalent"), but it does allow them to perform an offline brute-force attack against the user's password. In this respect, it is similar to a traditional hashed password. We make these attacks somewhat more expensive by performing the client-side stretching described above, instead of using the raw user password in the SRP calculation.<br />
<br />
[[File:PICL-IdPAuth-SRP-Verifier.png|client-side SRP Verifier calculation]]<br />
<br />
Test vectors are provided at the end of this page. The protocol requires that several values ("a", "b", "srpSalt") are chosen randomly, but for illustrative purposes, the test vectors use carefully-crafted non-random values. For the user's sake, please ensure implementations use proper random values instead of simply copying the test vectors.<br />
<br />
The client sends srpSalt and srpVerifier to the server when it creates the account. It will also re-compute the 'x' value (as an integer) during sign-in. The server will convert the srpVerifier string back into an integer ('v') for use during its own sign-in calculations.<br />
<br />
=== SRP Server-side Sign-In Flow ===<br />
<br />
When the user connects a new device to their account, they use the /auth/start API to start the SRP protocol. This sends the account email address to the server. The server looks up the stored srpVerifier for this account, creates a random 'b' integer, performs some math to compute the "B" number, then converts B into a string known as "srpB". "srpB" is returned to the client, along with srpSalt and the key-stretching parameters. "b" and "srpB" are retained for the subsequent /auth/finish call. '''Note that it is critical that the "b" integer remain secret on the server.''' The server also allocates a random loginSRPToken to connect the two calls.<br />
<br />
[[File:PICL-IdPAuth-SRP-Server.png|server-side SRP]]<br />
<br />
Later, /auth/start will be called with the client's srpA string and its M1 key-confirmation string. srpA is combined with srpB to calculate the "u" integer. srpA is also turned into an integer and used to compute the shared-secret "S" integer. S is then used to compute the shared key "srpK", which is the output of the SRP process.<br />
<br />
=== SRP Client Calculation ===<br />
<br />
While the client is waiting for the response to /auth/start, it begins its key-stretching calculations. Everything else must wait until the response to /auth/start arrives, which includes the key-stretching parameters (which are retroactively confirmed), srpSalt, and the server's generated srpB value.<br />
<br />
[[File:PICL-IdPAuth-SRP-Client.png|client-side SRP]]<br />
<br />
Once the client knows srpSalt, it computes the same "x" integer as it did in the middle of the srpVerifier calculation. It also converts srpB into an integer named "B". Then it creates a random "a" integer, uses it to compute the string "srpA", then combines srpA with the server's srpB to compute the "u" integer. It then combines the static "k", the password-derived "x", the combined "u", and the server's "B", together with some magic math, to derive the "S" integer. If everything went well, the client will compute the same "S" value as the server did. If not (the password was wrong, or the client is talking to a fake server that doesn't really know srpVerifier), then the two "S" values will not match.<br />
<br />
('''Again, it is critical that the client keep its "a" and "x" integers secret, both during and after the protocol run.''')<br />
<br />
To safely tell if the "S" values match, both client and server combine srpA, srpB, and their (independently) generated "S" strings to form a string named "M1". The client sends M1 (along with srpA) in the /session/auth/finish message. The server compares the client's copy of M1 against its own. If they match, the client knew the password and the server can safely respond with the encrypted account data. If they do not match, the client (or a man-in-the-middle attacker) did not know the password, and the client should increment a counter that can trigger defenses against online guessing attacks. The server must then return an error to the client, and '''not''' use or reveal srpK (or the correct M1) in any way.<br />
<br />
Both client and server also hash "S" into "srpK". This is the shared session key, from which specific message encryption and MAC keys are derived (as described below).<br />
<br />
=== SRP Notes ===<br />
<br />
The SRP "g" (generator) and "N" (prime modulus) should use the 2048-bit value from RFC 5054 Appendix A. 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.<br />
<br />
There are several places in SRP where integers (usually in the range 1..N-1) are converted into bytestrings, either for transmission over a wire, or to be passed into a hash function. The SRP spec is somewhat ambiguous about padding here: if the integer happens to be less than 2^2040, the simplest toString() approach will yield a '''255''' byte string, not a 256 byte string. PiCL consistently uses padding, so compatible implementations must prepend one or more NUL bytes to these short strings before transmission or hashing. The examples above were brute-forced to ensure that "srpVerifier", "srpA", "srpB", and "S" all wind up with leading zeros, to exercise the padding code in compatible implementations. If you are having problems getting your code to match these results, add some assertions to test that the stringified integers being put into hashes are exactly 256 bytes long.<br />
<br />
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 (/auth/finish).<br />
<br />
The server receives "A" in /auth/finish, 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 keyFetchToken+sessionToken as described below, returning the encrypted/MACed bundle in the response to /auth/finish.<br />
<br />
Outstanding crypto questions:<br />
<br />
* How exactly should the "a" and "b" integers be generated? The issue is of how much bias SRP can tolerate. Ideally these integers are uniformly distributed from 1 to N-1 (inclusive). The only way to obtain a purely uniform distribution from a source of random bytes is try-try-again: pick a (integral-number-of-bytes) number, compare it to the desired range, try again if it falls outside the range. If you get unlucky, this can take a lot of guesses, depending upon how close the range is to a power of two. ECDSA implementations tend to pick a number twice as long as the modulus and then modulo it down (rand(2^4096) % N), which yields a tiny fraction of a bit of bias. The cheaper approach is to do the same with a number of equal length (rand(2^2048) % N), which imposes more bias. Some SRP implementations appear to be satisfied by rand(2^256). We need more review here.<br />
* The original SRP papers defined M1=H(A+B+S) as we use here, but other implementation (in particular RFC2945) uses a curious construct that includes the username, the salt, and an odd XOR combination of N and g. We need to decide what to use for M1.<br />
<br />
== /auth/finish ==<br />
<br />
The client-side SRP calculation results in two values that are sent to the server in the /auth/finish 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.<br />
<br />
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.<br />
<br />
The server then allocates a single-use 32-byte random token named "authToken". It encrypts the token with the session key, and returns a success message with the encrypted bundle.<br />
<br />
All tokens have an associated tokenID, described below. The server needs to maintain a table that maps the tokenID to the token itself, so it can derive other values from the token later. The tokens are also associated with a specific account, so later API requests do not specify an email address or account ID.<br />
<br />
== Decrypting the /auth/finish Response ==<br />
<br />
The SRP session key ("srpK") is used to derive two other keys: respHMACkey and respXORkey.<br />
<br />
[[File:PICL-IdPAuth-encrypt-authToken.png|Decrypting the authToken]]<br />
<br />
The respXORkey is used to encrypt the authToken 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.<br />
<br />
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 returns the authToken value.<br />
<br />
= Creating a Session =<br />
<br />
For login, the single-use authToken is spent on a call to /session/create .<br />
This allocates two new (random 32-byte) tokens: a long-lived "sessionToken", and a single-use "keyFetchToken". The /session/create call returns an encrypted bundle containing the two tokens.<br />
<br />
[[File:PICL-IdPAuth-encrypt-sessionToken.png|Decrypting the sessionToken and keyFetchToken]]<br />
<br />
For calls which accept an authToken, the client uses authToken to derive three values:<br />
<br />
* tokenID<br />
* reqHMACkey<br />
* requestKey<br />
<br />
<br />
The client uses tokenID and reqHMACkey for a HAWK (https://github.com/hueniverse/hawk/) request to the "POST /session/create" API, using tokenID as "credentials.id" and reqHMACkey as "credentials.key". The server uses tokenID to look up the corresponding token, then derives reqHMACkey to validate the request.<br />
<br />
Each authToken-using call then derives additional API-specific values from requestKey. /session/create uses two derived values:<br />
<br />
* respHMACkey<br />
* respXORkey<br />
<br />
<br />
When the server receives a valid /session/create request, it allocates sessionToken and keyFetchToken, concatenates them, encrypts the pair by XORing it with the derived respXORkey, and attaches a MAC generated with respHMACkey. The encrypted MACed bundle is returned to the client.<br />
<br />
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 keyFetchToken and sessionToken values.<br />
<br />
Each authToken is single-use: once a successful request has been made with it, the authToken and its corresponding ID is removed from the server's memory, and subsequent attempts to use it will return a "no such token" error. The token is consumed even if the request fails (e.g. the MAC did not match).<br />
<br />
The authToken can be used (once) by multiple APIs. The server only needs to maintain one table mapping tokenID to authToken, because all these APIs share the same method of deriving a tokenID and reqHMACkey from the authToken.<br />
<br />
When a HAWK request appears, it should look up the tokenID in this table, retrieve the authToken, recompute reqHMACkey and requestKey, validate the request, delete the table entry, then hand the HTTP request and requestKey to the specific API handler. That handler should derive the API-specific values (respHMACkey, respXORkey, etc) to process the request or construct the response.<br />
<br />
The server can support multiple sessions per account (typically one per client device, plus perhaps others for account-management portals). There can also be multiple outstanding keyFetchTokens. The sessionToken lasts forever (until revoked by a password change or explicit revocation command), and can be used an unlimited number of times. The keyFetchToken expires after 60 seconds, and is single-use.<br />
<br />
= Obtaining keys kA and kB =<br />
<br />
Clients which have exchanged an authToken for either a sessionToken or an accountResetToken will also receive a keyFetchToken. This single-use token allows the client to retrieve kA and wrap(kB), which enables the client to encrypt and decrypt browser data (bookmarks, open-tabs, etc) correctly. As above, the keyFetchToken is used to derive tokenID, reqHMACkey, respHMACkey, and respXORkey, which are used in a HAWK request to the "GET /account/keys" API.<br />
<br />
The server pulls kA and wrap(kB) from the account table, concatenates them, encrypts the pair by XORing it with the derived respXORkey, and attaches a MAC generated with respHMACkey.<br />
<br />
[[File:PICL-IdPAuth-keys-server.png|keyFetchToken: server encrypts keys]]<br />
<br />
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 and wrap(kB) values.<br />
<br />
[[File:PICL-IdPAuth-keys-client.png|keyFetchToken: client decrypts keys]]<br />
<br />
Finally, the server-provided wrap(kB) value is simply XORed with the password-derived unwrapBKey (both are 32-byte strings) to obtain kB. There is no MAC on wrap(kB).<br />
<br />
[[File:PICL-IdPAuth-key-unwrap.png|unwrapping kB]]<br />
<br />
"kA" and "kB" enable the browser to encrypt/decrypt synchronized data records. They will be used to derive separate encryption and HMAC keys for each data collection (bookmarks, form-fill data, saved-password, open-tabs, etc). This will allow the user to share some data, but not everything, with a third party. The client may intentionally forget kA and kB (only retaining the derived keys) to reduce the power available to someone who steals their device.<br />
<br />
Note that /account/keys will not succeed until the account's email address has been verified. Also note that each keyFetchToken is single-use and short-lived. The token is consumed even if the request fails (e.g. the MAC does not match).<br />
<br />
= Signing Certificates =<br />
<br />
The sessionToken is used to derive two values:<br />
<br />
* tokenID<br />
* request HMAC key<br />
<br />
<br />
[[File:PICL-IdPAuth-use-session.png|Using the sessionToken, signing certificates]]<br />
<br />
The requestHMACkey is used in a HAWK request to provide integrity over many APIs, including /certificate/sign. requestHMACkey 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.<br />
<br />
For /certificate/sign, it is critical to enable payload verification by setting options.payload=true (on both client and server). Otherwise a man-in-the-middle could submit their own public key, get it signed, and then delete the user's data on the storage servers.<br />
<br />
The following keyserver APIs require a HAWK-protected request that uses the sessionToken. In addition, some require that the account be in the "verified" state:<br />
<br />
* GET /account/devices<br />
* POST /session/destroy<br />
* GET /recovery_email/status<br />
* POST /recovery_email/resend_code<br />
* POST /certificate/sign (requires "verified" account)<br />
<br />
= Resetting The Account =<br />
<br />
The account may be reset in two circumstances: when the user changes their password, and when the user forgets their password. In both cases, the client first obtains an "accountResetToken". This token is then used to change the SRP Verifier and either reset or replace the wrap(kB) value.<br />
<br />
== Changing the Password ==<br />
<br />
When the user wishes to change their password (i.e. they still know the old password), they first use the /auth/start+/auth/finish SRP protocol safely obtain an "authToken". They they use the "/password/change/start" API to exchange the authToken for an accountResetToken and a keyFetchToken.<br />
<br />
[[File:PICL-IdPAuth-encrypt-passwordChange.png|Server encrypts passwordChange response]]<br />
<br />
The accountResetToken will be used below to set the new password safely. The keyFetchToken should be used first, to obtain kB, so the subsequent account reset can replace wrap(kB) with a new value. This allows the password-changing client to retain their class-B data.<br />
<br />
Requiring an authToken proves that the user has provided the correct account password recently. When the account is reset, all active sessions and tokens will be cancelled (disconnecting all devices from the account). The client should immediately establish a new session as described above.<br />
<br />
This API is only used when the user knows their old password: if they have forgotten the password, use the "/password/forgot" APIs below.<br />
<br />
== Handling a Forgotten Password ==<br />
<br />
When the user has forgotten their password, they can use one of their "recovery methods" to obtain an accountResetToken. For now, this means we send a random code to the email address associated with their account. The user must copy this code from the email into their client, whereupon the client will get an accountResetToken that can be delivered to the API below.<br />
<br />
Note that, since the forgotten-password client never learns kB, any class-B data will be lost. This is necessary to protect class-B data from attackers who can read the users's email but do not know the account password (including those who compromise the IdP and the keyserver itself). When using /account/reset below, they will set wrap(kB) to a string of all zeros, which means the server should generate a new random wrap(kB) (just as it does during account creation).<br />
<br />
The /password/forgot/send_code API is used to ask the server to send a recovery code. This takes a recovery method, which for now is just an email address. This API is unauthenticated (after all, the user who has forgotten their password knows nothing but their email address). The server marks the corresponding account as "pending recovery", allocates a random forgotPasswordToken for the account, creates a recovery code, and sends the code (with instructions) via email. The API returns forgotPasswordToken to the client.<br />
<br />
The user must copy the recovery code into the same browser where they started the process. The client then submits the code to /password/forgot/verify_code along with the forgotPasswordToken they received. If they match, the server allocates a accountResetToken and returns it to the client. If they do not match, the server increments a counter (which is used to decide if an online guessing attack is happening).<br />
<br />
forgotPasswordToken can be used three times before it is exhausted. If the user guesses incorrectly this often, the client must call send_code again to get a new token and code. Each account has at most one token+code active at a time.<br />
<br />
The recovery code is initially a random 8-digit decimal number. If an attacker tries to sign in as someone else, hits the "forgot my password" button, then submits a guess to /password/forgot/verify_code, they will have a 1-in-100-million chance of success. If the server detects too many wrong guesses, it should increase the length of new codes. Another defensive technique is to require that users click an email link before being given the code: the server is told when the link is clicked, so the code will not be enabled until the email has been read. It remains to be seen whether this will be sufficient.<br />
<br />
The exact thresholds are TBD, but a nominal goal is to keep the chances of any attack succeeding to below 1-in-a-million per year. To achieve this, we can tolerate 100 verify_code failures in a single year before we must increase the length of the code.<br />
<br />
== Using accountResetToken ==<br />
<br />
An accountResetToken, obtained through either of the methods above, is used to invoke the /account/reset API. If the request is accepted, the server replaces the account password with a new one, updates the wrap(kB) value, cancels all active sessions and tokens (disconnecting all devices from the account), and sends a "your password has been changed" email to the user.<br />
<br />
If the client knew the old password, it can supply a new wrap(kB) value that will yield the same kB key as before, so no data will be lost. If the client did *not* know the old password, then it will supply a wrap(kB) of all-zeros, which tells the server to generate a new random wrap(kB) (just like during account creation), which means the class-B data will be lost.<br />
<br />
The client puts their new password through the same stretching procedure as described in the new-account section above, resulting in a new srpVerifier and unwrapBKey. If they knew the old kB, they XOR it with the new unwrapBKey to obtain a new wrap(kB).<br />
<br />
/account/reset needs request confidentiality, since the arguments include the newly wrapped kB value and the new SRP verifier, both of which enable a brute-force attack against the password. HAWK provides request integrity. The response is a single "ok" or "fail", conveyed by the HTTP headers, so we do not require response confidentiality, and can live without response integrity.<br />
<br />
So the single-use resetToken is used to derive three values:<br />
<br />
* tokenID<br />
* request HMAC key<br />
* request XOR key<br />
<br />
<br />
[[File:PICL-IdPAuth-encrypt-resetAccount.png|Client encrypts resetAccount request]]<br />
<br />
The request data will contain wrap(kB) and the new SRP verifier, concatenated together. Since we always pad the SRP verifier to the full (256-byte) group length, both pieces are fixed-length. We generate enough reqXORkey bytes to cover both values.<br />
<br />
The request data is XORed with requestXORkey, then delivered in the body of a HAWK request that uses tokenID as credentials.id and requestHMACkey as credentials.key . Note: it is critical to include the request body in the HAWK integrity check (options.payload=true, on both client and server), otherwise a man-in-the-middle could substitute their own SRP verifier, giving them control over the account (access to the user's class-A data, and a brute-force attack on their password).<br />
<br />
The client submits other values in the same request:<br />
<br />
* stretchParams<br />
* mainKDFSalt<br />
* srpSalt<br />
<br />
<br />
These values do not require confidentiality, so are not included in the encrypted bundle. They are still protected by the HAWK integrity check. Note that the server should assert that both salts are different than the previously stored values.<br />
<br />
After using /account/reset, clients should immediately perform the login protocol from above. If the old password was forgotten, this is necessary to fetch kA. In either case, a new sessionToken is required, since old sessions and tokens are revoked by /account/reset. Clients should retain the new srpPassword value during this process to avoid needing to run the lengthy key-stretching routine a second time.<br />
<br />
= Deleting The Account =<br />
<br />
When the user wishes to completely delete their account, the browser needs to perform two actions:<br />
<br />
* contact the storage servers and delete all records and collections<br />
* contact the keyserver and delete the account information<br />
<br />
The user should be prompted for their password as confirmation (i.e. a browser in the normal attached-and-synchronizing state should not be able to erase the account information: it must acquire a new authToken first).<br />
<br />
The device then obtains an authToken as described above, then spends it on a HAWK-protected request to the /account/destroy endpoint. This request contains no body and returns only a success code.<br />
<br />
[[File:PICL-IdPAuth-deleteAccount.png|Deleting the Account]]<br />
<br />
= Crypto Notes =<br />
<br />
Strong entropy is needed in the following places:<br />
<br />
* (client) creation of private "a" value inside SRP<br />
* (server) initial creation of kA and wrap(kB)<br />
* (server) creation of private "B" value inside SRP<br />
* (server) creation of signToken and resetToken<br />
<br />
<br />
On the server, code should get entropy from /dev/urandom via a function that uses it, like "crypto.randomBytes()" in node.js or "os.urandom()" in python. On the client, code should combine local entropy with some fetched from the keyserver via getEntropy(), to guard against failures in the local entropy pool. Something like HKDF(IKM=localEntropy+remoteEntropy, salt="", info=KW("mergeEntropy")).<br />
<br />
An HKDF-based stream cipher is used to protect the response for getToken2(), and the request for resetAccount(). HKDF is used to create a number of random bytes equal to the length of the message, then these are XORed with the plaintext to produce the ciphertext. An HMAC is then computed from the ciphertext, to protect the integrity of the message.<br />
<br />
HKDF, like any KDF, is defined to produce output that is indistinguishable from random data ("The HKDF Scheme", http://eprint.iacr.org/2010/264.pdf , by Hugo Krawczyk, section 3). XORing a plaintext with a random keystream to produce ciphertext is a simple and secure approach to data encryption, epitomized by AES-CTR or a stream cipher (http://cr.yp.to/snuffle/design.pdf). HKDF is not the fastest way to generate such a keystream, but it is safe, easy to specify, and easy to implement (just HMAC and XOR).<br />
<br />
Each keystream must be unique. SRP is defined to produce a random session key for each run (as long as at least one of the sides provides a random ephemeral key). We define resetToken to be a single-use randomly-generated value. Hence our two HKDF-XOR keystreams will be unique.<br />
<br />
A slightly more-traditional alternative would be to use AES-CTR (with the same HMAC-SHA256 used here), with a randomly-generated IV. This is equally secure, but requires implementors to obtain an AES library (with CTR mode, which does not seem to be universal). An even more traditional technique would be AES-CBC, which introduces the need for padding and a way to specify the length of the plaintext. The additional specification complexity, plus the library load, leads me to prefer HKDF+XOR.<br />
<br />
kB is equal to the XOR of wrapKey (which is a deterministic function of the user's email address, password, mainSalt, and the stretching parameters) and the server's randomly-generated wrap(kB) value, making kB a random value too. Using XOR as a wrapping function allows us to avoid sending kB or wrap(kB) in the initial createAccount arguments, which are not protected by SRP, and thus would enable an eavesdropper to mount a dictionary attack on the password (using wrap(kB) as their oracle).<br />
<br />
Likewise, allowing the server to generate kA avoids exposure during createAccount. The only point of vulnerability is a forgotten-password resetAccount() message, if an eavesdropper can learn the resetToken. In this case, the attacker might either use the resetToken themselves (then use signToken to learn kA and wrap(kB)), or passively decrypt the resetAccount() arguments to retrieve just wrap(kB). Given wrap(kB) and some encrypted browser data, the attacker can guess passwords (and derive kB, etc) until the data decrypts properly.<br />
<br />
To make this technique safe, any time kB or the password is changed, the mainSalt should be changed too. Otherwise knowledge of both wrap(old-kB) and old-kB would reveal wrapKey, making it easy to deduce the new kB. Changing mainSalt causes wrapKey to change too, preventing this.<br />
<br />
mainSalt is incorporated at the end of the stretching process to allow it to proceed in parallel with the getToken1() call that retrieves the salt. The inputs to the lengthy stretch come entirely from the user (email and password) or are optimistically (stretching parameters). This speedup seems more important than the minor security benefit of including the salt at the beginning of the stretch.<br />
<br />
There is no MAC on wrap(kB). If the keyserver chooses to deliver a bogus wrap(kB) or kA, the client will discover the problem a moment later when it talks to a storage server and attempts to retrieve data from an unrecognized collection-ID (since we intend to derive collection-IDs from the key used to encrypt their data, which will be derived from kA or kB as appropriate). It might be useful to add a checksum to kA and wrap(kB) to detect accidental corruption (e.g. store and deliver kA+SHA256(kA)), but this doesn't protect against intentional changes. We omit this checksum for now, assuming that disks will be reliable enough to let us never experience such failures.<br />
<br />
We use scrypt without a per-user salt. This is safe because the "password" input to scrypt is already diversified by the user's email address. The intention is to allow the scrypt-helper to run on anonymous data, so that an attacker who compromises this helper cannot easily learn the email addresses of the partially-stretched K1 values that it is receiving, confounding their attack.<br />
<br />
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. <br />
For /certificate/sign, we do not need request confidentiality or response confidentiality, since the client's pubkey and the resulting certificate will both be exposed over a similar SSL connection to the storage server later. And it is sufficient to rely on the response integrity provided by SSL, since the client can verify the returned certificate for itself. For the other keyserver APIs protected by HAWK, these properties are either unnecessary, or are provided by additional mechanisms.<br />
<br />
= Proof-Of-Work =<br />
<br />
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(). The server can control how much work is required.<br />
<br />
The getToken1() call looks for a "X-PiCL-PoW:" HTTP header. Most of the time, clients don't supply this header. But if the server responds to the getToken1() call with an error that indicates PoW is required, clients must create a valid PoW string and include it as the value of an "X-PiCL-PoW:" header in their next call to getToken1().<br />
<br />
The server's error message includes two parameters. The first is a "prefix string": the client's PoW string is required to begin with this prefix. The second is a "threshold hash". SHA256(PoWString) is required to be lexicographically earlier than the thresholdHashString (i.e. the numerical value of its hash must be closer to zero than the threshold). The client is expected to concatenate the prefix with a counter, then repeatedly increment the counter and hash the result until they meet the threshold, then re-submit their getToken1() request with the combined prefix+counter string in the header. If the client has spent more than e.g. 10 seconds doing this, the client should probably help the user cancel the operation and try again.<br />
<br />
When a server is under a DoS attack (either via some manual configuration tool or sensed automatically), it should start requiring valid unique X-PiCL-PoW headers. The server should initially require very little work, by using a threshold hash with just a few leading zero bits. If this is insufficient to reduce the attack volume, the threshold should be lowered, requiring even more work (from both the attacker and legitimate clients).<br />
<br />
The server should create a prefix string that contains a parseable timestamp and a random nonce (e.g. "%d-%d-" % (int(time.time()), b32encode(os.urandom(8)))). The server should also decide on a cutoff time (perhaps ten minutes ago). Each server must then maintain a table of "old PoW strings" to prevent replay attacks (these do not need to be shared among all servers: an in-RAM cache is fine).<br />
<br />
When the server receives a proposed PoW string, it first splits off the leading timestamp, and if the timestamp is older than the cutoff time, it rejects the string (either by dropping the connection, or returning a new "PoW required" error if it's feeling nice). Then it hashes the whole string and compares it against the threshold, rejecting those which fail to meet the threshold. Finally, for strings that pass the hash threshold, it checks the "old strings" table, and rejects any that appear on that list.<br />
<br />
If the PoW string makes it past all these checks, the server should add the string to the "old strings" table, then accept the request (i.e. compute an srpB value and add a session-id table entry for the request).<br />
<br />
The old-strings table check should be optimized to reject present strings quickly (i.e. if we are under attack, we should expect to see lots of duplicates of the same string, and must minimize the work we do when this occurs).<br />
<br />
The server can remove values from the old-strings table that have timestamps older than the cutoff time. The server can also discard values at other times (to avoid consuming too much memory), without losing anything but protection against resource consumption.<br />
<br />
Other notes:<br />
<br />
The server-side code for this can be deferred until we care to have a response to a DoS attack. However the client-side code for this must be present from day one, otherwise we won't be able to turn on the defense later without fear of disabling legitimate old clients.<br />
<br />
The server should perform as little work as possible before rejecting a token. Every extra CPU cycle it spends in this path is increasing the DoS attack amplification factor.<br />
<br />
The nonce in the prefix string exists to make sure that two successive clients get different prefixes, and thus do not come up with the same counter value (and inadvertently create identical strings, looking like a replay attack). If this proved annoying or expensive, we could instead obligate clients to produce their own nonce.<br />
<br />
TBD: Is this worth it? Should the PoW string go into an HTTP header? (I want it to be cheap to extract, and not clutter logs). Should the error response be a distinctive HTTP error code so our monitoring tools can easily count them? We can also use this feature to slow down online guessing attacks (i.e. trigger it either when getToken1 is called too much or when getToken2 produces too many errors). Since getToken1() includes an email address, we could also requires PoWs for some addresses (e.g. those we know to be under attack) but not others.<br />
<br />
= Glossary =<br />
<br />
This defines some of the jargon we've developed for this protocol.<br />
<br />
* data classes: each type of browser data (bookmarks, passwords, history, etc) can be assigned, by the user, to either class-A or class-B<br />
* class-A: data assigned to this class can be recovered, even if the user forgets their password, by proving control over an email address and resetting the account. It can also be read by Mozilla (since it runs the keyserver and knows kA), or by the user's IdP (by resetting the account without the user's permission).<br />
* class-B: data in this class cannot be recovered if the password is forgotten. It cannot be read by the IdP. Mozilla (via the keyserver) cannot read this data, but can attempt a brute-force dictionary attack against the password.<br />
* kA: the master key for data stored as "class-A", a 32-byte binary string. Individual encryption keys for different datatypes are derived from kA.<br />
* kB: the master key for data stored as "class-B", a 32-byte binary string.<br />
* wrap(kB): an encrypted copy of kB. The keyserver stores wrap(kB) and never sees kB itself. The client (browser) uses a key derived from the user's password to decrypt wrap(kB), obtaining the real kB.<br />
* sessionToken: a long-lived per-device token which allows the device to obtained signed BrowserID certificates for the account's identity (GUID@picl-something.org). This token remains valid until the user revokes it (either by changing their password, or triggering some kind of "revoke a specific device" or "revoke all devices" function).<br />
<br />
= Test Vectors =<br />
<br />
The following example uses a non-ASCII email address of "andré@example.org" (with an accented "e", UTF8 encoding is 616e6472c3a9406578616d706c652e6f7267) and a non-ascii password of "pässwörd" (with accents on "a" and "o", UTF8 encoding is 70c3a4737377c3b67264).<br />
<br />
These test vectors were produced by the python code in https://github.com/warner/picl-spec-crypto . The diagrams may lag behind the latest version of that code.<br />
<br />
== stretch-KDF ==<br />
<br />
email:<br />
616e6472c3a94065<br />
78616d706c652e6f<br />
7267<br />
<br />
password:<br />
70c3a4737377c3b6<br />
7264<br />
<br />
K1 (scrypt input):<br />
f84913e3d8e6d624<br />
689d0a3e9678ac8d<br />
cc79d2c2f3d96414<br />
88cd9d6ef6cd83dd<br />
<br />
K2 (scrypt output):<br />
5b82f146a6412692<br />
3e4167a0350bb181<br />
feba61f63cb17140<br />
12b19cb0be0119c5<br />
<br />
stretchedPW:<br />
c16d46c31bee242c<br />
b31f916e9e38d60b<br />
76431d3f5304549c<br />
c75ae4bc20c7108c<br />
<br />
== main-KDF ==<br />
<br />
mainSalt (normally random):<br />
00f0000000000000<br />
0000000000000000<br />
0000000000000000<br />
000000000000034d<br />
<br />
srpPW:<br />
00f9b71800ab5337<br />
d51177d8fbc682a3<br />
653fa6dae5b87628<br />
eeec43a18af59a9d<br />
<br />
unwrapBKey:<br />
6ea660be9c89ec35<br />
5397f89afb282ea0<br />
bf21095760c8c500<br />
9bbcc894155bbe2a<br />
<br />
internal x (base 10):<br />
8192518690918<br />
99580124814080709381476194749939<br />
03899664126296984459627523279550<br />
<br />
internal x (hex):<br />
b5200337cc3f3f92<br />
6cdddae0b2d31029<br />
c069936a844aff58<br />
779a545be89d0abe<br />
<br />
v (verifier as number) (base 10):<br />
114649<br />
57230405843056840989945621595830<br />
71784395917725741221739574165799<br />
54316134303691657140298181419198<br />
87853709633756255809680435884948<br />
69849281177012209169281795507853<br />
57610332070005048463659745521969<br />
83218225819721112680718485091921<br />
64608360806562626442477160609654<br />
43167308814558974899899506977051<br />
96721477608178869100211706638584<br />
53875100985456239693728258285562<br />
04889672594983678412848291529879<br />
88548996842770025110751388952323<br />
22170663943486107183421205517476<br />
84831590615660554713667726412525<br />
73641352721966728239512914666806<br />
49625530438034148797508015907639<br />
67594925530663571631035463732161<br />
30193328802116982288883318596822<br />
<br />
== SRP Verifier ==<br />
<br />
k (base 10):<br />
259003859907<br />
09503006915442163037721228467470<br />
35652616593381637186118123578112<br />
<br />
srpSalt (normally random):<br />
00f1000000000000<br />
0000000000000000<br />
0000000000000000<br />
0000000000000179<br />
<br />
srpVerifier:<br />
00173ffa0263e63c cfd6791b8ee2a40f<br />
048ec94cd95aa8a3 125726f9805e0c82<br />
83c658dc0b607fbb 25db68e68e93f265<br />
8483049c68af7e82 14c49fde2712a775<br />
b63e545160d64b00 189a86708c69657d<br />
a7a1678eda0cd79f 86b8560ebdb1ffc2<br />
21db360eab901d64 3a75bf1205070a57<br />
91230ae56466b8c3 c1eb656e19b794f1<br />
ea0d2a077b3a7553 50208ea0118fec8c<br />
4b2ec344a05c66ae 1449b32609ca7189<br />
451c259d65bd15b3 4d8729afdb5faff8<br />
af1f3437bbdc0c3d 0b069a8ab2a959c9<br />
0c5a43d42082c774 90f3afcc10ef5648<br />
625c0605cdaace6c 6fdc9e9a7e6635d6<br />
19f50af773452247 0502cab26a52a198<br />
f5b00a2798589165 07b0b4e9ef9524d6<br />
<br />
== SRP B ==<br />
<br />
private b (normally random) (base 10):<br />
1198277<br />
66042000957856349411550091527197<br />
08125226960544768993643095227800<br />
29105361555030352774505625606029<br />
71778328003253459733139844872578<br />
33596964143417216389157582554932<br />
02841499373672188315534280693274<br />
23189198736863575142046053414928<br />
39548731879043135718309539706489<br />
29157321423483527294767988835942<br />
53343430842313006332606344714480<br />
99439808861069316482621424231409<br />
08830704769167700098392968117727<br />
43420990997238759832829219109897<br />
32876428831985487823417312772399<br />
92628295469389578458363237146486<br />
38545526799188280210660508721582<br />
00403102624831815596140094933216<br />
29832845626116777080504444704039<br />
04739431335617585333671378812943<br />
<br />
private b (hex):<br />
00f3000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 000000000000000f<br />
<br />
transmitted srpB:<br />
0022ce5a7b9d8127 7172caa20b0f1efb<br />
4643b3becc535664 73959b07b790d3c3<br />
f08650d5531c19ad 30ebb67bdb481d1d<br />
9cf61bf272f84398 48fdda58a4e6abc5<br />
abb2ac496da5098d 5cbf90e29b4b110e<br />
4e2c033c70af7392 5fa37457ee13ea3e<br />
8fde4ab516dff1c2 ae8e57a6b264fb9d<br />
b637eeeae9b5e43d faba9b329d3b8770<br />
ce89888709e02627 0e474eef822436e6<br />
397562f284778673 a1a7bc12b6883d1c<br />
21fbc27ffb3dbeb8 5efda279a69a1941<br />
4969113f10451603 065f0a0126666456<br />
51dde44a52f4d8de 113e2131321df1bf<br />
4369d2585364f9e5 36c39a4dce33221b<br />
e57d50ddccb4384e 3612bbfd03a268a3<br />
6e4f7e01de651401 e108cc247db50392<br />
<br />
== SRP A ==<br />
<br />
private a (normally random) (base 10):<br />
1193346<br />
47663227291363113405741243413916<br />
43482736314616601220006703889414<br />
28162541137108417166380088052095<br />
43910927476491099816542561560345<br />
50331133015255005622124012256352<br />
06121987030570656676375703406470<br />
63422988042473190059156975005813<br />
46381864669664357382020200036915<br />
26156674010218162984912976536206<br />
14440782978764393137821956464627<br />
16314542157937343986808167341567<br />
89864323268060014089757606109012<br />
50649711198896213496068605039486<br />
22864591676298304745954690086093<br />
75374681084741884719851454277570<br />
80362211874088739962880012800917<br />
05751238004976540634839106888223<br />
63866455314898189520502368799907<br />
19946264951520393624479315579863<br />
<br />
private a (hex):<br />
00f2000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 000000000000d3d7<br />
<br />
transmitted srpA:<br />
007da76cb7e77af5 ab61f334dbd5a958<br />
513afcdf0f47ab99 271fc5f7860fe213<br />
2e5802ca79d2e5c0 64bb80a38ee08771<br />
c98a937696698d87 8d78571568c98a1c<br />
40cc6e7cb101988a 2f9ba3d65679027d<br />
4d9068cb8aad6ebf f0101bab6d52b5fd<br />
fa81d2ed48bba119 d4ecdb7f3f478bd2<br />
36d5749f2275e948 4f2d0a9259d05e49<br />
d78a23dd26c60bfb a04fd346e5146469<br />
a8c3f010a627be81 c58ded1caaef2363<br />
635a45f97ca0d895 cc92ace1d09a99d6<br />
beb6b0dc0829535c 857a419e834db128<br />
64cd6ee8a843563b 0240520ff0195735<br />
cd9d316842d5d3f8 ef7209a0bb4b54ad<br />
7374d73e79be2c39 75632de562c59647<br />
0bb27bad79c3e2fc ddf194e1666cb9fc<br />
<br />
== SRP key-agreement ==<br />
<br />
u:<br />
b284aa1064e87751 50da6b5e2147b47c<br />
a7df505bed94a6f4 bb2ad873332ad732<br />
<br />
S:<br />
0092aaf0f527906a a5e8601f5d707907<br />
a03137e1b601e04b 5a1deb02a981f4be<br />
037b39829a27dba5 0f1b27545ff2e287<br />
29c2b79dcbdd32c9 d6b20d340affab91<br />
a626a8075806c26f e39df91d0ad979f9<br />
b2ee8aad1bc783e7 097407b63bfe58d9<br />
118b9b0b2a7c5c4c debaf8e9a460f4bf<br />
6247b0da34b760a5 9fac891757ddedca<br />
f08eed823b090586 c63009b2d740cc9f<br />
5397be89a2c32cdc fe6d6251ce11e44e<br />
6ecbdd9b6d93f30e 90896d2527564c7e<br />
b9ff70aa91acc0ba c1740a11cd184ffb<br />
989554ab58117c21 96b353d70c356160<br />
100ef5f4c28d19f6 e59ea2508e8e8aac<br />
6001497c27f362ed bafb25e0f045bfdf<br />
9fb02db9c908f103 40a639fe84c31b27<br />
<br />
M1:<br />
27949ec1e0f16256<br />
33436865edb037e2<br />
3eb6bf5cb91873f2<br />
a2729373c2039008<br />
<br />
srpK:<br />
e68fd0112bfa31dc<br />
ffc8e9c96a1cbadb<br />
4c3145978ff35c73<br />
e5bf8d30bbc7499a<br />
<br />
== /auth ==<br />
<br />
srpK:<br />
e68fd0112bfa31dc<br />
ffc8e9c96a1cbadb<br />
4c3145978ff35c73<br />
e5bf8d30bbc7499a<br />
<br />
respHMACkey:<br />
6584613597ef012f<br />
f1752b7869f01d03<br />
c72547a7b7199681<br />
531d9df1991edf23<br />
<br />
respXORkey:<br />
455835926ae37a1b<br />
627bd16affbeeab6<br />
27ecc737121826ca<br />
4a2bac2c100bf417<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
plaintext:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
ciphertext:<br />
253957f10e861c7c<br />
0a12bb0193d384d9<br />
579db544666d50bd<br />
3252d6576c768a68<br />
<br />
MAC:<br />
a98c87f5769ab4cc<br />
ca3df863faeb217e<br />
b16ddc29d712b301<br />
12b446324ee806d6<br />
<br />
response:<br />
253957f10e861c7c<br />
0a12bb0193d384d9<br />
579db544666d50bd<br />
3252d6576c768a68<br />
a98c87f5769ab4cc<br />
ca3df863faeb217e<br />
b16ddc29d712b301<br />
12b446324ee806d6<br />
<br />
== /session ==<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
tokenID:<br />
6dcae8ff8f55a793<br />
a0fa1ed31115451b<br />
4df233b3a0641cc6<br />
18ecadfd1fe4a691<br />
<br />
reqHMACkey:<br />
1640a4e6bc8c8e54<br />
858be9960a8b0740<br />
fa06effdf169246f<br />
52012ae868fc6c48<br />
<br />
respHMACkey:<br />
7f3e075e74523ced<br />
fa817c2fa4ae97e1<br />
e51da38d7a992b66<br />
8a35c86af946b155<br />
<br />
respXORkey:<br />
02977a9167830705<br />
74b610cc25320262<br />
175b45fbd7b26438<br />
f9e200abc029f14e<br />
f38399314b172f1e<br />
e928fcdcd194ab19<br />
92433cab0e94569d<br />
bf623b46dd9fbf55<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
sessionToken:<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
plaintext:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
ciphertext:<br />
8216f812e3068182<br />
fc3f9a47a9bf8ced<br />
87cad7684327f2af<br />
617b9a305cb46fd1<br />
53223b92efb289b9<br />
418156777d3905b6<br />
22f28e18ba21e02a<br />
07db81fd612201ea<br />
<br />
MAC:<br />
639fd132f637abd3<br />
ecd2482ccf11ed76<br />
8cfd6979e1954046<br />
1e8ef5204e66c542<br />
<br />
response:<br />
8216f812e3068182<br />
fc3f9a47a9bf8ced<br />
87cad7684327f2af<br />
617b9a305cb46fd1<br />
53223b92efb289b9<br />
418156777d3905b6<br />
22f28e18ba21e02a<br />
07db81fd612201ea<br />
639fd132f637abd3<br />
ecd2482ccf11ed76<br />
8cfd6979e1954046<br />
1e8ef5204e66c542<br />
<br />
== /account/keys ==<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
tokenID:<br />
d010c94c753c012c<br />
d6801e8beb1aa6cc<br />
3da9ea3de3de1dee<br />
32785dbd99a579e8<br />
<br />
reqHMACkey:<br />
1707b05908acc4dc<br />
cda5b8304d9500d0<br />
8c53e00c31672a53<br />
490dfb5ef2934060<br />
<br />
respHMACkey:<br />
31d0c12186b76897<br />
c3351878a65097cf<br />
d595da4ce48e69a2<br />
485ff1a77c71b0d0<br />
<br />
respXORkey:<br />
eed35591e1f1c43b<br />
7cd604e371b9cfb7<br />
a980c9a36fa737c6<br />
a48c5d60a89fc291<br />
4ec1a2150a0777b7<br />
9a1e8499058cd17a<br />
ebc1441db8b3bf18<br />
2cd0aefa92482692<br />
<br />
kA:<br />
2021222324252627<br />
28292a2b2c2d2e2f<br />
3031323334353637<br />
38393a3b3c3d3e3f<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
plaintext:<br />
2021222324252627<br />
28292a2b2c2d2e2f<br />
3031323334353637<br />
38393a3b3c3d3e3f<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
ciphertext:<br />
cef277b2c5d4e21c<br />
54ff2ec85d94e198<br />
99b1fb905b9201f1<br />
9cb5675b94a2fcae<br />
0e80e0564e4231f0<br />
d257ced249c19f35<br />
bb90164eece6e94f<br />
7489f4a1ce1578cd<br />
<br />
MAC:<br />
86f1c57d2e7f6c97<br />
8181684e189b710f<br />
dd26a3f34e3aaed8<br />
64be9577ae81a256<br />
<br />
response:<br />
cef277b2c5d4e21c<br />
54ff2ec85d94e198<br />
99b1fb905b9201f1<br />
9cb5675b94a2fcae<br />
0e80e0564e4231f0<br />
d257ced249c19f35<br />
bb90164eece6e94f<br />
7489f4a1ce1578cd<br />
86f1c57d2e7f6c97<br />
8181684e189b710f<br />
dd26a3f34e3aaed8<br />
64be9577ae81a256<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
unwrapBKey:<br />
6ea660be9c89ec35<br />
5397f89afb282ea0<br />
bf21095760c8c500<br />
9bbcc894155bbe2a<br />
<br />
kB:<br />
2ee722fdd8ccaa72<br />
1bdeb2d1b76560ef<br />
ef705b04349d9357<br />
c3e592cf4906e075<br />
<br />
== use session (certificate/sign, etc) ==<br />
<br />
sessionToken:<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
tokenID:<br />
639503a218ffbb62<br />
983e9628be5cd64a<br />
0438d0ae81b2b9da<br />
deb900a83470bc6b<br />
<br />
reqHMACkey:<br />
3a0188943837ab22<br />
8fe74e759566d0e4<br />
837cbcc7494157aa<br />
c4da82025b2811b2<br />
<br />
== /password/change ==<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
tokenID:<br />
cafc36360afd92de<br />
5ca21800022a9af1<br />
3a5766b91bd82fd4<br />
0eaa5b6e01489796<br />
<br />
reqHMACkey:<br />
b07c0cf4553e44ff<br />
fe991caa2546b50d<br />
895fb9ac8f8746d2<br />
d29119d9616de193<br />
<br />
respHMACkey:<br />
d2ddfefd1913fa34<br />
48e18abda9b54c92<br />
43fd51bf14dc9091<br />
2179269c0e958a04<br />
<br />
respXORkey:<br />
dcc5425e13b876ea<br />
f1d3aa95a4735622<br />
46994088d86adb5a<br />
526d9f1f5d170254<br />
456dd26dcc54483e<br />
f489d55097b69028<br />
8826f0cf1985a6ad<br />
e3e83461517c8d49<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
accountResetToken:<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
plaintext:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
ciphertext:<br />
5c44c0dd973df06d<br />
795a201e28fed8ad<br />
d608d21b4cff4dcd<br />
caf40584c18a9ccb<br />
85ac10ae08918ef9<br />
3c401f9b5b7b5ee7<br />
58f7221ccd50707a<br />
3b31eeba8da15396<br />
<br />
MAC:<br />
cc3053fe922268d7<br />
9c0dd6eb74bd40f5<br />
07ae2d587483b864<br />
8ef771b699dd39d9<br />
<br />
response:<br />
5c44c0dd973df06d<br />
795a201e28fed8ad<br />
d608d21b4cff4dcd<br />
caf40584c18a9ccb<br />
85ac10ae08918ef9<br />
3c401f9b5b7b5ee7<br />
58f7221ccd50707a<br />
3b31eeba8da15396<br />
cc3053fe922268d7<br />
9c0dd6eb74bd40f5<br />
07ae2d587483b864<br />
8ef771b699dd39d9<br />
<br />
== /account/reset ==<br />
<br />
accountResetToken:<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
tokenID:<br />
a6857e5d53d35073<br />
d50ef2ce2c4dd747<br />
32bb2eae1af5bf79<br />
618ed945e1310792<br />
<br />
reqHMACkey:<br />
47fab27352ee6b48<br />
33938d76519bbdb8<br />
ac7293f8b5e74335<br />
6fdd1d5edf39f52d<br />
<br />
reqXORkey:<br />
82ed612313a11673 95108d7d379b2029<br />
7a539ce9d3861e95 1bf5a9b9cdbfb332<br />
bd6aba056ce0c568 2c5a93963446b1b4<br />
7397c8c24f3a1d67 2a0ddc856474f5b1<br />
33ab884ce33335c1 5578a1a7302933cb<br />
458fbee0a5e52414 c914beb97568a30c<br />
28364dc8fb03ae7c 76a2f324a9a1cee6<br />
71b74aa8906d0e03 39fb52a1bf2b1ef5<br />
ab5d883295db62af 20701cb3af42a09e<br />
c76cda585ab5644b 7250ef7b780537e5<br />
b3e784d37a118bd6 57a0fe29ec6e5cd3<br />
325be8e1d8a3dd71 b360ea266757e463<br />
ada6b0a7a85a8ac0 eed618d9f6ee91ab<br />
1d2f714f224d67db 46843c4e3339de15<br />
efe0297a45f9fe0d 6d768b5c589a290f<br />
11f03237192cc0a3 a02645a810d83bb1<br />
84d582bfb15d2393 3fa4805374da62c6<br />
a2c887b157285c6a 79b47156c9abe02e<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
newSRPv:<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
<br />
plaintext:<br />
4041424344454647 48494a4b4c4d4e4f<br />
5051525354555657 58595a5b5c5d5e5f<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
<br />
ciphertext:<br />
c2ac236057e45034 dd59c7367bd66e66<br />
2a02ceba87d348c2 43acf3e291e2ed6d<br />
ac7bab147df1d479 3d4b82872557a0a5<br />
6286d9d35e2b0c76 3b1ccd947565e4a0<br />
22ba995df22224d0 4469b0b6213822da<br />
549eaff1b4f43505 d805afa86479b21d<br />
39275cd9ea12bf6d 67b3e235b8b0dff7<br />
60a65bb9817c1f12 28ea43b0ae3a0fe4<br />
ba4c992384ca73be 31610da2be53b18f<br />
d67dcb494ba4755a 6341fe6a691426f4<br />
a2f695c26b009ac7 46b1ef38fd7f4dc2<br />
234af9f0c9b2cc60 a271fb377646f572<br />
bcb7a1b6b94b9bd1 ffc709c8e7ff80ba<br />
0c3e605e335c76ca 57952d5f2228cf04<br />
fef1386b54e8ef1c 7c679a4d498b381e<br />
00e12326083dd1b2 b13754b901c92aa0<br />
95c493aea04c3282 2eb5914265cb73d7<br />
b3d996a046394d7b 68a56047d8baf13f<br />
<br />
== /account/destroy ==<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
tokenID:<br />
b2512ff41c4e6d8a<br />
beb3bda37e326f51<br />
cf4efdbf90e50e77<br />
029be2563884b9fe<br />
<br />
reqHMACkey:<br />
75cfa782c19e41f9<br />
c7e125f3dc4c3bf1<br />
0a77c93a9999e06f<br />
b2646b3038e4ea44<br />
<br />
= Keyserver Protocol Summary =<br />
<br />
* POST /account/create (email,srpV,srpSalt) -> ok (server sends verification email)<br />
** creates a user account<br />
* GET /account/devices [sessionToken] () -> list of devices<br />
* GET /account/keys [keyFetchToken,needs-verf] () -> kA/wrap(kB)<br />
** single-use, only if email is verified, encrypted results<br />
* POST /account/reset [authed+encrypted by accountResetToken] (wrap(kB),srpV,srpSalt) -> ok<br />
** single-use, does not require email to be verified, revoke all tokens for account, send notification email to user<br />
* POST /account/delete [authToken] () -> ok, account deleted<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authToken] () -> keyFetchToken, sessionToken<br />
* POST /session/destroy [sessionToken] () -> ok<br />
** for detaching a device, destroy all tokens<br />
* POST /recovery_email/status [sessionToken] () -> "verified" status of email<br />
** use "Accept: text/event-stream" header for server-sent-events; server will send "update" event with the new content of the resource any time it changes.<br />
* POST /recovery_email/resend_code [sessionToken] () -> re-send verification email<br />
* POST /recovery_email/verify_code (code) -> set "verified" flag<br />
** this code will come from a clickable link and is an unauthenticated endpoint<br />
** this could maybe take the recovery method if that would be helpful<br />
** sets verified flag on recovery method<br />
* POST /certificate/sign [sessionToken,needs-verf] (pubkey) -> cert<br />
** only if recovery email is verified<br />
* POST /password/change/start [authToken,needs-verf] () -> accountResetToken, keyFetchToken<br />
* POST /password/forgot/send_code () -> forgotPasswordToken<br />
** sends code to recovery method (email for now, maybe SMS later)<br />
** this is a short code, not a clickable link<br />
* POST /password/forgot/resend_code (forgotPasswordToken) -> re-sends code<br />
* POST /password/forgot/verify_code (forgotPasswordToken, code) -> accountResetToken<br />
** sets verified flag on recovery method<br />
* POST /get_random_bytes<br />
<br />
<br />
== Typical Client Flows ==<br />
<br />
Create account<br />
<br />
* POST /account/create (email,srpV,srpSalt) -> ok (server sends verification email)<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authed with authToken]() -> keyFetchToken, sessionToken<br />
* GET /recovery_email/status [sessionToken] () -> "verified" status<br />
** (optional, only if user requests resend) POST /recovery_email/resend_code [sessionToken]() -> ok<br />
** POST /recovery_email/verify_code (code) -> ok<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
* POST /certificate/sign [sessionToken] (pubkey) -> cert<br />
<br />
<br />
Attach to new device<br />
<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authToken] () -> keyFetchToken, sessionToken<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
** (if unverified-error, do waitUntilEmailVerified, then try again)<br />
* POST /certificate/sign [sessionToken] (pubkey) -> cert<br />
<br />
<br />
Forgot password<br />
<br />
* POST /password/forgot/send_code (email) -> forgotPasswordToken<br />
* POST /password/forgot/verify_code (forgotPasswordToken, code) -> accountResetToken<br />
* POST /account/reset [authed+encrypted by accountResetToken] (0000,srpV,srpSalt) -> ok<br />
* GOTO "Attach to new device"<br />
<br />
<br />
Change Password<br />
<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /password/change/start [authToken] () -> accountResetToken, keyFetchToken<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
* POST /account/reset [authed+encrypted by accountResetToken] (wrap(kB),srpV,srpSalt) -> ok<br />
* GOTO "Attach to new device"</div>Warnerhttps://wiki.mozilla.org/index.php?title=File:PICL-IdPAuth-encrypt-resetAccount.png&diff=692240File:PICL-IdPAuth-encrypt-resetAccount.png2013-08-08T19:14:43Z<p>Warner: Warner uploaded a new version of &quot;File:PICL-IdPAuth-encrypt-resetAccount.png&quot;</p>
<hr />
<div></div>Warnerhttps://wiki.mozilla.org/index.php?title=File:PICL-IdPAuth-use-session.png&diff=692238File:PICL-IdPAuth-use-session.png2013-08-08T19:13:20Z<p>Warner: Warner uploaded a new version of &quot;File:PICL-IdPAuth-use-session.png&quot;</p>
<hr />
<div></div>Warnerhttps://wiki.mozilla.org/index.php?title=File:PICL-IdPAuth-keys-client.png&diff=692235File:PICL-IdPAuth-keys-client.png2013-08-08T19:12:15Z<p>Warner: Warner uploaded a new version of &quot;File:PICL-IdPAuth-keys-client.png&quot;</p>
<hr />
<div></div>Warnerhttps://wiki.mozilla.org/index.php?title=File:PICL-IdPAuth-keys-server.png&diff=692233File:PICL-IdPAuth-keys-server.png2013-08-08T19:11:05Z<p>Warner: Warner uploaded a new version of &quot;File:PICL-IdPAuth-keys-server.png&quot;</p>
<hr />
<div></div>Warnerhttps://wiki.mozilla.org/index.php?title=File:PICL-IdPAuth-encrypt-authToken.png&diff=692231File:PICL-IdPAuth-encrypt-authToken.png2013-08-08T19:09:55Z<p>Warner: Warner uploaded a new version of &quot;File:PICL-IdPAuth-encrypt-authToken.png&quot;</p>
<hr />
<div></div>Warnerhttps://wiki.mozilla.org/index.php?title=File:PICL-IdPAuth-main-KDF.png&diff=692228File:PICL-IdPAuth-main-KDF.png2013-08-08T19:08:35Z<p>Warner: Warner uploaded a new version of &quot;File:PICL-IdPAuth-main-KDF.png&quot;</p>
<hr />
<div></div>Warnerhttps://wiki.mozilla.org/index.php?title=Identity/AttachedServices/KeyServerProtocol&diff=692223Identity/AttachedServices/KeyServerProtocol2013-08-08T19:06:16Z<p>Warner: /* Creating a Session */ explain new shared-tokenID way to use authTokens</p>
<hr />
<div>= PiCL Key Server / IdP Protocol =<br />
<br />
NOTE: This specification is slowly converging on stability (31-Jul-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.<br />
<br />
The server is being developed in https://github.com/mozilla/picl-idp . This repo currently includes a demonstration client (node.js CLI).<br />
<br />
Note that all messages are delivered over an HTTPS connection. The client browser may also implement cert-pinning to improve on the certificate validation process. The protections described below are in addition to those provided by TLS.<br />
<br />
Remaining TODO items:<br />
<br />
* decide on client-side key-stretching parameters: http://keywrapping.appspot.com can help<br />
* finalize SRP questions (definition of M1, generation of a/b)<br />
* finalize proof-of-work/DoS-prevention details<br />
* decide how to rate-limit account-creation calls<br />
* confirm this is actually implementable inside Firefox (especially w.r.t. NSS and Android/Java crypto)<br />
<br />
= Creating The Account =<br />
<br />
The first act performed by a user is to create the account. They enter email+password into their browser, which then does the following steps:<br />
<br />
* decide upon stretching parameters (perhaps consulting the keyserver for recommendations, but imposing a minimum strength requirement). For now we used a fixed {firstPBKDF:20000, scrypt: {N:65536, r:8, p:1}, secondPBKDF:20000}.<br />
* randomly choose a 32-byte mainSalt (this should be unique, but is not secret)<br />
* choose the SRP group parameters (fixed: use the 2048-bit group described below)<br />
* perform key-stretching (as described below), derive stretchedPW and srpPW<br />
* randomly choose a 32-byte srpSalt (unique, but not secret)<br />
* create srpVerifier from srpPW and srpSalt (as described below)<br />
* deliver (email, stretchParams, mainSalt, srpParams, srpSalt) to the keyserver's "POST /account/create" API<br />
<br />
<br />
To limit abuse, the createAccount() should also require a fresh "createToken". This should be created by some other API, outside the scope of this document, that perhaps requires a CAPTCHA or something. createAccount() might also require a proof-of-work token, as described below.<br />
<br />
The server, when creating a new account, creates both kA and wrap(kB) as randomly-generated 256-bit (32-byte) strings. It stores these, along with all the remaining values, indexed by email, in the account table where they can be retrieved by getToken later.<br />
<br />
== Email Verification ==<br />
<br />
To prevent fixation attacks, we require new accounts to verify their configured recovery email address before letting them learn the generated keys or obtain a signed certificate. Nevertheless, we wish clients to forget the user's password while they wait for email verification to complete. To achieve this, clients can obtain a sessionToken before verification, but most APIs that require it will raise errors until verification is finished.<br />
<br />
The server will send email with a URL that contains a long random "verification code" in the "fragment" hash. This URL points to a static page with some javascript that submits the code to the "POST /account/recovery_methods/verify_code" API. The URL can be clicked by any browser (it is not bound to anything), and when the API is hit, the account is marked as verified.<br />
<br />
After the client submits /account/create, it performs the "/session/auth" login sequence below to obtain a sessionToken. It then polls the "GET /account/recovery_methods" (which requires a sessionToken but not account verification) until the user clicks the email link and the API reports verification is complete. Then the client uses "GET /account/keys" and "POST /certificate/sign", described below, to obtain kA, kB, and a signed certificate to talk to the storage server.<br />
<br />
= Login: Obtaining the authToken =<br />
<br />
To connect a browser to an existing account, we use the following login protocol to transform an email+password pair into a single-use authToken. We will use this in the next section to create a session, from which we can obtain encryption keys and signed certificates.<br />
<br />
This protocol starts by using key-stretching to transform the email+password into a "stretchedPW", then feeds this into an SRP protocol to get the authToken.<br />
<br />
[[File:PICL-IdPAuth-auth-start.png|IdP Auth Protocol]]<br />
<br />
This authToken can be used (once) to do one of the following:<br />
<br />
* /session/create: obtain a sessionToken (and keyFetchToken), which enables storage server access<br />
* /password/change: obtain an accountResetToken, to safely reset the account password<br />
* /account/delete: to delete the entire account<br />
<br />
The protocol is designed to enable parallelism between key-stretching and the initial network messages, to reduce the time it takes to connect a browser to the account. In total, the browser requires five messages in four roundtrips (1: /auth/start, 2: /auth/finish, 3: /session/create, 4: /account/keys and /certificate/sign in parallel) before it is ready to talk to the storage server.<br />
<br />
== auth/start ==<br />
<br />
As soon as the user finishes typing in the email address, the client should send it in the "/auth/start" 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 loginSRPToken that is used to associate this request with the subsequent /auth/finish 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.<br />
<br />
To mitigate DoS abuse, /auth/start may also require a proof-of-work string, described below.<br />
<br />
== Client-Side Key Stretching ==<br />
<br />
"Key Stretching" is the practice of running a password through a computationally-expensive one-way function before using it for encryption or authentication. The goal is to make brute-force dictionary attacks more expensive, by raising the cost of testing each guess.<br />
<br />
To protect the user's class-B data against compromises of our keyserver, we perform this key stretching on the client, rather than on the server. We use the memory-hard "scrypt" function (pronounced "ess-crypt") for this purpose, as motivated by the attacker-cost studies in [[Identity/CryptoIdeas/01-PBKDF-scrypt]]. Slower (low-RAM) devices can still participate by outsourcing the scrypt step to an "scrypt helper". To provide at least minimal protection against a compromise of this helper, we sandwich the scrypt step between two PBKDF2 steps. The complete protocol looks like this:<br />
<br />
[[File:PICL-IdPAuth-stretch-KDF.png|Stretching KDF]]<br />
<br />
Our initial parameter choices for this stretching are to use 20000 iterations of PBKDF2 on each side (40k in all), and to run scrypt with N/r/p set to 64k/8/1. This requires roughly 60MB of RAM for about 1.3 seconds on a 1.8GHz Intel Atom linux box (it will run faster, but use the same memory, on more modern CPUs). We are still studying performance on various platforms to decide what parameters to use in V1: lowering them speeds up the login process, but reduces security (by reducing the cost of an dictionary attack).<br />
<br />
Since the stretching is expected to take a second or two, the client can optimistically start this process (using default parameters) before receiving the auth/start 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 "mainSalt is added *after* the stretching, to enable this parallelism (at a tiny cost in security).<br />
<br />
After "stretchedPW" is derived, a second HKDF call is used to derive "srpPW" and "unwrapBKey" which will be used later.<br />
<br />
[[File:PICL-IdPAuth-main-KDF.png|masterKey KDF]]<br />
<br />
== SRP Protocol Details ==<br />
<br />
The PiCL client uses the SRP protocol (http://srp.stanford.edu/) to prove that it knows the (stretched) account password without revealing the actual password (or information enabling a brute-force attack) to the server or any eavesdroppers. <br />
<br />
SRP is somewhat underspecified. We use SRP-6a, with SHA256 as the hash, and the 2048-bit modulus defined in RFC 5053 Appendix A. We consistently zero-pad all string values to 256 bytes (2048 bits), and use H(A+B+S) as the key-confirmation message "M1". These details, plus the SRP design papers and RFCs 2945 and 5054, should be enough to build a compatible implementation. The diagrams below and the test vectors at the end of this page can be used to verify compatibility.<br />
<br />
The server should use Jed's SRP module from https://github.com/jedp/node-srp . The client might use SJCL (http://crypto.stanford.edu/sjcl/) or native code (NSS).<br />
<br />
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).<br />
<br />
Note that SRP-6a uses a "k" value which basically encodes the group being used ("N" and "g"). Since all PICL accounts use the same 2048-bit group, they will all use the same "k" value (not to be confused with the per-session shared-secret "K" key that emerges from the protocol). This group's "k" integer is (as a base-10 number):<br />
259003859907<br />
09503006915442163037721228467470<br />
35652616593381637186118123578112<br />
<br />
=== SRP Verifier Calculation ===<br />
<br />
When the client first creates the account, it must combine the account email address, the stretched password (srpPW), and a randomly-generated srpSalt, to compute the srpVerifier. The server will use this verifier later, to check whether or not the client really knows the password.<br />
<br />
If the server is compromised and an attacker learns the srpVerifier for a given account, they cannot use this to directly log in (the verifier is not "password-equivalent"), but it does allow them to perform an offline brute-force attack against the user's password. In this respect, it is similar to a traditional hashed password. We make these attacks somewhat more expensive by performing the client-side stretching described above, instead of using the raw user password in the SRP calculation.<br />
<br />
[[File:PICL-IdPAuth-SRP-Verifier.png|client-side SRP Verifier calculation]]<br />
<br />
Test vectors are provided at the end of this page. The protocol requires that several values ("a", "b", "srpSalt") are chosen randomly, but for illustrative purposes, the test vectors use carefully-crafted non-random values. For the user's sake, please ensure implementations use proper random values instead of simply copying the test vectors.<br />
<br />
The client sends srpSalt and srpVerifier to the server when it creates the account. It will also re-compute the 'x' value (as an integer) during sign-in. The server will convert the srpVerifier string back into an integer ('v') for use during its own sign-in calculations.<br />
<br />
=== SRP Server-side Sign-In Flow ===<br />
<br />
When the user connects a new device to their account, they use the /auth/start API to start the SRP protocol. This sends the account email address to the server. The server looks up the stored srpVerifier for this account, creates a random 'b' integer, performs some math to compute the "B" number, then converts B into a string known as "srpB". "srpB" is returned to the client, along with srpSalt and the key-stretching parameters. "b" and "srpB" are retained for the subsequent /auth/finish call. '''Note that it is critical that the "b" integer remain secret on the server.''' The server also allocates a random loginSRPToken to connect the two calls.<br />
<br />
[[File:PICL-IdPAuth-SRP-Server.png|server-side SRP]]<br />
<br />
Later, /auth/start will be called with the client's srpA string and its M1 key-confirmation string. srpA is combined with srpB to calculate the "u" integer. srpA is also turned into an integer and used to compute the shared-secret "S" integer. S is then used to compute the shared key "srpK", which is the output of the SRP process.<br />
<br />
=== SRP Client Calculation ===<br />
<br />
While the client is waiting for the response to /auth/start, it begins its key-stretching calculations. Everything else must wait until the response to /auth/start arrives, which includes the key-stretching parameters (which are retroactively confirmed), srpSalt, and the server's generated srpB value.<br />
<br />
[[File:PICL-IdPAuth-SRP-Client.png|client-side SRP]]<br />
<br />
Once the client knows srpSalt, it computes the same "x" integer as it did in the middle of the srpVerifier calculation. It also converts srpB into an integer named "B". Then it creates a random "a" integer, uses it to compute the string "srpA", then combines srpA with the server's srpB to compute the "u" integer. It then combines the static "k", the password-derived "x", the combined "u", and the server's "B", together with some magic math, to derive the "S" integer. If everything went well, the client will compute the same "S" value as the server did. If not (the password was wrong, or the client is talking to a fake server that doesn't really know srpVerifier), then the two "S" values will not match.<br />
<br />
('''Again, it is critical that the client keep its "a" and "x" integers secret, both during and after the protocol run.''')<br />
<br />
To safely tell if the "S" values match, both client and server combine srpA, srpB, and their (independently) generated "S" strings to form a string named "M1". The client sends M1 (along with srpA) in the /session/auth/finish message. The server compares the client's copy of M1 against its own. If they match, the client knew the password and the server can safely respond with the encrypted account data. If they do not match, the client (or a man-in-the-middle attacker) did not know the password, and the client should increment a counter that can trigger defenses against online guessing attacks. The server must then return an error to the client, and '''not''' use or reveal srpK (or the correct M1) in any way.<br />
<br />
Both client and server also hash "S" into "srpK". This is the shared session key, from which specific message encryption and MAC keys are derived (as described below).<br />
<br />
=== SRP Notes ===<br />
<br />
The SRP "g" (generator) and "N" (prime modulus) should use the 2048-bit value from RFC 5054 Appendix A. 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.<br />
<br />
There are several places in SRP where integers (usually in the range 1..N-1) are converted into bytestrings, either for transmission over a wire, or to be passed into a hash function. The SRP spec is somewhat ambiguous about padding here: if the integer happens to be less than 2^2040, the simplest toString() approach will yield a '''255''' byte string, not a 256 byte string. PiCL consistently uses padding, so compatible implementations must prepend one or more NUL bytes to these short strings before transmission or hashing. The examples above were brute-forced to ensure that "srpVerifier", "srpA", "srpB", and "S" all wind up with leading zeros, to exercise the padding code in compatible implementations. If you are having problems getting your code to match these results, add some assertions to test that the stringified integers being put into hashes are exactly 256 bytes long.<br />
<br />
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 (/auth/finish).<br />
<br />
The server receives "A" in /auth/finish, 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 keyFetchToken+sessionToken as described below, returning the encrypted/MACed bundle in the response to /auth/finish.<br />
<br />
Outstanding crypto questions:<br />
<br />
* How exactly should the "a" and "b" integers be generated? The issue is of how much bias SRP can tolerate. Ideally these integers are uniformly distributed from 1 to N-1 (inclusive). The only way to obtain a purely uniform distribution from a source of random bytes is try-try-again: pick a (integral-number-of-bytes) number, compare it to the desired range, try again if it falls outside the range. If you get unlucky, this can take a lot of guesses, depending upon how close the range is to a power of two. ECDSA implementations tend to pick a number twice as long as the modulus and then modulo it down (rand(2^4096) % N), which yields a tiny fraction of a bit of bias. The cheaper approach is to do the same with a number of equal length (rand(2^2048) % N), which imposes more bias. Some SRP implementations appear to be satisfied by rand(2^256). We need more review here.<br />
* The original SRP papers defined M1=H(A+B+S) as we use here, but other implementation (in particular RFC2945) uses a curious construct that includes the username, the salt, and an odd XOR combination of N and g. We need to decide what to use for M1.<br />
<br />
== /auth/finish ==<br />
<br />
The client-side SRP calculation results in two values that are sent to the server in the /auth/finish 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.<br />
<br />
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.<br />
<br />
The server then allocates a single-use 32-byte random token named "authToken". It encrypts the token with the session key, and returns a success message with the encrypted bundle.<br />
<br />
All tokens have an associated tokenID, described below. The server needs to maintain a table that maps the tokenID to the token itself, so it can derive other values from the token later. The tokens are also associated with a specific account, so later API requests do not specify an email address or account ID.<br />
<br />
== Decrypting the /auth/finish Response ==<br />
<br />
The SRP session key ("srpK") is used to derive two other keys: respHMACkey and respXORkey.<br />
<br />
[[File:PICL-IdPAuth-encrypt-authToken.png|Decrypting the authToken]]<br />
<br />
The respXORkey is used to encrypt the authToken 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.<br />
<br />
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 returns the authToken value.<br />
<br />
= Creating a Session =<br />
<br />
For login, the single-use authToken is spent on a call to /session/create .<br />
This allocates two new (random 32-byte) tokens: a long-lived "sessionToken", and a single-use "keyFetchToken". The /session/create call returns an encrypted bundle containing the two tokens.<br />
<br />
[[File:PICL-IdPAuth-encrypt-sessionToken.png|Decrypting the sessionToken and keyFetchToken]]<br />
<br />
For calls which accept an authToken, the client uses authToken to derive three values:<br />
<br />
* tokenID<br />
* reqHMACkey<br />
* requestKey<br />
<br />
<br />
The client uses tokenID and reqHMACkey for a HAWK (https://github.com/hueniverse/hawk/) request to the "POST /session/create" API, using tokenID as "credentials.id" and reqHMACkey as "credentials.key". The server uses tokenID to look up the corresponding token, then derives reqHMACkey to validate the request.<br />
<br />
Each authToken-using call then derives additional API-specific values from requestKey. /session/create uses two derived values:<br />
<br />
* respHMACkey<br />
* respXORkey<br />
<br />
<br />
When the server receives a valid /session/create request, it allocates sessionToken and keyFetchToken, concatenates them, encrypts the pair by XORing it with the derived respXORkey, and attaches a MAC generated with respHMACkey. The encrypted MACed bundle is returned to the client.<br />
<br />
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 keyFetchToken and sessionToken values.<br />
<br />
Each authToken is single-use: once a successful request has been made with it, the authToken and its corresponding ID is removed from the server's memory, and subsequent attempts to use it will return a "no such token" error. The token is consumed even if the request fails (e.g. the MAC did not match).<br />
<br />
The authToken can be used (once) by multiple APIs. The server only needs to maintain one table mapping tokenID to authToken, because all these APIs share the same method of deriving a tokenID and reqHMACkey from the authToken.<br />
<br />
When a HAWK request appears, it should look up the tokenID in this table, retrieve the authToken, recompute reqHMACkey and requestKey, validate the request, delete the table entry, then hand the HTTP request and requestKey to the specific API handler. That handler should derive the API-specific values (respHMACkey, respXORkey, etc) to process the request or construct the response.<br />
<br />
The server can support multiple sessions per account (typically one per client device, plus perhaps others for account-management portals). There can also be multiple outstanding keyFetchTokens. The sessionToken lasts forever (until revoked by a password change or explicit revocation command), and can be used an unlimited number of times. The keyFetchToken expires after 60 seconds, and is single-use.<br />
<br />
= Obtaining keys kA and kB =<br />
<br />
Clients which have exchanged an authToken for either a sessionToken or an accountResetToken will also receive a keyFetchToken. This single-use token allows the client to retrieve kA and wrap(kB), which enables the client to encrypt and decrypt browser data (bookmarks, open-tabs, etc) correctly. As above, the keyFetchToken is used to derive tokenID, reqHMACkey, respHMACkey, and respXORkey, which are used in a HAWK request to the "GET /account/keys" API.<br />
<br />
The server pulls kA and wrap(kB) from the account table, concatenates them, encrypts the pair by XORing it with the derived respXORkey, and attaches a MAC generated with respHMACkey.<br />
<br />
[[File:PICL-IdPAuth-keys-server.png|keyFetchToken: server encrypts keys]]<br />
<br />
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 and wrap(kB) values.<br />
<br />
[[File:PICL-IdPAuth-keys-client.png|keyFetchToken: client decrypts keys]]<br />
<br />
Finally, the server-provided wrap(kB) value is simply XORed with the password-derived unwrapBKey (both are 32-byte strings) to obtain kB. There is no MAC on wrap(kB).<br />
<br />
[[File:PICL-IdPAuth-key-unwrap.png|unwrapping kB]]<br />
<br />
"kA" and "kB" enable the browser to encrypt/decrypt synchronized data records. They will be used to derive separate encryption and HMAC keys for each data collection (bookmarks, form-fill data, saved-password, open-tabs, etc). This will allow the user to share some data, but not everything, with a third party. The client may intentionally forget kA and kB (only retaining the derived keys) to reduce the power available to someone who steals their device.<br />
<br />
Note that /account/keys will not succeed until the account's email address has been verified. Also note that each keyFetchToken is single-use and short-lived. The token is consumed even if the request fails (e.g. the MAC does not match).<br />
<br />
= Signing Certificates =<br />
<br />
The sessionToken is used to derive two values:<br />
<br />
* tokenID<br />
* request HMAC key<br />
<br />
<br />
[[File:PICL-IdPAuth-use-session.png|Using the sessionToken, signing certificates]]<br />
<br />
The requestHMACkey is used in a HAWK request to provide integrity over many APIs, including /certificate/sign. requestHMACkey 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.<br />
<br />
For /certificate/sign, it is critical to enable payload verification by setting options.payload=true (on both client and server). Otherwise a man-in-the-middle could submit their own public key, get it signed, and then delete the user's data on the storage servers.<br />
<br />
The following keyserver APIs require a HAWK-protected request that uses the sessionToken. In addition, some require that the account be in the "verified" state:<br />
<br />
* GET /account/devices<br />
* POST /session/destroy<br />
* GET /recovery_email/status<br />
* POST /recovery_email/resend_code<br />
* POST /certificate/sign (requires "verified" account)<br />
<br />
= Resetting The Account =<br />
<br />
The account may be reset in two circumstances: when the user changes their password, and when the user forgets their password. In both cases, the client first obtains an "accountResetToken". This token is then used to change the SRP Verifier and either reset or replace the wrap(kB) value.<br />
<br />
== Changing the Password ==<br />
<br />
When the user wishes to change their password (i.e. they still know the old password), they first use the /auth/start+/auth/finish SRP protocol safely obtain an "authToken". They they use the "/password/change/start" API to exchange the authToken for an accountResetToken and a keyFetchToken.<br />
<br />
[[File:PICL-IdPAuth-encrypt-passwordChange.png|Server encrypts passwordChange response]]<br />
<br />
The accountResetToken will be used below to set the new password safely. The keyFetchToken should be used first, to obtain kB, so the subsequent account reset can replace wrap(kB) with a new value. This allows the password-changing client to retain their class-B data.<br />
<br />
Requiring an authToken proves that the user has provided the correct account password recently. When the account is reset, all active sessions and tokens will be cancelled (disconnecting all devices from the account). The client should immediately establish a new session as described above.<br />
<br />
This API is only used when the user knows their old password: if they have forgotten the password, use the "/password/forgot" APIs below.<br />
<br />
== Handling a Forgotten Password ==<br />
<br />
When the user has forgotten their password, they can use one of their "recovery methods" to obtain an accountResetToken. For now, this means we send a random code to the email address associated with their account. The user must copy this code from the email into their client, whereupon the client will get an accountResetToken that can be delivered to the API below.<br />
<br />
Note that, since the forgotten-password client never learns kB, any class-B data will be lost. This is necessary to protect class-B data from attackers who can read the users's email but do not know the account password (including those who compromise the IdP and the keyserver itself). When using /account/reset below, they will set wrap(kB) to a string of all zeros, which means the server should generate a new random wrap(kB) (just as it does during account creation).<br />
<br />
The /password/forgot/send_code API is used to ask the server to send a recovery code. This takes a recovery method, which for now is just an email address. This API is unauthenticated (after all, the user who has forgotten their password knows nothing but their email address). The server marks the corresponding account as "pending recovery", allocates a random forgotPasswordToken for the account, creates a recovery code, and sends the code (with instructions) via email. The API returns forgotPasswordToken to the client.<br />
<br />
The user must copy the recovery code into the same browser where they started the process. The client then submits the code to /password/forgot/verify_code along with the forgotPasswordToken they received. If they match, the server allocates a accountResetToken and returns it to the client. If they do not match, the server increments a counter (which is used to decide if an online guessing attack is happening).<br />
<br />
forgotPasswordToken can be used three times before it is exhausted. If the user guesses incorrectly this often, the client must call send_code again to get a new token and code. Each account has at most one token+code active at a time.<br />
<br />
The recovery code is initially a random 8-digit decimal number. If an attacker tries to sign in as someone else, hits the "forgot my password" button, then submits a guess to /password/forgot/verify_code, they will have a 1-in-100-million chance of success. If the server detects too many wrong guesses, it should increase the length of new codes. Another defensive technique is to require that users click an email link before being given the code: the server is told when the link is clicked, so the code will not be enabled until the email has been read. It remains to be seen whether this will be sufficient.<br />
<br />
The exact thresholds are TBD, but a nominal goal is to keep the chances of any attack succeeding to below 1-in-a-million per year. To achieve this, we can tolerate 100 verify_code failures in a single year before we must increase the length of the code.<br />
<br />
== Using accountResetToken ==<br />
<br />
An accountResetToken, obtained through either of the methods above, is used to invoke the /account/reset API. If the request is accepted, the server replaces the account password with a new one, updates the wrap(kB) value, cancels all active sessions and tokens (disconnecting all devices from the account), and sends a "your password has been changed" email to the user.<br />
<br />
If the client knew the old password, it can supply a new wrap(kB) value that will yield the same kB key as before, so no data will be lost. If the client did *not* know the old password, then it will supply a wrap(kB) of all-zeros, which tells the server to generate a new random wrap(kB) (just like during account creation), which means the class-B data will be lost.<br />
<br />
The client puts their new password through the same stretching procedure as described in the new-account section above, resulting in a new srpVerifier and unwrapBKey. If they knew the old kB, they XOR it with the new unwrapBKey to obtain a new wrap(kB).<br />
<br />
/account/reset needs request confidentiality, since the arguments include the newly wrapped kB value and the new SRP verifier, both of which enable a brute-force attack against the password. HAWK provides request integrity. The response is a single "ok" or "fail", conveyed by the HTTP headers, so we do not require response confidentiality, and can live without response integrity.<br />
<br />
So the single-use resetToken is used to derive three values:<br />
<br />
* tokenID<br />
* request HMAC key<br />
* request XOR key<br />
<br />
<br />
[[File:PICL-IdPAuth-encrypt-resetAccount.png|Client encrypts resetAccount request]]<br />
<br />
The request data will contain wrap(kB) and the new SRP verifier, concatenated together. Since we always pad the SRP verifier to the full (256-byte) group length, both pieces are fixed-length. We generate enough reqXORkey bytes to cover both values.<br />
<br />
The request data is XORed with requestXORkey, then delivered in the body of a HAWK request that uses tokenID as credentials.id and requestHMACkey as credentials.key . Note: it is critical to include the request body in the HAWK integrity check (options.payload=true, on both client and server), otherwise a man-in-the-middle could substitute their own SRP verifier, giving them control over the account (access to the user's class-A data, and a brute-force attack on their password).<br />
<br />
The client submits other values in the same request:<br />
<br />
* stretchParams<br />
* mainKDFSalt<br />
* srpSalt<br />
<br />
<br />
These values do not require confidentiality, so are not included in the encrypted bundle. They are still protected by the HAWK integrity check. Note that the server should assert that both salts are different than the previously stored values.<br />
<br />
After using /account/reset, clients should immediately perform the login protocol from above. If the old password was forgotten, this is necessary to fetch kA. In either case, a new sessionToken is required, since old sessions and tokens are revoked by /account/reset. Clients should retain the new srpPassword value during this process to avoid needing to run the lengthy key-stretching routine a second time.<br />
<br />
= Deleting The Account =<br />
<br />
When the user wishes to completely delete their account, the browser needs to perform two actions:<br />
<br />
* contact the storage servers and delete all records and collections<br />
* contact the keyserver and delete the account information<br />
<br />
The user should be prompted for their password as confirmation (i.e. a browser in the normal attached-and-synchronizing state should not be able to erase the account information: it must acquire a new authToken first).<br />
<br />
The device then obtains an authToken as described above, then spends it on a HAWK-protected request to the /account/destroy endpoint. This request contains no body and returns only a success code.<br />
<br />
[[File:PICL-IdPAuth-deleteAccount.png|Deleting the Account]]<br />
<br />
= Crypto Notes =<br />
<br />
Strong entropy is needed in the following places:<br />
<br />
* (client) creation of private "a" value inside SRP<br />
* (server) initial creation of kA and wrap(kB)<br />
* (server) creation of private "B" value inside SRP<br />
* (server) creation of signToken and resetToken<br />
<br />
<br />
On the server, code should get entropy from /dev/urandom via a function that uses it, like "crypto.randomBytes()" in node.js or "os.urandom()" in python. On the client, code should combine local entropy with some fetched from the keyserver via getEntropy(), to guard against failures in the local entropy pool. Something like HKDF(SKM=localEntropy+remoteEntropy, salt="", context=KW("mergeEntropy")).<br />
<br />
An HKDF-based stream cipher is used to protect the response for getToken2(), and the request for resetAccount(). HKDF is used to create a number of random bytes equal to the length of the message, then these are XORed with the plaintext to produce the ciphertext. An HMAC is then computed from the ciphertext, to protect the integrity of the message.<br />
<br />
HKDF, like any KDF, is defined to produce output that is indistinguishable from random data ("The HKDF Scheme", http://eprint.iacr.org/2010/264.pdf , by Hugo Krawczyk, section 3). XORing a plaintext with a random keystream to produce ciphertext is a simple and secure approach to data encryption, epitomized by AES-CTR or a stream cipher (http://cr.yp.to/snuffle/design.pdf). HKDF is not the fastest way to generate such a keystream, but it is safe, easy to specify, and easy to implement (just HMAC and XOR).<br />
<br />
Each keystream must be unique. SRP is defined to produce a random session key for each run (as long as at least one of the sides provides a random ephemeral key). We define resetToken to be a single-use randomly-generated value. Hence our two HKDF-XOR keystreams will be unique.<br />
<br />
A slightly more-traditional alternative would be to use AES-CTR (with the same HMAC-SHA256 used here), with a randomly-generated IV. This is equally secure, but requires implementors to obtain an AES library (with CTR mode, which does not seem to be universal). An even more traditional technique would be AES-CBC, which introduces the need for padding and a way to specify the length of the plaintext. The additional specification complexity, plus the library load, leads me to prefer HKDF+XOR.<br />
<br />
kB is equal to the XOR of wrapKey (which is a deterministic function of the user's email address, password, mainSalt, and the stretching parameters) and the server's randomly-generated wrap(kB) value, making kB a random value too. Using XOR as a wrapping function allows us to avoid sending kB or wrap(kB) in the initial createAccount arguments, which are not protected by SRP, and thus would enable an eavesdropper to mount a dictionary attack on the password (using wrap(kB) as their oracle).<br />
<br />
Likewise, allowing the server to generate kA avoids exposure during createAccount. The only point of vulnerability is a forgotten-password resetAccount() message, if an eavesdropper can learn the resetToken. In this case, the attacker might either use the resetToken themselves (then use signToken to learn kA and wrap(kB)), or passively decrypt the resetAccount() arguments to retrieve just wrap(kB). Given wrap(kB) and some encrypted browser data, the attacker can guess passwords (and derive kB, etc) until the data decrypts properly.<br />
<br />
To make this technique safe, any time kB or the password is changed, the mainSalt should be changed too. Otherwise knowledge of both wrap(old-kB) and old-kB would reveal wrapKey, making it easy to deduce the new kB. Changing mainSalt causes wrapKey to change too, preventing this.<br />
<br />
mainSalt is incorporated at the end of the stretching process to allow it to proceed in parallel with the getToken1() call that retrieves the salt. The inputs to the lengthy stretch come entirely from the user (email and password) or are optimistically (stretching parameters). This speedup seems more important than the minor security benefit of including the salt at the beginning of the stretch.<br />
<br />
There is no MAC on wrap(kB). If the keyserver chooses to deliver a bogus wrap(kB) or kA, the client will discover the problem a moment later when it talks to a storage server and attempts to retrieve data from an unrecognized collection-ID (since we intend to derive collection-IDs from the key used to encrypt their data, which will be derived from kA or kB as appropriate). It might be useful to add a checksum to kA and wrap(kB) to detect accidental corruption (e.g. store and deliver kA+SHA256(kA)), but this doesn't protect against intentional changes. We omit this checksum for now, assuming that disks will be reliable enough to let us never experience such failures.<br />
<br />
We use scrypt without a per-user salt. This is safe because the "password" input to scrypt is already diversified by the user's email address. The intention is to allow the scrypt-helper to run on anonymous data, so that an attacker who compromises this helper cannot easily learn the email addresses of the partially-stretched K1 values that it is receiving, confounding their attack.<br />
<br />
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. <br />
For /certificate/sign, we do not need request confidentiality or response confidentiality, since the client's pubkey and the resulting certificate will both be exposed over a similar SSL connection to the storage server later. And it is sufficient to rely on the response integrity provided by SSL, since the client can verify the returned certificate for itself. For the other keyserver APIs protected by HAWK, these properties are either unnecessary, or are provided by additional mechanisms.<br />
<br />
<br />
= Proof-Of-Work =<br />
<br />
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(). The server can control how much work is required.<br />
<br />
The getToken1() call looks for a "X-PiCL-PoW:" HTTP header. Most of the time, clients don't supply this header. But if the server responds to the getToken1() call with an error that indicates PoW is required, clients must create a valid PoW string and include it as the value of an "X-PiCL-PoW:" header in their next call to getToken1().<br />
<br />
The server's error message includes two parameters. The first is a "prefix string": the client's PoW string is required to begin with this prefix. The second is a "threshold hash". SHA256(PoWString) is required to be lexicographically earlier than the thresholdHashString (i.e. the numerical value of its hash must be closer to zero than the threshold). The client is expected to concatenate the prefix with a counter, then repeatedly increment the counter and hash the result until they meet the threshold, then re-submit their getToken1() request with the combined prefix+counter string in the header. If the client has spent more than e.g. 10 seconds doing this, the client should probably help the user cancel the operation and try again.<br />
<br />
When a server is under a DoS attack (either via some manual configuration tool or sensed automatically), it should start requiring valid unique X-PiCL-PoW headers. The server should initially require very little work, by using a threshold hash with just a few leading zero bits. If this is insufficient to reduce the attack volume, the threshold should be lowered, requiring even more work (from both the attacker and legitimate clients).<br />
<br />
The server should create a prefix string that contains a parseable timestamp and a random nonce (e.g. "%d-%d-" % (int(time.time()), b32encode(os.urandom(8)))). The server should also decide on a cutoff time (perhaps ten minutes ago). Each server must then maintain a table of "old PoW strings" to prevent replay attacks (these do not need to be shared among all servers: an in-RAM cache is fine).<br />
<br />
When the server receives a proposed PoW string, it first splits off the leading timestamp, and if the timestamp is older than the cutoff time, it rejects the string (either by dropping the connection, or returning a new "PoW required" error if it's feeling nice). Then it hashes the whole string and compares it against the threshold, rejecting those which fail to meet the threshold. Finally, for strings that pass the hash threshold, it checks the "old strings" table, and rejects any that appear on that list.<br />
<br />
If the PoW string makes it past all these checks, the server should add the string to the "old strings" table, then accept the request (i.e. compute an srpB value and add a session-id table entry for the request).<br />
<br />
The old-strings table check should be optimized to reject present strings quickly (i.e. if we are under attack, we should expect to see lots of duplicates of the same string, and must minimize the work we do when this occurs).<br />
<br />
The server can remove values from the old-strings table that have timestamps older than the cutoff time. The server can also discard values at other times (to avoid consuming too much memory), without losing anything but protection against resource consumption.<br />
<br />
Other notes:<br />
<br />
The server-side code for this can be deferred until we care to have a response to a DoS attack. However the client-side code for this must be present from day one, otherwise we won't be able to turn on the defense later without fear of disabling legitimate old clients.<br />
<br />
The server should perform as little work as possible before rejecting a token. Every extra CPU cycle it spends in this path is increasing the DoS attack amplification factor.<br />
<br />
The nonce in the prefix string exists to make sure that two successive clients get different prefixes, and thus do not come up with the same counter value (and inadvertently create identical strings, looking like a replay attack). If this proved annoying or expensive, we could instead obligate clients to produce their own nonce.<br />
<br />
TBD: Is this worth it? Should the PoW string go into an HTTP header? (I want it to be cheap to extract, and not clutter logs). Should the error response be a distinctive HTTP error code so our monitoring tools can easily count them? We can also use this feature to slow down online guessing attacks (i.e. trigger it either when getToken1 is called too much or when getToken2 produces too many errors). Since getToken1() includes an email address, we could also requires PoWs for some addresses (e.g. those we know to be under attack) but not others.<br />
<br />
= Glossary =<br />
<br />
This defines some of the jargon we've developed for this protocol.<br />
<br />
* data classes: each type of browser data (bookmarks, passwords, history, etc) can be assigned, by the user, to either class-A or class-B<br />
* class-A: data assigned to this class can be recovered, even if the user forgets their password, by proving control over an email address and resetting the account. It can also be read by Mozilla (since it runs the keyserver and knows kA), or by the user's IdP (by resetting the account without the user's permission).<br />
* class-B: data in this class cannot be recovered if the password is forgotten. It cannot be read by the IdP. Mozilla (via the keyserver) cannot read this data, but can attempt a brute-force dictionary attack against the password.<br />
* kA: the master key for data stored as "class-A", a 32-byte binary string. Individual encryption keys for different datatypes are derived from kA.<br />
* kB: the master key for data stored as "class-B", a 32-byte binary string.<br />
* wrap(kB): an encrypted copy of kB. The keyserver stores wrap(kB) and never sees kB itself. The client (browser) uses a key derived from the user's password to decrypt wrap(kB), obtaining the real kB.<br />
* sessionToken: a long-lived per-device token which allows the device to obtained signed BrowserID certificates for the account's identity (GUID@picl-something.org). This token remains valid until the user revokes it (either by changing their password, or triggering some kind of "revoke a specific device" or "revoke all devices" function).<br />
<br />
= Test Vectors =<br />
<br />
The following example uses a non-ASCII email address of "andré@example.org" (with an accented "e", UTF8 encoding is 616e6472c3a9406578616d706c652e6f7267) and a non-ascii password of "pässwörd" (with accents on "a" and "o", UTF8 encoding is 70c3a4737377c3b67264).<br />
<br />
These test vectors were produced by the python code in https://github.com/warner/picl-spec-crypto . The diagrams may lag behind the latest version of that code.<br />
<br />
== stretch-KDF ==<br />
<br />
email:<br />
616e6472c3a94065<br />
78616d706c652e6f<br />
7267<br />
<br />
password:<br />
70c3a4737377c3b6<br />
7264<br />
<br />
K1 (scrypt input):<br />
f84913e3d8e6d624<br />
689d0a3e9678ac8d<br />
cc79d2c2f3d96414<br />
88cd9d6ef6cd83dd<br />
<br />
K2 (scrypt output):<br />
5b82f146a6412692<br />
3e4167a0350bb181<br />
feba61f63cb17140<br />
12b19cb0be0119c5<br />
<br />
stretchedPW:<br />
c16d46c31bee242c<br />
b31f916e9e38d60b<br />
76431d3f5304549c<br />
c75ae4bc20c7108c<br />
<br />
== main-KDF ==<br />
<br />
mainSalt (normally random):<br />
00f0000000000000<br />
0000000000000000<br />
0000000000000000<br />
000000000000034d<br />
<br />
srpPW:<br />
00f9b71800ab5337<br />
d51177d8fbc682a3<br />
653fa6dae5b87628<br />
eeec43a18af59a9d<br />
<br />
unwrapBKey:<br />
6ea660be9c89ec35<br />
5397f89afb282ea0<br />
bf21095760c8c500<br />
9bbcc894155bbe2a<br />
<br />
internal x (base 10):<br />
8192518690918<br />
99580124814080709381476194749939<br />
03899664126296984459627523279550<br />
<br />
internal x (hex):<br />
b5200337cc3f3f92<br />
6cdddae0b2d31029<br />
c069936a844aff58<br />
779a545be89d0abe<br />
<br />
v (verifier as number) (base 10):<br />
114649<br />
57230405843056840989945621595830<br />
71784395917725741221739574165799<br />
54316134303691657140298181419198<br />
87853709633756255809680435884948<br />
69849281177012209169281795507853<br />
57610332070005048463659745521969<br />
83218225819721112680718485091921<br />
64608360806562626442477160609654<br />
43167308814558974899899506977051<br />
96721477608178869100211706638584<br />
53875100985456239693728258285562<br />
04889672594983678412848291529879<br />
88548996842770025110751388952323<br />
22170663943486107183421205517476<br />
84831590615660554713667726412525<br />
73641352721966728239512914666806<br />
49625530438034148797508015907639<br />
67594925530663571631035463732161<br />
30193328802116982288883318596822<br />
<br />
== SRP Verifier ==<br />
<br />
k (base 10):<br />
259003859907<br />
09503006915442163037721228467470<br />
35652616593381637186118123578112<br />
<br />
srpSalt (normally random):<br />
00f1000000000000<br />
0000000000000000<br />
0000000000000000<br />
0000000000000179<br />
<br />
srpVerifier:<br />
00173ffa0263e63c cfd6791b8ee2a40f<br />
048ec94cd95aa8a3 125726f9805e0c82<br />
83c658dc0b607fbb 25db68e68e93f265<br />
8483049c68af7e82 14c49fde2712a775<br />
b63e545160d64b00 189a86708c69657d<br />
a7a1678eda0cd79f 86b8560ebdb1ffc2<br />
21db360eab901d64 3a75bf1205070a57<br />
91230ae56466b8c3 c1eb656e19b794f1<br />
ea0d2a077b3a7553 50208ea0118fec8c<br />
4b2ec344a05c66ae 1449b32609ca7189<br />
451c259d65bd15b3 4d8729afdb5faff8<br />
af1f3437bbdc0c3d 0b069a8ab2a959c9<br />
0c5a43d42082c774 90f3afcc10ef5648<br />
625c0605cdaace6c 6fdc9e9a7e6635d6<br />
19f50af773452247 0502cab26a52a198<br />
f5b00a2798589165 07b0b4e9ef9524d6<br />
<br />
== SRP B ==<br />
<br />
private b (normally random) (base 10):<br />
1198277<br />
66042000957856349411550091527197<br />
08125226960544768993643095227800<br />
29105361555030352774505625606029<br />
71778328003253459733139844872578<br />
33596964143417216389157582554932<br />
02841499373672188315534280693274<br />
23189198736863575142046053414928<br />
39548731879043135718309539706489<br />
29157321423483527294767988835942<br />
53343430842313006332606344714480<br />
99439808861069316482621424231409<br />
08830704769167700098392968117727<br />
43420990997238759832829219109897<br />
32876428831985487823417312772399<br />
92628295469389578458363237146486<br />
38545526799188280210660508721582<br />
00403102624831815596140094933216<br />
29832845626116777080504444704039<br />
04739431335617585333671378812943<br />
<br />
private b (hex):<br />
00f3000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 000000000000000f<br />
<br />
transmitted srpB:<br />
0022ce5a7b9d8127 7172caa20b0f1efb<br />
4643b3becc535664 73959b07b790d3c3<br />
f08650d5531c19ad 30ebb67bdb481d1d<br />
9cf61bf272f84398 48fdda58a4e6abc5<br />
abb2ac496da5098d 5cbf90e29b4b110e<br />
4e2c033c70af7392 5fa37457ee13ea3e<br />
8fde4ab516dff1c2 ae8e57a6b264fb9d<br />
b637eeeae9b5e43d faba9b329d3b8770<br />
ce89888709e02627 0e474eef822436e6<br />
397562f284778673 a1a7bc12b6883d1c<br />
21fbc27ffb3dbeb8 5efda279a69a1941<br />
4969113f10451603 065f0a0126666456<br />
51dde44a52f4d8de 113e2131321df1bf<br />
4369d2585364f9e5 36c39a4dce33221b<br />
e57d50ddccb4384e 3612bbfd03a268a3<br />
6e4f7e01de651401 e108cc247db50392<br />
<br />
== SRP A ==<br />
<br />
private a (normally random) (base 10):<br />
1193346<br />
47663227291363113405741243413916<br />
43482736314616601220006703889414<br />
28162541137108417166380088052095<br />
43910927476491099816542561560345<br />
50331133015255005622124012256352<br />
06121987030570656676375703406470<br />
63422988042473190059156975005813<br />
46381864669664357382020200036915<br />
26156674010218162984912976536206<br />
14440782978764393137821956464627<br />
16314542157937343986808167341567<br />
89864323268060014089757606109012<br />
50649711198896213496068605039486<br />
22864591676298304745954690086093<br />
75374681084741884719851454277570<br />
80362211874088739962880012800917<br />
05751238004976540634839106888223<br />
63866455314898189520502368799907<br />
19946264951520393624479315579863<br />
<br />
private a (hex):<br />
00f2000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 000000000000d3d7<br />
<br />
transmitted srpA:<br />
007da76cb7e77af5 ab61f334dbd5a958<br />
513afcdf0f47ab99 271fc5f7860fe213<br />
2e5802ca79d2e5c0 64bb80a38ee08771<br />
c98a937696698d87 8d78571568c98a1c<br />
40cc6e7cb101988a 2f9ba3d65679027d<br />
4d9068cb8aad6ebf f0101bab6d52b5fd<br />
fa81d2ed48bba119 d4ecdb7f3f478bd2<br />
36d5749f2275e948 4f2d0a9259d05e49<br />
d78a23dd26c60bfb a04fd346e5146469<br />
a8c3f010a627be81 c58ded1caaef2363<br />
635a45f97ca0d895 cc92ace1d09a99d6<br />
beb6b0dc0829535c 857a419e834db128<br />
64cd6ee8a843563b 0240520ff0195735<br />
cd9d316842d5d3f8 ef7209a0bb4b54ad<br />
7374d73e79be2c39 75632de562c59647<br />
0bb27bad79c3e2fc ddf194e1666cb9fc<br />
<br />
== SRP key-agreement ==<br />
<br />
u:<br />
b284aa1064e87751 50da6b5e2147b47c<br />
a7df505bed94a6f4 bb2ad873332ad732<br />
<br />
S:<br />
0092aaf0f527906a a5e8601f5d707907<br />
a03137e1b601e04b 5a1deb02a981f4be<br />
037b39829a27dba5 0f1b27545ff2e287<br />
29c2b79dcbdd32c9 d6b20d340affab91<br />
a626a8075806c26f e39df91d0ad979f9<br />
b2ee8aad1bc783e7 097407b63bfe58d9<br />
118b9b0b2a7c5c4c debaf8e9a460f4bf<br />
6247b0da34b760a5 9fac891757ddedca<br />
f08eed823b090586 c63009b2d740cc9f<br />
5397be89a2c32cdc fe6d6251ce11e44e<br />
6ecbdd9b6d93f30e 90896d2527564c7e<br />
b9ff70aa91acc0ba c1740a11cd184ffb<br />
989554ab58117c21 96b353d70c356160<br />
100ef5f4c28d19f6 e59ea2508e8e8aac<br />
6001497c27f362ed bafb25e0f045bfdf<br />
9fb02db9c908f103 40a639fe84c31b27<br />
<br />
M1:<br />
27949ec1e0f16256<br />
33436865edb037e2<br />
3eb6bf5cb91873f2<br />
a2729373c2039008<br />
<br />
srpK:<br />
e68fd0112bfa31dc<br />
ffc8e9c96a1cbadb<br />
4c3145978ff35c73<br />
e5bf8d30bbc7499a<br />
<br />
== /auth ==<br />
<br />
srpK:<br />
e68fd0112bfa31dc<br />
ffc8e9c96a1cbadb<br />
4c3145978ff35c73<br />
e5bf8d30bbc7499a<br />
<br />
respHMACkey:<br />
6584613597ef012f<br />
f1752b7869f01d03<br />
c72547a7b7199681<br />
531d9df1991edf23<br />
<br />
respXORkey:<br />
455835926ae37a1b<br />
627bd16affbeeab6<br />
27ecc737121826ca<br />
4a2bac2c100bf417<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
plaintext:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
ciphertext:<br />
253957f10e861c7c<br />
0a12bb0193d384d9<br />
579db544666d50bd<br />
3252d6576c768a68<br />
<br />
MAC:<br />
a98c87f5769ab4cc<br />
ca3df863faeb217e<br />
b16ddc29d712b301<br />
12b446324ee806d6<br />
<br />
response:<br />
253957f10e861c7c<br />
0a12bb0193d384d9<br />
579db544666d50bd<br />
3252d6576c768a68<br />
a98c87f5769ab4cc<br />
ca3df863faeb217e<br />
b16ddc29d712b301<br />
12b446324ee806d6<br />
<br />
== /session ==<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
tokenID:<br />
6dcae8ff8f55a793<br />
a0fa1ed31115451b<br />
4df233b3a0641cc6<br />
18ecadfd1fe4a691<br />
<br />
reqHMACkey:<br />
1640a4e6bc8c8e54<br />
858be9960a8b0740<br />
fa06effdf169246f<br />
52012ae868fc6c48<br />
<br />
respHMACkey:<br />
7f3e075e74523ced<br />
fa817c2fa4ae97e1<br />
e51da38d7a992b66<br />
8a35c86af946b155<br />
<br />
respXORkey:<br />
02977a9167830705<br />
74b610cc25320262<br />
175b45fbd7b26438<br />
f9e200abc029f14e<br />
f38399314b172f1e<br />
e928fcdcd194ab19<br />
92433cab0e94569d<br />
bf623b46dd9fbf55<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
sessionToken:<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
plaintext:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
ciphertext:<br />
8216f812e3068182<br />
fc3f9a47a9bf8ced<br />
87cad7684327f2af<br />
617b9a305cb46fd1<br />
53223b92efb289b9<br />
418156777d3905b6<br />
22f28e18ba21e02a<br />
07db81fd612201ea<br />
<br />
MAC:<br />
639fd132f637abd3<br />
ecd2482ccf11ed76<br />
8cfd6979e1954046<br />
1e8ef5204e66c542<br />
<br />
response:<br />
8216f812e3068182<br />
fc3f9a47a9bf8ced<br />
87cad7684327f2af<br />
617b9a305cb46fd1<br />
53223b92efb289b9<br />
418156777d3905b6<br />
22f28e18ba21e02a<br />
07db81fd612201ea<br />
639fd132f637abd3<br />
ecd2482ccf11ed76<br />
8cfd6979e1954046<br />
1e8ef5204e66c542<br />
<br />
== /account/keys ==<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
tokenID:<br />
d010c94c753c012c<br />
d6801e8beb1aa6cc<br />
3da9ea3de3de1dee<br />
32785dbd99a579e8<br />
<br />
reqHMACkey:<br />
1707b05908acc4dc<br />
cda5b8304d9500d0<br />
8c53e00c31672a53<br />
490dfb5ef2934060<br />
<br />
respHMACkey:<br />
31d0c12186b76897<br />
c3351878a65097cf<br />
d595da4ce48e69a2<br />
485ff1a77c71b0d0<br />
<br />
respXORkey:<br />
eed35591e1f1c43b<br />
7cd604e371b9cfb7<br />
a980c9a36fa737c6<br />
a48c5d60a89fc291<br />
4ec1a2150a0777b7<br />
9a1e8499058cd17a<br />
ebc1441db8b3bf18<br />
2cd0aefa92482692<br />
<br />
kA:<br />
2021222324252627<br />
28292a2b2c2d2e2f<br />
3031323334353637<br />
38393a3b3c3d3e3f<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
plaintext:<br />
2021222324252627<br />
28292a2b2c2d2e2f<br />
3031323334353637<br />
38393a3b3c3d3e3f<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
ciphertext:<br />
cef277b2c5d4e21c<br />
54ff2ec85d94e198<br />
99b1fb905b9201f1<br />
9cb5675b94a2fcae<br />
0e80e0564e4231f0<br />
d257ced249c19f35<br />
bb90164eece6e94f<br />
7489f4a1ce1578cd<br />
<br />
MAC:<br />
86f1c57d2e7f6c97<br />
8181684e189b710f<br />
dd26a3f34e3aaed8<br />
64be9577ae81a256<br />
<br />
response:<br />
cef277b2c5d4e21c<br />
54ff2ec85d94e198<br />
99b1fb905b9201f1<br />
9cb5675b94a2fcae<br />
0e80e0564e4231f0<br />
d257ced249c19f35<br />
bb90164eece6e94f<br />
7489f4a1ce1578cd<br />
86f1c57d2e7f6c97<br />
8181684e189b710f<br />
dd26a3f34e3aaed8<br />
64be9577ae81a256<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
unwrapBKey:<br />
6ea660be9c89ec35<br />
5397f89afb282ea0<br />
bf21095760c8c500<br />
9bbcc894155bbe2a<br />
<br />
kB:<br />
2ee722fdd8ccaa72<br />
1bdeb2d1b76560ef<br />
ef705b04349d9357<br />
c3e592cf4906e075<br />
<br />
== use session (certificate/sign, etc) ==<br />
<br />
sessionToken:<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
tokenID:<br />
639503a218ffbb62<br />
983e9628be5cd64a<br />
0438d0ae81b2b9da<br />
deb900a83470bc6b<br />
<br />
reqHMACkey:<br />
3a0188943837ab22<br />
8fe74e759566d0e4<br />
837cbcc7494157aa<br />
c4da82025b2811b2<br />
<br />
== /password/change ==<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
tokenID:<br />
cafc36360afd92de<br />
5ca21800022a9af1<br />
3a5766b91bd82fd4<br />
0eaa5b6e01489796<br />
<br />
reqHMACkey:<br />
b07c0cf4553e44ff<br />
fe991caa2546b50d<br />
895fb9ac8f8746d2<br />
d29119d9616de193<br />
<br />
respHMACkey:<br />
d2ddfefd1913fa34<br />
48e18abda9b54c92<br />
43fd51bf14dc9091<br />
2179269c0e958a04<br />
<br />
respXORkey:<br />
dcc5425e13b876ea<br />
f1d3aa95a4735622<br />
46994088d86adb5a<br />
526d9f1f5d170254<br />
456dd26dcc54483e<br />
f489d55097b69028<br />
8826f0cf1985a6ad<br />
e3e83461517c8d49<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
accountResetToken:<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
plaintext:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
ciphertext:<br />
5c44c0dd973df06d<br />
795a201e28fed8ad<br />
d608d21b4cff4dcd<br />
caf40584c18a9ccb<br />
85ac10ae08918ef9<br />
3c401f9b5b7b5ee7<br />
58f7221ccd50707a<br />
3b31eeba8da15396<br />
<br />
MAC:<br />
cc3053fe922268d7<br />
9c0dd6eb74bd40f5<br />
07ae2d587483b864<br />
8ef771b699dd39d9<br />
<br />
response:<br />
5c44c0dd973df06d<br />
795a201e28fed8ad<br />
d608d21b4cff4dcd<br />
caf40584c18a9ccb<br />
85ac10ae08918ef9<br />
3c401f9b5b7b5ee7<br />
58f7221ccd50707a<br />
3b31eeba8da15396<br />
cc3053fe922268d7<br />
9c0dd6eb74bd40f5<br />
07ae2d587483b864<br />
8ef771b699dd39d9<br />
<br />
== /account/reset ==<br />
<br />
accountResetToken:<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
tokenID:<br />
a6857e5d53d35073<br />
d50ef2ce2c4dd747<br />
32bb2eae1af5bf79<br />
618ed945e1310792<br />
<br />
reqHMACkey:<br />
47fab27352ee6b48<br />
33938d76519bbdb8<br />
ac7293f8b5e74335<br />
6fdd1d5edf39f52d<br />
<br />
reqXORkey:<br />
82ed612313a11673 95108d7d379b2029<br />
7a539ce9d3861e95 1bf5a9b9cdbfb332<br />
bd6aba056ce0c568 2c5a93963446b1b4<br />
7397c8c24f3a1d67 2a0ddc856474f5b1<br />
33ab884ce33335c1 5578a1a7302933cb<br />
458fbee0a5e52414 c914beb97568a30c<br />
28364dc8fb03ae7c 76a2f324a9a1cee6<br />
71b74aa8906d0e03 39fb52a1bf2b1ef5<br />
ab5d883295db62af 20701cb3af42a09e<br />
c76cda585ab5644b 7250ef7b780537e5<br />
b3e784d37a118bd6 57a0fe29ec6e5cd3<br />
325be8e1d8a3dd71 b360ea266757e463<br />
ada6b0a7a85a8ac0 eed618d9f6ee91ab<br />
1d2f714f224d67db 46843c4e3339de15<br />
efe0297a45f9fe0d 6d768b5c589a290f<br />
11f03237192cc0a3 a02645a810d83bb1<br />
84d582bfb15d2393 3fa4805374da62c6<br />
a2c887b157285c6a 79b47156c9abe02e<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
newSRPv:<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
<br />
plaintext:<br />
4041424344454647 48494a4b4c4d4e4f<br />
5051525354555657 58595a5b5c5d5e5f<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
<br />
ciphertext:<br />
c2ac236057e45034 dd59c7367bd66e66<br />
2a02ceba87d348c2 43acf3e291e2ed6d<br />
ac7bab147df1d479 3d4b82872557a0a5<br />
6286d9d35e2b0c76 3b1ccd947565e4a0<br />
22ba995df22224d0 4469b0b6213822da<br />
549eaff1b4f43505 d805afa86479b21d<br />
39275cd9ea12bf6d 67b3e235b8b0dff7<br />
60a65bb9817c1f12 28ea43b0ae3a0fe4<br />
ba4c992384ca73be 31610da2be53b18f<br />
d67dcb494ba4755a 6341fe6a691426f4<br />
a2f695c26b009ac7 46b1ef38fd7f4dc2<br />
234af9f0c9b2cc60 a271fb377646f572<br />
bcb7a1b6b94b9bd1 ffc709c8e7ff80ba<br />
0c3e605e335c76ca 57952d5f2228cf04<br />
fef1386b54e8ef1c 7c679a4d498b381e<br />
00e12326083dd1b2 b13754b901c92aa0<br />
95c493aea04c3282 2eb5914265cb73d7<br />
b3d996a046394d7b 68a56047d8baf13f<br />
<br />
== /account/destroy ==<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
tokenID:<br />
b2512ff41c4e6d8a<br />
beb3bda37e326f51<br />
cf4efdbf90e50e77<br />
029be2563884b9fe<br />
<br />
reqHMACkey:<br />
75cfa782c19e41f9<br />
c7e125f3dc4c3bf1<br />
0a77c93a9999e06f<br />
b2646b3038e4ea44<br />
<br />
= Keyserver Protocol Summary =<br />
<br />
* POST /account/create (email,srpV,srpSalt) -> ok (server sends verification email)<br />
** creates a user account<br />
* GET /account/devices [sessionToken] () -> list of devices<br />
* GET /account/keys [keyFetchToken,needs-verf] () -> kA/wrap(kB)<br />
** single-use, only if email is verified, encrypted results<br />
* POST /account/reset [authed+encrypted by accountResetToken] (wrap(kB),srpV,srpSalt) -> ok<br />
** single-use, does not require email to be verified, revoke all tokens for account, send notification email to user<br />
* POST /account/delete [authToken] () -> ok, account deleted<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authToken] () -> keyFetchToken, sessionToken<br />
* POST /session/destroy [sessionToken] () -> ok<br />
** for detaching a device, destroy all tokens<br />
* POST /recovery_email/status [sessionToken] () -> "verified" status of email<br />
** use "Accept: text/event-stream" header for server-sent-events; server will send "update" event with the new content of the resource any time it changes.<br />
* POST /recovery_email/resend_code [sessionToken] () -> re-send verification email<br />
* POST /recovery_email/verify_code (code) -> set "verified" flag<br />
** this code will come from a clickable link and is an unauthenticated endpoint<br />
** this could maybe take the recovery method if that would be helpful<br />
** sets verified flag on recovery method<br />
* POST /certificate/sign [sessionToken,needs-verf] (pubkey) -> cert<br />
** only if recovery email is verified<br />
* POST /password/change/start [authToken,needs-verf] () -> accountResetToken, keyFetchToken<br />
* POST /password/forgot/send_code () -> forgotPasswordToken<br />
** sends code to recovery method (email for now, maybe SMS later)<br />
** this is a short code, not a clickable link<br />
* POST /password/forgot/resend_code (forgotPasswordToken) -> re-sends code<br />
* POST /password/forgot/verify_code (forgotPasswordToken, code) -> accountResetToken<br />
** sets verified flag on recovery method<br />
* POST /get_random_bytes<br />
<br />
<br />
== Typical Client Flows ==<br />
<br />
Create account<br />
<br />
* POST /account/create (email,srpV,srpSalt) -> ok (server sends verification email)<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authed with authToken]() -> keyFetchToken, sessionToken<br />
* GET /recovery_email/status [sessionToken] () -> "verified" status<br />
** (optional, only if user requests resend) POST /recovery_email/resend_code [sessionToken]() -> ok<br />
** POST /recovery_email/verify_code (code) -> ok<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
* POST /certificate/sign [sessionToken] (pubkey) -> cert<br />
<br />
<br />
Attach to new device<br />
<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authToken] () -> keyFetchToken, sessionToken<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
** (if unverified-error, do waitUntilEmailVerified, then try again)<br />
* POST /certificate/sign [sessionToken] (pubkey) -> cert<br />
<br />
<br />
Forgot password<br />
<br />
* POST /password/forgot/send_code (email) -> forgotPasswordToken<br />
* POST /password/forgot/verify_code (forgotPasswordToken, code) -> accountResetToken<br />
* POST /account/reset [authed+encrypted by accountResetToken] (0000,srpV,srpSalt) -> ok<br />
* GOTO "Attach to new device"<br />
<br />
<br />
Change Password<br />
<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /password/change/start [authToken] () -> accountResetToken, keyFetchToken<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
* POST /account/reset [authed+encrypted by accountResetToken] (wrap(kB),srpV,srpSalt) -> ok<br />
* GOTO "Attach to new device"</div>Warnerhttps://wiki.mozilla.org/index.php?title=File:PICL-IdPAuth-deleteAccount.png&diff=692204File:PICL-IdPAuth-deleteAccount.png2013-08-08T18:57:02Z<p>Warner: Warner uploaded a new version of &quot;File:PICL-IdPAuth-deleteAccount.png&quot;</p>
<hr />
<div></div>Warnerhttps://wiki.mozilla.org/index.php?title=File:PICL-IdPAuth-encrypt-passwordChange.png&diff=692199File:PICL-IdPAuth-encrypt-passwordChange.png2013-08-08T18:55:52Z<p>Warner: Warner uploaded a new version of &quot;File:PICL-IdPAuth-encrypt-passwordChange.png&quot;</p>
<hr />
<div></div>Warnerhttps://wiki.mozilla.org/index.php?title=File:PICL-IdPAuth-encrypt-sessionToken.png&diff=692198File:PICL-IdPAuth-encrypt-sessionToken.png2013-08-08T18:54:28Z<p>Warner: Warner uploaded a new version of &quot;File:PICL-IdPAuth-encrypt-sessionToken.png&quot;</p>
<hr />
<div></div>Warnerhttps://wiki.mozilla.org/index.php?title=Identity/AttachedServices/KeyServerProtocol&diff=691409Identity/AttachedServices/KeyServerProtocol2013-08-07T18:47:07Z<p>Warner: /* Obtaining keys kA and kB */</p>
<hr />
<div>= PiCL Key Server / IdP Protocol =<br />
<br />
NOTE: This specification is slowly converging on stability (31-Jul-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.<br />
<br />
The server is being developed in https://github.com/mozilla/picl-idp . This repo currently includes a demonstration client (node.js CLI).<br />
<br />
Note that all messages are delivered over an HTTPS connection. The client browser may also implement cert-pinning to improve on the certificate validation process. The protections described below are in addition to those provided by TLS.<br />
<br />
Remaining TODO items:<br />
<br />
* decide on client-side key-stretching parameters: http://keywrapping.appspot.com can help<br />
* finalize SRP questions (definition of M1, generation of a/b)<br />
* finalize proof-of-work/DoS-prevention details<br />
* decide how to rate-limit account-creation calls<br />
* confirm this is actually implementable inside Firefox (especially w.r.t. NSS and Android/Java crypto)<br />
<br />
= Creating The Account =<br />
<br />
The first act performed by a user is to create the account. They enter email+password into their browser, which then does the following steps:<br />
<br />
* decide upon stretching parameters (perhaps consulting the keyserver for recommendations, but imposing a minimum strength requirement). For now we used a fixed {firstPBKDF:20000, scrypt: {N:65536, r:8, p:1}, secondPBKDF:20000}.<br />
* randomly choose a 32-byte mainSalt (this should be unique, but is not secret)<br />
* choose the SRP group parameters (fixed: use the 2048-bit group described below)<br />
* perform key-stretching (as described below), derive stretchedPW and srpPW<br />
* randomly choose a 32-byte srpSalt (unique, but not secret)<br />
* create srpVerifier from srpPW and srpSalt (as described below)<br />
* deliver (email, stretchParams, mainSalt, srpParams, srpSalt) to the keyserver's "POST /account/create" API<br />
<br />
<br />
To limit abuse, the createAccount() should also require a fresh "createToken". This should be created by some other API, outside the scope of this document, that perhaps requires a CAPTCHA or something. createAccount() might also require a proof-of-work token, as described below.<br />
<br />
The server, when creating a new account, creates both kA and wrap(kB) as randomly-generated 256-bit (32-byte) strings. It stores these, along with all the remaining values, indexed by email, in the account table where they can be retrieved by getToken later.<br />
<br />
== Email Verification ==<br />
<br />
To prevent fixation attacks, we require new accounts to verify their configured recovery email address before letting them learn the generated keys or obtain a signed certificate. Nevertheless, we wish clients to forget the user's password while they wait for email verification to complete. To achieve this, clients can obtain a sessionToken before verification, but most APIs that require it will raise errors until verification is finished.<br />
<br />
The server will send email with a URL that contains a long random "verification code" in the "fragment" hash. This URL points to a static page with some javascript that submits the code to the "POST /account/recovery_methods/verify_code" API. The URL can be clicked by any browser (it is not bound to anything), and when the API is hit, the account is marked as verified.<br />
<br />
After the client submits /account/create, it performs the "/session/auth" login sequence below to obtain a sessionToken. It then polls the "GET /account/recovery_methods" (which requires a sessionToken but not account verification) until the user clicks the email link and the API reports verification is complete. Then the client uses "GET /account/keys" and "POST /certificate/sign", described below, to obtain kA, kB, and a signed certificate to talk to the storage server.<br />
<br />
= Login: Obtaining the authToken =<br />
<br />
To connect a browser to an existing account, we use the following login protocol to transform an email+password pair into a single-use authToken. We will use this in the next section to create a session, from which we can obtain encryption keys and signed certificates.<br />
<br />
This protocol starts by using key-stretching to transform the email+password into a "stretchedPW", then feeds this into an SRP protocol to get the authToken.<br />
<br />
[[File:PICL-IdPAuth-auth-start.png|IdP Auth Protocol]]<br />
<br />
This authToken can be used (once) to do one of the following:<br />
<br />
* /session/create: obtain a sessionToken (and keyFetchToken), which enables storage server access<br />
* /password/change: obtain an accountResetToken, to safely reset the account password<br />
* /account/delete: to delete the entire account<br />
<br />
The protocol is designed to enable parallelism between key-stretching and the initial network messages, to reduce the time it takes to connect a browser to the account. In total, the browser requires five messages in four roundtrips (1: /auth/start, 2: /auth/finish, 3: /session/create, 4: /account/keys and /certificate/sign in parallel) before it is ready to talk to the storage server.<br />
<br />
== auth/start ==<br />
<br />
As soon as the user finishes typing in the email address, the client should send it in the "/auth/start" 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 loginSRPToken that is used to associate this request with the subsequent /auth/finish 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.<br />
<br />
To mitigate DoS abuse, /auth/start may also require a proof-of-work string, described below.<br />
<br />
== Client-Side Key Stretching ==<br />
<br />
"Key Stretching" is the practice of running a password through a computationally-expensive one-way function before using it for encryption or authentication. The goal is to make brute-force dictionary attacks more expensive, by raising the cost of testing each guess.<br />
<br />
To protect the user's class-B data against compromises of our keyserver, we perform this key stretching on the client, rather than on the server. We use the memory-hard "scrypt" function (pronounced "ess-crypt") for this purpose, as motivated by the attacker-cost studies in [[Identity/CryptoIdeas/01-PBKDF-scrypt]]. Slower (low-RAM) devices can still participate by outsourcing the scrypt step to an "scrypt helper". To provide at least minimal protection against a compromise of this helper, we sandwich the scrypt step between two PBKDF2 steps. The complete protocol looks like this:<br />
<br />
[[File:PICL-IdPAuth-stretch-KDF.png|Stretching KDF]]<br />
<br />
Our initial parameter choices for this stretching are to use 20000 iterations of PBKDF2 on each side (40k in all), and to run scrypt with N/r/p set to 64k/8/1. This requires roughly 60MB of RAM for about 1.3 seconds on a 1.8GHz Intel Atom linux box (it will run faster, but use the same memory, on more modern CPUs). We are still studying performance on various platforms to decide what parameters to use in V1: lowering them speeds up the login process, but reduces security (by reducing the cost of an dictionary attack).<br />
<br />
Since the stretching is expected to take a second or two, the client can optimistically start this process (using default parameters) before receiving the auth/start 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 "mainSalt is added *after* the stretching, to enable this parallelism (at a tiny cost in security).<br />
<br />
After "stretchedPW" is derived, a second HKDF call is used to derive "srpPW" and "unwrapBKey" which will be used later.<br />
<br />
[[File:PICL-IdPAuth-main-KDF.png|masterKey KDF]]<br />
<br />
== SRP Protocol Details ==<br />
<br />
The PiCL client uses the SRP protocol (http://srp.stanford.edu/) to prove that it knows the (stretched) account password without revealing the actual password (or information enabling a brute-force attack) to the server or any eavesdroppers. <br />
<br />
SRP is somewhat underspecified. We use SRP-6a, with SHA256 as the hash, and the 2048-bit modulus defined in RFC 5053 Appendix A. We consistently zero-pad all string values to 256 bytes (2048 bits), and use H(A+B+S) as the key-confirmation message "M1". These details, plus the SRP design papers and RFCs 2945 and 5054, should be enough to build a compatible implementation. The diagrams below and the test vectors at the end of this page can be used to verify compatibility.<br />
<br />
The server should use Jed's SRP module from https://github.com/jedp/node-srp . The client might use SJCL (http://crypto.stanford.edu/sjcl/) or native code (NSS).<br />
<br />
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).<br />
<br />
Note that SRP-6a uses a "k" value which basically encodes the group being used ("N" and "g"). Since all PICL accounts use the same 2048-bit group, they will all use the same "k" value (not to be confused with the per-session shared-secret "K" key that emerges from the protocol). This group's "k" integer is (as a base-10 number):<br />
259003859907<br />
09503006915442163037721228467470<br />
35652616593381637186118123578112<br />
<br />
=== SRP Verifier Calculation ===<br />
<br />
When the client first creates the account, it must combine the account email address, the stretched password (srpPW), and a randomly-generated srpSalt, to compute the srpVerifier. The server will use this verifier later, to check whether or not the client really knows the password.<br />
<br />
If the server is compromised and an attacker learns the srpVerifier for a given account, they cannot use this to directly log in (the verifier is not "password-equivalent"), but it does allow them to perform an offline brute-force attack against the user's password. In this respect, it is similar to a traditional hashed password. We make these attacks somewhat more expensive by performing the client-side stretching described above, instead of using the raw user password in the SRP calculation.<br />
<br />
[[File:PICL-IdPAuth-SRP-Verifier.png|client-side SRP Verifier calculation]]<br />
<br />
Test vectors are provided at the end of this page. The protocol requires that several values ("a", "b", "srpSalt") are chosen randomly, but for illustrative purposes, the test vectors use carefully-crafted non-random values. For the user's sake, please ensure implementations use proper random values instead of simply copying the test vectors.<br />
<br />
The client sends srpSalt and srpVerifier to the server when it creates the account. It will also re-compute the 'x' value (as an integer) during sign-in. The server will convert the srpVerifier string back into an integer ('v') for use during its own sign-in calculations.<br />
<br />
=== SRP Server-side Sign-In Flow ===<br />
<br />
When the user connects a new device to their account, they use the /auth/start API to start the SRP protocol. This sends the account email address to the server. The server looks up the stored srpVerifier for this account, creates a random 'b' integer, performs some math to compute the "B" number, then converts B into a string known as "srpB". "srpB" is returned to the client, along with srpSalt and the key-stretching parameters. "b" and "srpB" are retained for the subsequent /auth/finish call. '''Note that it is critical that the "b" integer remain secret on the server.''' The server also allocates a random loginSRPToken to connect the two calls.<br />
<br />
[[File:PICL-IdPAuth-SRP-Server.png|server-side SRP]]<br />
<br />
Later, /auth/start will be called with the client's srpA string and its M1 key-confirmation string. srpA is combined with srpB to calculate the "u" integer. srpA is also turned into an integer and used to compute the shared-secret "S" integer. S is then used to compute the shared key "srpK", which is the output of the SRP process.<br />
<br />
=== SRP Client Calculation ===<br />
<br />
While the client is waiting for the response to /auth/start, it begins its key-stretching calculations. Everything else must wait until the response to /auth/start arrives, which includes the key-stretching parameters (which are retroactively confirmed), srpSalt, and the server's generated srpB value.<br />
<br />
[[File:PICL-IdPAuth-SRP-Client.png|client-side SRP]]<br />
<br />
Once the client knows srpSalt, it computes the same "x" integer as it did in the middle of the srpVerifier calculation. It also converts srpB into an integer named "B". Then it creates a random "a" integer, uses it to compute the string "srpA", then combines srpA with the server's srpB to compute the "u" integer. It then combines the static "k", the password-derived "x", the combined "u", and the server's "B", together with some magic math, to derive the "S" integer. If everything went well, the client will compute the same "S" value as the server did. If not (the password was wrong, or the client is talking to a fake server that doesn't really know srpVerifier), then the two "S" values will not match.<br />
<br />
('''Again, it is critical that the client keep its "a" and "x" integers secret, both during and after the protocol run.''')<br />
<br />
To safely tell if the "S" values match, both client and server combine srpA, srpB, and their (independently) generated "S" strings to form a string named "M1". The client sends M1 (along with srpA) in the /session/auth/finish message. The server compares the client's copy of M1 against its own. If they match, the client knew the password and the server can safely respond with the encrypted account data. If they do not match, the client (or a man-in-the-middle attacker) did not know the password, and the client should increment a counter that can trigger defenses against online guessing attacks. The server must then return an error to the client, and '''not''' use or reveal srpK (or the correct M1) in any way.<br />
<br />
Both client and server also hash "S" into "srpK". This is the shared session key, from which specific message encryption and MAC keys are derived (as described below).<br />
<br />
=== SRP Notes ===<br />
<br />
The SRP "g" (generator) and "N" (prime modulus) should use the 2048-bit value from RFC 5054 Appendix A. 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.<br />
<br />
There are several places in SRP where integers (usually in the range 1..N-1) are converted into bytestrings, either for transmission over a wire, or to be passed into a hash function. The SRP spec is somewhat ambiguous about padding here: if the integer happens to be less than 2^2040, the simplest toString() approach will yield a '''255''' byte string, not a 256 byte string. PiCL consistently uses padding, so compatible implementations must prepend one or more NUL bytes to these short strings before transmission or hashing. The examples above were brute-forced to ensure that "srpVerifier", "srpA", "srpB", and "S" all wind up with leading zeros, to exercise the padding code in compatible implementations. If you are having problems getting your code to match these results, add some assertions to test that the stringified integers being put into hashes are exactly 256 bytes long.<br />
<br />
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 (/auth/finish).<br />
<br />
The server receives "A" in /auth/finish, 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 keyFetchToken+sessionToken as described below, returning the encrypted/MACed bundle in the response to /auth/finish.<br />
<br />
Outstanding crypto questions:<br />
<br />
* How exactly should the "a" and "b" integers be generated? The issue is of how much bias SRP can tolerate. Ideally these integers are uniformly distributed from 1 to N-1 (inclusive). The only way to obtain a purely uniform distribution from a source of random bytes is try-try-again: pick a (integral-number-of-bytes) number, compare it to the desired range, try again if it falls outside the range. If you get unlucky, this can take a lot of guesses, depending upon how close the range is to a power of two. ECDSA implementations tend to pick a number twice as long as the modulus and then modulo it down (rand(2^4096) % N), which yields a tiny fraction of a bit of bias. The cheaper approach is to do the same with a number of equal length (rand(2^2048) % N), which imposes more bias. Some SRP implementations appear to be satisfied by rand(2^256). We need more review here.<br />
* The original SRP papers defined M1=H(A+B+S) as we use here, but other implementation (in particular RFC2945) uses a curious construct that includes the username, the salt, and an odd XOR combination of N and g. We need to decide what to use for M1.<br />
<br />
== /auth/finish ==<br />
<br />
The client-side SRP calculation results in two values that are sent to the server in the /auth/finish 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.<br />
<br />
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.<br />
<br />
The server then allocates a single-use 32-byte random token named "authToken". It encrypts the token with the session key, and returns a success message with the encrypted bundle.<br />
<br />
All tokens have an associated tokenID, described below. The server needs to maintain a table that maps the tokenID to the token itself, so it can derive other values from the token later. The tokens are also associated with a specific account, so later API requests do not specify an email address or account ID.<br />
<br />
== Decrypting the /auth/finish Response ==<br />
<br />
The SRP session key ("srpK") is used to derive two other keys: respHMACkey and respXORkey.<br />
<br />
[[File:PICL-IdPAuth-encrypt-authToken.png|Decrypting the authToken]]<br />
<br />
The respXORkey is used to encrypt the authToken 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.<br />
<br />
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 returns the authToken value.<br />
<br />
= Creating a Session =<br />
<br />
For login, the single-use authToken is spent on a call to /session/create .<br />
This allocates two new (random 32-byte) tokens: a long-lived "sessionToken", and a single-use "keyFetchToken". The /session/create call returns an encrypted bundle containing the two tokens.<br />
<br />
[[File:PICL-IdPAuth-encrypt-sessionToken.png|Decrypting the sessionToken and keyFetchToken]]<br />
<br />
The authToken is used to derive four values:<br />
<br />
* tokenID<br />
* reqHMACkey<br />
* respHMACkey<br />
* respXORkey<br />
<br />
The client uses tokenID and reqHMACkey for a HAWK (https://github.com/hueniverse/hawk/) request to the "POST /session/create" API, using tokenID as "credentials.id" and reqHMACkey as "credentials.key". The server uses tokenID to look up the corresponding token, then derives reqHMACkey to validate the request. The server allocates sessionToken and keyFetchToken, concatenates them, encrypts the pair by XORing it with the derived respXORkey, and attaches a MAC generated with respHMACkey. The encrypted MACed bundle is returned to the client.<br />
<br />
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 keyFetchToken and sessionToken values.<br />
<br />
Each authToken is single-use: once a successful request has been made with it, the authToken and its corresponding ID is removed from the server's memory, and subsequent attempts to use it will return a "no such token" error. The token is consumed even if the request fails (e.g. the MAC did not match).<br />
<br />
Since the authToken can be used by multiple APIs, the server ought to maintain a table that maps the various flavors of tokenIDs (computed for the different APIs) back to the authToken. When a HAWK request with one of these IDs appears, it should look up the tokenID in the corresponding table, retrieve the authToken, compute the associated values (reqHMACkey, etc), create the response, then delete the entire row.<br />
<br />
The server can support multiple sessions per account (typically one per client device, plus perhaps others for account-management portals). There can also be multiple outstanding keyFetchTokens. The sessionToken lasts forever (until revoked by a password change or explicit revocation command), and can be used an unlimited number of times. The keyFetchToken expires after 60 seconds, and is single-use.<br />
<br />
= Obtaining keys kA and kB =<br />
<br />
Clients which have exchanged an authToken for either a sessionToken or an accountResetToken will also receive a keyFetchToken. This single-use token allows the client to retrieve kA and wrap(kB), which enables the client to encrypt and decrypt browser data (bookmarks, open-tabs, etc) correctly. As above, the keyFetchToken is used to derive tokenID, reqHMACkey, respHMACkey, and respXORkey, which are used in a HAWK request to the "GET /account/keys" API.<br />
<br />
The server pulls kA and wrap(kB) from the account table, concatenates them, encrypts the pair by XORing it with the derived respXORkey, and attaches a MAC generated with respHMACkey.<br />
<br />
[[File:PICL-IdPAuth-keys-server.png|keyFetchToken: server encrypts keys]]<br />
<br />
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 and wrap(kB) values.<br />
<br />
[[File:PICL-IdPAuth-keys-client.png|keyFetchToken: client decrypts keys]]<br />
<br />
Finally, the server-provided wrap(kB) value is simply XORed with the password-derived unwrapBKey (both are 32-byte strings) to obtain kB. There is no MAC on wrap(kB).<br />
<br />
[[File:PICL-IdPAuth-key-unwrap.png|unwrapping kB]]<br />
<br />
"kA" and "kB" enable the browser to encrypt/decrypt synchronized data records. They will be used to derive separate encryption and HMAC keys for each data collection (bookmarks, form-fill data, saved-password, open-tabs, etc). This will allow the user to share some data, but not everything, with a third party. The client may intentionally forget kA and kB (only retaining the derived keys) to reduce the power available to someone who steals their device.<br />
<br />
Note that /account/keys will not succeed until the account's email address has been verified. Also note that each keyFetchToken is single-use and short-lived. The token is consumed even if the request fails (e.g. the MAC does not match).<br />
<br />
= Signing Certificates =<br />
<br />
The sessionToken is used to derive two values:<br />
<br />
* tokenID<br />
* request HMAC key<br />
<br />
<br />
[[File:PICL-IdPAuth-use-session.png|Using the sessionToken, signing certificates]]<br />
<br />
The requestHMACkey is used in a HAWK request to provide integrity over many APIs, including /certificate/sign. requestHMACkey 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.<br />
<br />
For /certificate/sign, it is critical to enable payload verification by setting options.payload=true (on both client and server). Otherwise a man-in-the-middle could submit their own public key, get it signed, and then delete the user's data on the storage servers.<br />
<br />
The following keyserver APIs require a HAWK-protected request that uses the sessionToken. In addition, some require that the account be in the "verified" state:<br />
<br />
* GET /account/devices<br />
* POST /session/destroy<br />
* GET /recovery_email/status<br />
* POST /recovery_email/resend_code<br />
* POST /certificate/sign (requires "verified" account)<br />
<br />
= Resetting The Account =<br />
<br />
The account may be reset in two circumstances: when the user changes their password, and when the user forgets their password. In both cases, the client first obtains an "accountResetToken". This token is then used to change the SRP Verifier and either reset or replace the wrap(kB) value.<br />
<br />
== Changing the Password ==<br />
<br />
When the user wishes to change their password (i.e. they still know the old password), they first use the /auth/start+/auth/finish SRP protocol safely obtain an "authToken". They they use the "/password/change/start" API to exchange the authToken for an accountResetToken and a keyFetchToken.<br />
<br />
[[File:PICL-IdPAuth-encrypt-passwordChange.png|Server encrypts passwordChange response]]<br />
<br />
The accountResetToken will be used below to set the new password safely. The keyFetchToken should be used first, to obtain kB, so the subsequent account reset can replace wrap(kB) with a new value. This allows the password-changing client to retain their class-B data.<br />
<br />
Requiring an authToken proves that the user has provided the correct account password recently. When the account is reset, all active sessions and tokens will be cancelled (disconnecting all devices from the account). The client should immediately establish a new session as described above.<br />
<br />
This API is only used when the user knows their old password: if they have forgotten the password, use the "/password/forgot" APIs below.<br />
<br />
== Handling a Forgotten Password ==<br />
<br />
When the user has forgotten their password, they can use one of their "recovery methods" to obtain an accountResetToken. For now, this means we send a random code to the email address associated with their account. The user must copy this code from the email into their client, whereupon the client will get an accountResetToken that can be delivered to the API below.<br />
<br />
Note that, since the forgotten-password client never learns kB, any class-B data will be lost. This is necessary to protect class-B data from attackers who can read the users's email but do not know the account password (including those who compromise the IdP and the keyserver itself). When using /account/reset below, they will set wrap(kB) to a string of all zeros, which means the server should generate a new random wrap(kB) (just as it does during account creation).<br />
<br />
The /password/forgot/send_code API is used to ask the server to send a recovery code. This takes a recovery method, which for now is just an email address. This API is unauthenticated (after all, the user who has forgotten their password knows nothing but their email address). The server marks the corresponding account as "pending recovery", allocates a random forgotPasswordToken for the account, creates a recovery code, and sends the code (with instructions) via email. The API returns forgotPasswordToken to the client.<br />
<br />
The user must copy the recovery code into the same browser where they started the process. The client then submits the code to /password/forgot/verify_code along with the forgotPasswordToken they received. If they match, the server allocates a accountResetToken and returns it to the client. If they do not match, the server increments a counter (which is used to decide if an online guessing attack is happening).<br />
<br />
forgotPasswordToken can be used three times before it is exhausted. If the user guesses incorrectly this often, the client must call send_code again to get a new token and code. Each account has at most one token+code active at a time.<br />
<br />
The recovery code is initially a random 8-digit decimal number. If an attacker tries to sign in as someone else, hits the "forgot my password" button, then submits a guess to /password/forgot/verify_code, they will have a 1-in-100-million chance of success. If the server detects too many wrong guesses, it should increase the length of new codes. Another defensive technique is to require that users click an email link before being given the code: the server is told when the link is clicked, so the code will not be enabled until the email has been read. It remains to be seen whether this will be sufficient.<br />
<br />
The exact thresholds are TBD, but a nominal goal is to keep the chances of any attack succeeding to below 1-in-a-million per year. To achieve this, we can tolerate 100 verify_code failures in a single year before we must increase the length of the code.<br />
<br />
== Using accountResetToken ==<br />
<br />
An accountResetToken, obtained through either of the methods above, is used to invoke the /account/reset API. If the request is accepted, the server replaces the account password with a new one, updates the wrap(kB) value, cancels all active sessions and tokens (disconnecting all devices from the account), and sends a "your password has been changed" email to the user.<br />
<br />
If the client knew the old password, it can supply a new wrap(kB) value that will yield the same kB key as before, so no data will be lost. If the client did *not* know the old password, then it will supply a wrap(kB) of all-zeros, which tells the server to generate a new random wrap(kB) (just like during account creation), which means the class-B data will be lost.<br />
<br />
The client puts their new password through the same stretching procedure as described in the new-account section above, resulting in a new srpVerifier and unwrapBKey. If they knew the old kB, they XOR it with the new unwrapBKey to obtain a new wrap(kB).<br />
<br />
/account/reset needs request confidentiality, since the arguments include the newly wrapped kB value and the new SRP verifier, both of which enable a brute-force attack against the password. HAWK provides request integrity. The response is a single "ok" or "fail", conveyed by the HTTP headers, so we do not require response confidentiality, and can live without response integrity.<br />
<br />
So the single-use resetToken is used to derive three values:<br />
<br />
* tokenID<br />
* request HMAC key<br />
* request XOR key<br />
<br />
<br />
[[File:PICL-IdPAuth-encrypt-resetAccount.png|Client encrypts resetAccount request]]<br />
<br />
The request data will contain wrap(kB) and the new SRP verifier, concatenated together. Since we always pad the SRP verifier to the full (256-byte) group length, both pieces are fixed-length. We generate enough reqXORkey bytes to cover both values.<br />
<br />
The request data is XORed with requestXORkey, then delivered in the body of a HAWK request that uses tokenID as credentials.id and requestHMACkey as credentials.key . Note: it is critical to include the request body in the HAWK integrity check (options.payload=true, on both client and server), otherwise a man-in-the-middle could substitute their own SRP verifier, giving them control over the account (access to the user's class-A data, and a brute-force attack on their password).<br />
<br />
The client submits other values in the same request:<br />
<br />
* stretchParams<br />
* mainKDFSalt<br />
* srpSalt<br />
<br />
<br />
These values do not require confidentiality, so are not included in the encrypted bundle. They are still protected by the HAWK integrity check. Note that the server should assert that both salts are different than the previously stored values.<br />
<br />
After using /account/reset, clients should immediately perform the login protocol from above. If the old password was forgotten, this is necessary to fetch kA. In either case, a new sessionToken is required, since old sessions and tokens are revoked by /account/reset. Clients should retain the new srpPassword value during this process to avoid needing to run the lengthy key-stretching routine a second time.<br />
<br />
= Deleting The Account =<br />
<br />
When the user wishes to completely delete their account, the browser needs to perform two actions:<br />
<br />
* contact the storage servers and delete all records and collections<br />
* contact the keyserver and delete the account information<br />
<br />
The user should be prompted for their password as confirmation (i.e. a browser in the normal attached-and-synchronizing state should not be able to erase the account information: it must acquire a new authToken first).<br />
<br />
The device then obtains an authToken as described above, then spends it on a HAWK-protected request to the /account/destroy endpoint. This request contains no body and returns only a success code.<br />
<br />
[[File:PICL-IdPAuth-deleteAccount.png|Deleting the Account]]<br />
<br />
= Crypto Notes =<br />
<br />
Strong entropy is needed in the following places:<br />
<br />
* (client) creation of private "a" value inside SRP<br />
* (server) initial creation of kA and wrap(kB)<br />
* (server) creation of private "B" value inside SRP<br />
* (server) creation of signToken and resetToken<br />
<br />
<br />
On the server, code should get entropy from /dev/urandom via a function that uses it, like "crypto.randomBytes()" in node.js or "os.urandom()" in python. On the client, code should combine local entropy with some fetched from the keyserver via getEntropy(), to guard against failures in the local entropy pool. Something like HKDF(SKM=localEntropy+remoteEntropy, salt="", context=KW("mergeEntropy")).<br />
<br />
An HKDF-based stream cipher is used to protect the response for getToken2(), and the request for resetAccount(). HKDF is used to create a number of random bytes equal to the length of the message, then these are XORed with the plaintext to produce the ciphertext. An HMAC is then computed from the ciphertext, to protect the integrity of the message.<br />
<br />
HKDF, like any KDF, is defined to produce output that is indistinguishable from random data ("The HKDF Scheme", http://eprint.iacr.org/2010/264.pdf , by Hugo Krawczyk, section 3). XORing a plaintext with a random keystream to produce ciphertext is a simple and secure approach to data encryption, epitomized by AES-CTR or a stream cipher (http://cr.yp.to/snuffle/design.pdf). HKDF is not the fastest way to generate such a keystream, but it is safe, easy to specify, and easy to implement (just HMAC and XOR).<br />
<br />
Each keystream must be unique. SRP is defined to produce a random session key for each run (as long as at least one of the sides provides a random ephemeral key). We define resetToken to be a single-use randomly-generated value. Hence our two HKDF-XOR keystreams will be unique.<br />
<br />
A slightly more-traditional alternative would be to use AES-CTR (with the same HMAC-SHA256 used here), with a randomly-generated IV. This is equally secure, but requires implementors to obtain an AES library (with CTR mode, which does not seem to be universal). An even more traditional technique would be AES-CBC, which introduces the need for padding and a way to specify the length of the plaintext. The additional specification complexity, plus the library load, leads me to prefer HKDF+XOR.<br />
<br />
kB is equal to the XOR of wrapKey (which is a deterministic function of the user's email address, password, mainSalt, and the stretching parameters) and the server's randomly-generated wrap(kB) value, making kB a random value too. Using XOR as a wrapping function allows us to avoid sending kB or wrap(kB) in the initial createAccount arguments, which are not protected by SRP, and thus would enable an eavesdropper to mount a dictionary attack on the password (using wrap(kB) as their oracle).<br />
<br />
Likewise, allowing the server to generate kA avoids exposure during createAccount. The only point of vulnerability is a forgotten-password resetAccount() message, if an eavesdropper can learn the resetToken. In this case, the attacker might either use the resetToken themselves (then use signToken to learn kA and wrap(kB)), or passively decrypt the resetAccount() arguments to retrieve just wrap(kB). Given wrap(kB) and some encrypted browser data, the attacker can guess passwords (and derive kB, etc) until the data decrypts properly.<br />
<br />
To make this technique safe, any time kB or the password is changed, the mainSalt should be changed too. Otherwise knowledge of both wrap(old-kB) and old-kB would reveal wrapKey, making it easy to deduce the new kB. Changing mainSalt causes wrapKey to change too, preventing this.<br />
<br />
mainSalt is incorporated at the end of the stretching process to allow it to proceed in parallel with the getToken1() call that retrieves the salt. The inputs to the lengthy stretch come entirely from the user (email and password) or are optimistically (stretching parameters). This speedup seems more important than the minor security benefit of including the salt at the beginning of the stretch.<br />
<br />
There is no MAC on wrap(kB). If the keyserver chooses to deliver a bogus wrap(kB) or kA, the client will discover the problem a moment later when it talks to a storage server and attempts to retrieve data from an unrecognized collection-ID (since we intend to derive collection-IDs from the key used to encrypt their data, which will be derived from kA or kB as appropriate). It might be useful to add a checksum to kA and wrap(kB) to detect accidental corruption (e.g. store and deliver kA+SHA256(kA)), but this doesn't protect against intentional changes. We omit this checksum for now, assuming that disks will be reliable enough to let us never experience such failures.<br />
<br />
We use scrypt without a per-user salt. This is safe because the "password" input to scrypt is already diversified by the user's email address. The intention is to allow the scrypt-helper to run on anonymous data, so that an attacker who compromises this helper cannot easily learn the email addresses of the partially-stretched K1 values that it is receiving, confounding their attack.<br />
<br />
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. <br />
For /certificate/sign, we do not need request confidentiality or response confidentiality, since the client's pubkey and the resulting certificate will both be exposed over a similar SSL connection to the storage server later. And it is sufficient to rely on the response integrity provided by SSL, since the client can verify the returned certificate for itself. For the other keyserver APIs protected by HAWK, these properties are either unnecessary, or are provided by additional mechanisms.<br />
<br />
<br />
= Proof-Of-Work =<br />
<br />
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(). The server can control how much work is required.<br />
<br />
The getToken1() call looks for a "X-PiCL-PoW:" HTTP header. Most of the time, clients don't supply this header. But if the server responds to the getToken1() call with an error that indicates PoW is required, clients must create a valid PoW string and include it as the value of an "X-PiCL-PoW:" header in their next call to getToken1().<br />
<br />
The server's error message includes two parameters. The first is a "prefix string": the client's PoW string is required to begin with this prefix. The second is a "threshold hash". SHA256(PoWString) is required to be lexicographically earlier than the thresholdHashString (i.e. the numerical value of its hash must be closer to zero than the threshold). The client is expected to concatenate the prefix with a counter, then repeatedly increment the counter and hash the result until they meet the threshold, then re-submit their getToken1() request with the combined prefix+counter string in the header. If the client has spent more than e.g. 10 seconds doing this, the client should probably help the user cancel the operation and try again.<br />
<br />
When a server is under a DoS attack (either via some manual configuration tool or sensed automatically), it should start requiring valid unique X-PiCL-PoW headers. The server should initially require very little work, by using a threshold hash with just a few leading zero bits. If this is insufficient to reduce the attack volume, the threshold should be lowered, requiring even more work (from both the attacker and legitimate clients).<br />
<br />
The server should create a prefix string that contains a parseable timestamp and a random nonce (e.g. "%d-%d-" % (int(time.time()), b32encode(os.urandom(8)))). The server should also decide on a cutoff time (perhaps ten minutes ago). Each server must then maintain a table of "old PoW strings" to prevent replay attacks (these do not need to be shared among all servers: an in-RAM cache is fine).<br />
<br />
When the server receives a proposed PoW string, it first splits off the leading timestamp, and if the timestamp is older than the cutoff time, it rejects the string (either by dropping the connection, or returning a new "PoW required" error if it's feeling nice). Then it hashes the whole string and compares it against the threshold, rejecting those which fail to meet the threshold. Finally, for strings that pass the hash threshold, it checks the "old strings" table, and rejects any that appear on that list.<br />
<br />
If the PoW string makes it past all these checks, the server should add the string to the "old strings" table, then accept the request (i.e. compute an srpB value and add a session-id table entry for the request).<br />
<br />
The old-strings table check should be optimized to reject present strings quickly (i.e. if we are under attack, we should expect to see lots of duplicates of the same string, and must minimize the work we do when this occurs).<br />
<br />
The server can remove values from the old-strings table that have timestamps older than the cutoff time. The server can also discard values at other times (to avoid consuming too much memory), without losing anything but protection against resource consumption.<br />
<br />
Other notes:<br />
<br />
The server-side code for this can be deferred until we care to have a response to a DoS attack. However the client-side code for this must be present from day one, otherwise we won't be able to turn on the defense later without fear of disabling legitimate old clients.<br />
<br />
The server should perform as little work as possible before rejecting a token. Every extra CPU cycle it spends in this path is increasing the DoS attack amplification factor.<br />
<br />
The nonce in the prefix string exists to make sure that two successive clients get different prefixes, and thus do not come up with the same counter value (and inadvertently create identical strings, looking like a replay attack). If this proved annoying or expensive, we could instead obligate clients to produce their own nonce.<br />
<br />
TBD: Is this worth it? Should the PoW string go into an HTTP header? (I want it to be cheap to extract, and not clutter logs). Should the error response be a distinctive HTTP error code so our monitoring tools can easily count them? We can also use this feature to slow down online guessing attacks (i.e. trigger it either when getToken1 is called too much or when getToken2 produces too many errors). Since getToken1() includes an email address, we could also requires PoWs for some addresses (e.g. those we know to be under attack) but not others.<br />
<br />
= Glossary =<br />
<br />
This defines some of the jargon we've developed for this protocol.<br />
<br />
* data classes: each type of browser data (bookmarks, passwords, history, etc) can be assigned, by the user, to either class-A or class-B<br />
* class-A: data assigned to this class can be recovered, even if the user forgets their password, by proving control over an email address and resetting the account. It can also be read by Mozilla (since it runs the keyserver and knows kA), or by the user's IdP (by resetting the account without the user's permission).<br />
* class-B: data in this class cannot be recovered if the password is forgotten. It cannot be read by the IdP. Mozilla (via the keyserver) cannot read this data, but can attempt a brute-force dictionary attack against the password.<br />
* kA: the master key for data stored as "class-A", a 32-byte binary string. Individual encryption keys for different datatypes are derived from kA.<br />
* kB: the master key for data stored as "class-B", a 32-byte binary string.<br />
* wrap(kB): an encrypted copy of kB. The keyserver stores wrap(kB) and never sees kB itself. The client (browser) uses a key derived from the user's password to decrypt wrap(kB), obtaining the real kB.<br />
* sessionToken: a long-lived per-device token which allows the device to obtained signed BrowserID certificates for the account's identity (GUID@picl-something.org). This token remains valid until the user revokes it (either by changing their password, or triggering some kind of "revoke a specific device" or "revoke all devices" function).<br />
<br />
= Test Vectors =<br />
<br />
The following example uses a non-ASCII email address of "andré@example.org" (with an accented "e", UTF8 encoding is 616e6472c3a9406578616d706c652e6f7267) and a non-ascii password of "pässwörd" (with accents on "a" and "o", UTF8 encoding is 70c3a4737377c3b67264).<br />
<br />
These test vectors were produced by the python code in https://github.com/warner/picl-spec-crypto . The diagrams may lag behind the latest version of that code.<br />
<br />
== stretch-KDF ==<br />
<br />
email:<br />
616e6472c3a94065<br />
78616d706c652e6f<br />
7267<br />
<br />
password:<br />
70c3a4737377c3b6<br />
7264<br />
<br />
K1 (scrypt input):<br />
f84913e3d8e6d624<br />
689d0a3e9678ac8d<br />
cc79d2c2f3d96414<br />
88cd9d6ef6cd83dd<br />
<br />
K2 (scrypt output):<br />
5b82f146a6412692<br />
3e4167a0350bb181<br />
feba61f63cb17140<br />
12b19cb0be0119c5<br />
<br />
stretchedPW:<br />
c16d46c31bee242c<br />
b31f916e9e38d60b<br />
76431d3f5304549c<br />
c75ae4bc20c7108c<br />
<br />
== main-KDF ==<br />
<br />
mainSalt (normally random):<br />
00f0000000000000<br />
0000000000000000<br />
0000000000000000<br />
000000000000034d<br />
<br />
srpPW:<br />
00f9b71800ab5337<br />
d51177d8fbc682a3<br />
653fa6dae5b87628<br />
eeec43a18af59a9d<br />
<br />
unwrapBKey:<br />
6ea660be9c89ec35<br />
5397f89afb282ea0<br />
bf21095760c8c500<br />
9bbcc894155bbe2a<br />
<br />
internal x (base 10):<br />
8192518690918<br />
99580124814080709381476194749939<br />
03899664126296984459627523279550<br />
<br />
internal x (hex):<br />
b5200337cc3f3f92<br />
6cdddae0b2d31029<br />
c069936a844aff58<br />
779a545be89d0abe<br />
<br />
v (verifier as number) (base 10):<br />
114649<br />
57230405843056840989945621595830<br />
71784395917725741221739574165799<br />
54316134303691657140298181419198<br />
87853709633756255809680435884948<br />
69849281177012209169281795507853<br />
57610332070005048463659745521969<br />
83218225819721112680718485091921<br />
64608360806562626442477160609654<br />
43167308814558974899899506977051<br />
96721477608178869100211706638584<br />
53875100985456239693728258285562<br />
04889672594983678412848291529879<br />
88548996842770025110751388952323<br />
22170663943486107183421205517476<br />
84831590615660554713667726412525<br />
73641352721966728239512914666806<br />
49625530438034148797508015907639<br />
67594925530663571631035463732161<br />
30193328802116982288883318596822<br />
<br />
== SRP Verifier ==<br />
<br />
k (base 10):<br />
259003859907<br />
09503006915442163037721228467470<br />
35652616593381637186118123578112<br />
<br />
srpSalt (normally random):<br />
00f1000000000000<br />
0000000000000000<br />
0000000000000000<br />
0000000000000179<br />
<br />
srpVerifier:<br />
00173ffa0263e63c cfd6791b8ee2a40f<br />
048ec94cd95aa8a3 125726f9805e0c82<br />
83c658dc0b607fbb 25db68e68e93f265<br />
8483049c68af7e82 14c49fde2712a775<br />
b63e545160d64b00 189a86708c69657d<br />
a7a1678eda0cd79f 86b8560ebdb1ffc2<br />
21db360eab901d64 3a75bf1205070a57<br />
91230ae56466b8c3 c1eb656e19b794f1<br />
ea0d2a077b3a7553 50208ea0118fec8c<br />
4b2ec344a05c66ae 1449b32609ca7189<br />
451c259d65bd15b3 4d8729afdb5faff8<br />
af1f3437bbdc0c3d 0b069a8ab2a959c9<br />
0c5a43d42082c774 90f3afcc10ef5648<br />
625c0605cdaace6c 6fdc9e9a7e6635d6<br />
19f50af773452247 0502cab26a52a198<br />
f5b00a2798589165 07b0b4e9ef9524d6<br />
<br />
== SRP B ==<br />
<br />
private b (normally random) (base 10):<br />
1198277<br />
66042000957856349411550091527197<br />
08125226960544768993643095227800<br />
29105361555030352774505625606029<br />
71778328003253459733139844872578<br />
33596964143417216389157582554932<br />
02841499373672188315534280693274<br />
23189198736863575142046053414928<br />
39548731879043135718309539706489<br />
29157321423483527294767988835942<br />
53343430842313006332606344714480<br />
99439808861069316482621424231409<br />
08830704769167700098392968117727<br />
43420990997238759832829219109897<br />
32876428831985487823417312772399<br />
92628295469389578458363237146486<br />
38545526799188280210660508721582<br />
00403102624831815596140094933216<br />
29832845626116777080504444704039<br />
04739431335617585333671378812943<br />
<br />
private b (hex):<br />
00f3000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 000000000000000f<br />
<br />
transmitted srpB:<br />
0022ce5a7b9d8127 7172caa20b0f1efb<br />
4643b3becc535664 73959b07b790d3c3<br />
f08650d5531c19ad 30ebb67bdb481d1d<br />
9cf61bf272f84398 48fdda58a4e6abc5<br />
abb2ac496da5098d 5cbf90e29b4b110e<br />
4e2c033c70af7392 5fa37457ee13ea3e<br />
8fde4ab516dff1c2 ae8e57a6b264fb9d<br />
b637eeeae9b5e43d faba9b329d3b8770<br />
ce89888709e02627 0e474eef822436e6<br />
397562f284778673 a1a7bc12b6883d1c<br />
21fbc27ffb3dbeb8 5efda279a69a1941<br />
4969113f10451603 065f0a0126666456<br />
51dde44a52f4d8de 113e2131321df1bf<br />
4369d2585364f9e5 36c39a4dce33221b<br />
e57d50ddccb4384e 3612bbfd03a268a3<br />
6e4f7e01de651401 e108cc247db50392<br />
<br />
== SRP A ==<br />
<br />
private a (normally random) (base 10):<br />
1193346<br />
47663227291363113405741243413916<br />
43482736314616601220006703889414<br />
28162541137108417166380088052095<br />
43910927476491099816542561560345<br />
50331133015255005622124012256352<br />
06121987030570656676375703406470<br />
63422988042473190059156975005813<br />
46381864669664357382020200036915<br />
26156674010218162984912976536206<br />
14440782978764393137821956464627<br />
16314542157937343986808167341567<br />
89864323268060014089757606109012<br />
50649711198896213496068605039486<br />
22864591676298304745954690086093<br />
75374681084741884719851454277570<br />
80362211874088739962880012800917<br />
05751238004976540634839106888223<br />
63866455314898189520502368799907<br />
19946264951520393624479315579863<br />
<br />
private a (hex):<br />
00f2000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 000000000000d3d7<br />
<br />
transmitted srpA:<br />
007da76cb7e77af5 ab61f334dbd5a958<br />
513afcdf0f47ab99 271fc5f7860fe213<br />
2e5802ca79d2e5c0 64bb80a38ee08771<br />
c98a937696698d87 8d78571568c98a1c<br />
40cc6e7cb101988a 2f9ba3d65679027d<br />
4d9068cb8aad6ebf f0101bab6d52b5fd<br />
fa81d2ed48bba119 d4ecdb7f3f478bd2<br />
36d5749f2275e948 4f2d0a9259d05e49<br />
d78a23dd26c60bfb a04fd346e5146469<br />
a8c3f010a627be81 c58ded1caaef2363<br />
635a45f97ca0d895 cc92ace1d09a99d6<br />
beb6b0dc0829535c 857a419e834db128<br />
64cd6ee8a843563b 0240520ff0195735<br />
cd9d316842d5d3f8 ef7209a0bb4b54ad<br />
7374d73e79be2c39 75632de562c59647<br />
0bb27bad79c3e2fc ddf194e1666cb9fc<br />
<br />
== SRP key-agreement ==<br />
<br />
u:<br />
b284aa1064e87751 50da6b5e2147b47c<br />
a7df505bed94a6f4 bb2ad873332ad732<br />
<br />
S:<br />
0092aaf0f527906a a5e8601f5d707907<br />
a03137e1b601e04b 5a1deb02a981f4be<br />
037b39829a27dba5 0f1b27545ff2e287<br />
29c2b79dcbdd32c9 d6b20d340affab91<br />
a626a8075806c26f e39df91d0ad979f9<br />
b2ee8aad1bc783e7 097407b63bfe58d9<br />
118b9b0b2a7c5c4c debaf8e9a460f4bf<br />
6247b0da34b760a5 9fac891757ddedca<br />
f08eed823b090586 c63009b2d740cc9f<br />
5397be89a2c32cdc fe6d6251ce11e44e<br />
6ecbdd9b6d93f30e 90896d2527564c7e<br />
b9ff70aa91acc0ba c1740a11cd184ffb<br />
989554ab58117c21 96b353d70c356160<br />
100ef5f4c28d19f6 e59ea2508e8e8aac<br />
6001497c27f362ed bafb25e0f045bfdf<br />
9fb02db9c908f103 40a639fe84c31b27<br />
<br />
M1:<br />
27949ec1e0f16256<br />
33436865edb037e2<br />
3eb6bf5cb91873f2<br />
a2729373c2039008<br />
<br />
srpK:<br />
e68fd0112bfa31dc<br />
ffc8e9c96a1cbadb<br />
4c3145978ff35c73<br />
e5bf8d30bbc7499a<br />
<br />
== /auth ==<br />
<br />
srpK:<br />
e68fd0112bfa31dc<br />
ffc8e9c96a1cbadb<br />
4c3145978ff35c73<br />
e5bf8d30bbc7499a<br />
<br />
respHMACkey:<br />
6584613597ef012f<br />
f1752b7869f01d03<br />
c72547a7b7199681<br />
531d9df1991edf23<br />
<br />
respXORkey:<br />
455835926ae37a1b<br />
627bd16affbeeab6<br />
27ecc737121826ca<br />
4a2bac2c100bf417<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
plaintext:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
ciphertext:<br />
253957f10e861c7c<br />
0a12bb0193d384d9<br />
579db544666d50bd<br />
3252d6576c768a68<br />
<br />
MAC:<br />
a98c87f5769ab4cc<br />
ca3df863faeb217e<br />
b16ddc29d712b301<br />
12b446324ee806d6<br />
<br />
response:<br />
253957f10e861c7c<br />
0a12bb0193d384d9<br />
579db544666d50bd<br />
3252d6576c768a68<br />
a98c87f5769ab4cc<br />
ca3df863faeb217e<br />
b16ddc29d712b301<br />
12b446324ee806d6<br />
<br />
== /session ==<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
tokenID:<br />
6dcae8ff8f55a793<br />
a0fa1ed31115451b<br />
4df233b3a0641cc6<br />
18ecadfd1fe4a691<br />
<br />
reqHMACkey:<br />
1640a4e6bc8c8e54<br />
858be9960a8b0740<br />
fa06effdf169246f<br />
52012ae868fc6c48<br />
<br />
respHMACkey:<br />
7f3e075e74523ced<br />
fa817c2fa4ae97e1<br />
e51da38d7a992b66<br />
8a35c86af946b155<br />
<br />
respXORkey:<br />
02977a9167830705<br />
74b610cc25320262<br />
175b45fbd7b26438<br />
f9e200abc029f14e<br />
f38399314b172f1e<br />
e928fcdcd194ab19<br />
92433cab0e94569d<br />
bf623b46dd9fbf55<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
sessionToken:<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
plaintext:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
ciphertext:<br />
8216f812e3068182<br />
fc3f9a47a9bf8ced<br />
87cad7684327f2af<br />
617b9a305cb46fd1<br />
53223b92efb289b9<br />
418156777d3905b6<br />
22f28e18ba21e02a<br />
07db81fd612201ea<br />
<br />
MAC:<br />
639fd132f637abd3<br />
ecd2482ccf11ed76<br />
8cfd6979e1954046<br />
1e8ef5204e66c542<br />
<br />
response:<br />
8216f812e3068182<br />
fc3f9a47a9bf8ced<br />
87cad7684327f2af<br />
617b9a305cb46fd1<br />
53223b92efb289b9<br />
418156777d3905b6<br />
22f28e18ba21e02a<br />
07db81fd612201ea<br />
639fd132f637abd3<br />
ecd2482ccf11ed76<br />
8cfd6979e1954046<br />
1e8ef5204e66c542<br />
<br />
== /account/keys ==<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
tokenID:<br />
d010c94c753c012c<br />
d6801e8beb1aa6cc<br />
3da9ea3de3de1dee<br />
32785dbd99a579e8<br />
<br />
reqHMACkey:<br />
1707b05908acc4dc<br />
cda5b8304d9500d0<br />
8c53e00c31672a53<br />
490dfb5ef2934060<br />
<br />
respHMACkey:<br />
31d0c12186b76897<br />
c3351878a65097cf<br />
d595da4ce48e69a2<br />
485ff1a77c71b0d0<br />
<br />
respXORkey:<br />
eed35591e1f1c43b<br />
7cd604e371b9cfb7<br />
a980c9a36fa737c6<br />
a48c5d60a89fc291<br />
4ec1a2150a0777b7<br />
9a1e8499058cd17a<br />
ebc1441db8b3bf18<br />
2cd0aefa92482692<br />
<br />
kA:<br />
2021222324252627<br />
28292a2b2c2d2e2f<br />
3031323334353637<br />
38393a3b3c3d3e3f<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
plaintext:<br />
2021222324252627<br />
28292a2b2c2d2e2f<br />
3031323334353637<br />
38393a3b3c3d3e3f<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
ciphertext:<br />
cef277b2c5d4e21c<br />
54ff2ec85d94e198<br />
99b1fb905b9201f1<br />
9cb5675b94a2fcae<br />
0e80e0564e4231f0<br />
d257ced249c19f35<br />
bb90164eece6e94f<br />
7489f4a1ce1578cd<br />
<br />
MAC:<br />
86f1c57d2e7f6c97<br />
8181684e189b710f<br />
dd26a3f34e3aaed8<br />
64be9577ae81a256<br />
<br />
response:<br />
cef277b2c5d4e21c<br />
54ff2ec85d94e198<br />
99b1fb905b9201f1<br />
9cb5675b94a2fcae<br />
0e80e0564e4231f0<br />
d257ced249c19f35<br />
bb90164eece6e94f<br />
7489f4a1ce1578cd<br />
86f1c57d2e7f6c97<br />
8181684e189b710f<br />
dd26a3f34e3aaed8<br />
64be9577ae81a256<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
unwrapBKey:<br />
6ea660be9c89ec35<br />
5397f89afb282ea0<br />
bf21095760c8c500<br />
9bbcc894155bbe2a<br />
<br />
kB:<br />
2ee722fdd8ccaa72<br />
1bdeb2d1b76560ef<br />
ef705b04349d9357<br />
c3e592cf4906e075<br />
<br />
== use session (certificate/sign, etc) ==<br />
<br />
sessionToken:<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
tokenID:<br />
639503a218ffbb62<br />
983e9628be5cd64a<br />
0438d0ae81b2b9da<br />
deb900a83470bc6b<br />
<br />
reqHMACkey:<br />
3a0188943837ab22<br />
8fe74e759566d0e4<br />
837cbcc7494157aa<br />
c4da82025b2811b2<br />
<br />
== /password/change ==<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
tokenID:<br />
cafc36360afd92de<br />
5ca21800022a9af1<br />
3a5766b91bd82fd4<br />
0eaa5b6e01489796<br />
<br />
reqHMACkey:<br />
b07c0cf4553e44ff<br />
fe991caa2546b50d<br />
895fb9ac8f8746d2<br />
d29119d9616de193<br />
<br />
respHMACkey:<br />
d2ddfefd1913fa34<br />
48e18abda9b54c92<br />
43fd51bf14dc9091<br />
2179269c0e958a04<br />
<br />
respXORkey:<br />
dcc5425e13b876ea<br />
f1d3aa95a4735622<br />
46994088d86adb5a<br />
526d9f1f5d170254<br />
456dd26dcc54483e<br />
f489d55097b69028<br />
8826f0cf1985a6ad<br />
e3e83461517c8d49<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
accountResetToken:<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
plaintext:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
ciphertext:<br />
5c44c0dd973df06d<br />
795a201e28fed8ad<br />
d608d21b4cff4dcd<br />
caf40584c18a9ccb<br />
85ac10ae08918ef9<br />
3c401f9b5b7b5ee7<br />
58f7221ccd50707a<br />
3b31eeba8da15396<br />
<br />
MAC:<br />
cc3053fe922268d7<br />
9c0dd6eb74bd40f5<br />
07ae2d587483b864<br />
8ef771b699dd39d9<br />
<br />
response:<br />
5c44c0dd973df06d<br />
795a201e28fed8ad<br />
d608d21b4cff4dcd<br />
caf40584c18a9ccb<br />
85ac10ae08918ef9<br />
3c401f9b5b7b5ee7<br />
58f7221ccd50707a<br />
3b31eeba8da15396<br />
cc3053fe922268d7<br />
9c0dd6eb74bd40f5<br />
07ae2d587483b864<br />
8ef771b699dd39d9<br />
<br />
== /account/reset ==<br />
<br />
accountResetToken:<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
tokenID:<br />
a6857e5d53d35073<br />
d50ef2ce2c4dd747<br />
32bb2eae1af5bf79<br />
618ed945e1310792<br />
<br />
reqHMACkey:<br />
47fab27352ee6b48<br />
33938d76519bbdb8<br />
ac7293f8b5e74335<br />
6fdd1d5edf39f52d<br />
<br />
reqXORkey:<br />
82ed612313a11673 95108d7d379b2029<br />
7a539ce9d3861e95 1bf5a9b9cdbfb332<br />
bd6aba056ce0c568 2c5a93963446b1b4<br />
7397c8c24f3a1d67 2a0ddc856474f5b1<br />
33ab884ce33335c1 5578a1a7302933cb<br />
458fbee0a5e52414 c914beb97568a30c<br />
28364dc8fb03ae7c 76a2f324a9a1cee6<br />
71b74aa8906d0e03 39fb52a1bf2b1ef5<br />
ab5d883295db62af 20701cb3af42a09e<br />
c76cda585ab5644b 7250ef7b780537e5<br />
b3e784d37a118bd6 57a0fe29ec6e5cd3<br />
325be8e1d8a3dd71 b360ea266757e463<br />
ada6b0a7a85a8ac0 eed618d9f6ee91ab<br />
1d2f714f224d67db 46843c4e3339de15<br />
efe0297a45f9fe0d 6d768b5c589a290f<br />
11f03237192cc0a3 a02645a810d83bb1<br />
84d582bfb15d2393 3fa4805374da62c6<br />
a2c887b157285c6a 79b47156c9abe02e<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
newSRPv:<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
<br />
plaintext:<br />
4041424344454647 48494a4b4c4d4e4f<br />
5051525354555657 58595a5b5c5d5e5f<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
<br />
ciphertext:<br />
c2ac236057e45034 dd59c7367bd66e66<br />
2a02ceba87d348c2 43acf3e291e2ed6d<br />
ac7bab147df1d479 3d4b82872557a0a5<br />
6286d9d35e2b0c76 3b1ccd947565e4a0<br />
22ba995df22224d0 4469b0b6213822da<br />
549eaff1b4f43505 d805afa86479b21d<br />
39275cd9ea12bf6d 67b3e235b8b0dff7<br />
60a65bb9817c1f12 28ea43b0ae3a0fe4<br />
ba4c992384ca73be 31610da2be53b18f<br />
d67dcb494ba4755a 6341fe6a691426f4<br />
a2f695c26b009ac7 46b1ef38fd7f4dc2<br />
234af9f0c9b2cc60 a271fb377646f572<br />
bcb7a1b6b94b9bd1 ffc709c8e7ff80ba<br />
0c3e605e335c76ca 57952d5f2228cf04<br />
fef1386b54e8ef1c 7c679a4d498b381e<br />
00e12326083dd1b2 b13754b901c92aa0<br />
95c493aea04c3282 2eb5914265cb73d7<br />
b3d996a046394d7b 68a56047d8baf13f<br />
<br />
== /account/destroy ==<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
tokenID:<br />
b2512ff41c4e6d8a<br />
beb3bda37e326f51<br />
cf4efdbf90e50e77<br />
029be2563884b9fe<br />
<br />
reqHMACkey:<br />
75cfa782c19e41f9<br />
c7e125f3dc4c3bf1<br />
0a77c93a9999e06f<br />
b2646b3038e4ea44<br />
<br />
= Keyserver Protocol Summary =<br />
<br />
* POST /account/create (email,srpV,srpSalt) -> ok (server sends verification email)<br />
** creates a user account<br />
* GET /account/devices [sessionToken] () -> list of devices<br />
* GET /account/keys [keyFetchToken,needs-verf] () -> kA/wrap(kB)<br />
** single-use, only if email is verified, encrypted results<br />
* POST /account/reset [authed+encrypted by accountResetToken] (wrap(kB),srpV,srpSalt) -> ok<br />
** single-use, does not require email to be verified, revoke all tokens for account, send notification email to user<br />
* POST /account/delete [authToken] () -> ok, account deleted<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authToken] () -> keyFetchToken, sessionToken<br />
* POST /session/destroy [sessionToken] () -> ok<br />
** for detaching a device, destroy all tokens<br />
* POST /recovery_email/status [sessionToken] () -> "verified" status of email<br />
** use "Accept: text/event-stream" header for server-sent-events; server will send "update" event with the new content of the resource any time it changes.<br />
* POST /recovery_email/resend_code [sessionToken] () -> re-send verification email<br />
* POST /recovery_email/verify_code (code) -> set "verified" flag<br />
** this code will come from a clickable link and is an unauthenticated endpoint<br />
** this could maybe take the recovery method if that would be helpful<br />
** sets verified flag on recovery method<br />
* POST /certificate/sign [sessionToken,needs-verf] (pubkey) -> cert<br />
** only if recovery email is verified<br />
* POST /password/change/start [authToken,needs-verf] () -> accountResetToken, keyFetchToken<br />
* POST /password/forgot/send_code () -> forgotPasswordToken<br />
** sends code to recovery method (email for now, maybe SMS later)<br />
** this is a short code, not a clickable link<br />
* POST /password/forgot/resend_code (forgotPasswordToken) -> re-sends code<br />
* POST /password/forgot/verify_code (forgotPasswordToken, code) -> accountResetToken<br />
** sets verified flag on recovery method<br />
* POST /get_random_bytes<br />
<br />
<br />
== Typical Client Flows ==<br />
<br />
Create account<br />
<br />
* POST /account/create (email,srpV,srpSalt) -> ok (server sends verification email)<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authed with authToken]() -> keyFetchToken, sessionToken<br />
* GET /recovery_email/status [sessionToken] () -> "verified" status<br />
** (optional, only if user requests resend) POST /recovery_email/resend_code [sessionToken]() -> ok<br />
** POST /recovery_email/verify_code (code) -> ok<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
* POST /certificate/sign [sessionToken] (pubkey) -> cert<br />
<br />
<br />
Attach to new device<br />
<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authToken] () -> keyFetchToken, sessionToken<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
** (if unverified-error, do waitUntilEmailVerified, then try again)<br />
* POST /certificate/sign [sessionToken] (pubkey) -> cert<br />
<br />
<br />
Forgot password<br />
<br />
* POST /password/forgot/send_code (email) -> forgotPasswordToken<br />
* POST /password/forgot/verify_code (forgotPasswordToken, code) -> accountResetToken<br />
* POST /account/reset [authed+encrypted by accountResetToken] (0000,srpV,srpSalt) -> ok<br />
* GOTO "Attach to new device"<br />
<br />
<br />
Change Password<br />
<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /password/change/start [authToken] () -> accountResetToken, keyFetchToken<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
* POST /account/reset [authed+encrypted by accountResetToken] (wrap(kB),srpV,srpSalt) -> ok<br />
* GOTO "Attach to new device"</div>Warnerhttps://wiki.mozilla.org/index.php?title=Identity/AttachedServices/KeyServerProtocol&diff=691407Identity/AttachedServices/KeyServerProtocol2013-08-07T18:46:30Z<p>Warner: /* Creating a Session */</p>
<hr />
<div>= PiCL Key Server / IdP Protocol =<br />
<br />
NOTE: This specification is slowly converging on stability (31-Jul-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.<br />
<br />
The server is being developed in https://github.com/mozilla/picl-idp . This repo currently includes a demonstration client (node.js CLI).<br />
<br />
Note that all messages are delivered over an HTTPS connection. The client browser may also implement cert-pinning to improve on the certificate validation process. The protections described below are in addition to those provided by TLS.<br />
<br />
Remaining TODO items:<br />
<br />
* decide on client-side key-stretching parameters: http://keywrapping.appspot.com can help<br />
* finalize SRP questions (definition of M1, generation of a/b)<br />
* finalize proof-of-work/DoS-prevention details<br />
* decide how to rate-limit account-creation calls<br />
* confirm this is actually implementable inside Firefox (especially w.r.t. NSS and Android/Java crypto)<br />
<br />
= Creating The Account =<br />
<br />
The first act performed by a user is to create the account. They enter email+password into their browser, which then does the following steps:<br />
<br />
* decide upon stretching parameters (perhaps consulting the keyserver for recommendations, but imposing a minimum strength requirement). For now we used a fixed {firstPBKDF:20000, scrypt: {N:65536, r:8, p:1}, secondPBKDF:20000}.<br />
* randomly choose a 32-byte mainSalt (this should be unique, but is not secret)<br />
* choose the SRP group parameters (fixed: use the 2048-bit group described below)<br />
* perform key-stretching (as described below), derive stretchedPW and srpPW<br />
* randomly choose a 32-byte srpSalt (unique, but not secret)<br />
* create srpVerifier from srpPW and srpSalt (as described below)<br />
* deliver (email, stretchParams, mainSalt, srpParams, srpSalt) to the keyserver's "POST /account/create" API<br />
<br />
<br />
To limit abuse, the createAccount() should also require a fresh "createToken". This should be created by some other API, outside the scope of this document, that perhaps requires a CAPTCHA or something. createAccount() might also require a proof-of-work token, as described below.<br />
<br />
The server, when creating a new account, creates both kA and wrap(kB) as randomly-generated 256-bit (32-byte) strings. It stores these, along with all the remaining values, indexed by email, in the account table where they can be retrieved by getToken later.<br />
<br />
== Email Verification ==<br />
<br />
To prevent fixation attacks, we require new accounts to verify their configured recovery email address before letting them learn the generated keys or obtain a signed certificate. Nevertheless, we wish clients to forget the user's password while they wait for email verification to complete. To achieve this, clients can obtain a sessionToken before verification, but most APIs that require it will raise errors until verification is finished.<br />
<br />
The server will send email with a URL that contains a long random "verification code" in the "fragment" hash. This URL points to a static page with some javascript that submits the code to the "POST /account/recovery_methods/verify_code" API. The URL can be clicked by any browser (it is not bound to anything), and when the API is hit, the account is marked as verified.<br />
<br />
After the client submits /account/create, it performs the "/session/auth" login sequence below to obtain a sessionToken. It then polls the "GET /account/recovery_methods" (which requires a sessionToken but not account verification) until the user clicks the email link and the API reports verification is complete. Then the client uses "GET /account/keys" and "POST /certificate/sign", described below, to obtain kA, kB, and a signed certificate to talk to the storage server.<br />
<br />
= Login: Obtaining the authToken =<br />
<br />
To connect a browser to an existing account, we use the following login protocol to transform an email+password pair into a single-use authToken. We will use this in the next section to create a session, from which we can obtain encryption keys and signed certificates.<br />
<br />
This protocol starts by using key-stretching to transform the email+password into a "stretchedPW", then feeds this into an SRP protocol to get the authToken.<br />
<br />
[[File:PICL-IdPAuth-auth-start.png|IdP Auth Protocol]]<br />
<br />
This authToken can be used (once) to do one of the following:<br />
<br />
* /session/create: obtain a sessionToken (and keyFetchToken), which enables storage server access<br />
* /password/change: obtain an accountResetToken, to safely reset the account password<br />
* /account/delete: to delete the entire account<br />
<br />
The protocol is designed to enable parallelism between key-stretching and the initial network messages, to reduce the time it takes to connect a browser to the account. In total, the browser requires five messages in four roundtrips (1: /auth/start, 2: /auth/finish, 3: /session/create, 4: /account/keys and /certificate/sign in parallel) before it is ready to talk to the storage server.<br />
<br />
== auth/start ==<br />
<br />
As soon as the user finishes typing in the email address, the client should send it in the "/auth/start" 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 loginSRPToken that is used to associate this request with the subsequent /auth/finish 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.<br />
<br />
To mitigate DoS abuse, /auth/start may also require a proof-of-work string, described below.<br />
<br />
== Client-Side Key Stretching ==<br />
<br />
"Key Stretching" is the practice of running a password through a computationally-expensive one-way function before using it for encryption or authentication. The goal is to make brute-force dictionary attacks more expensive, by raising the cost of testing each guess.<br />
<br />
To protect the user's class-B data against compromises of our keyserver, we perform this key stretching on the client, rather than on the server. We use the memory-hard "scrypt" function (pronounced "ess-crypt") for this purpose, as motivated by the attacker-cost studies in [[Identity/CryptoIdeas/01-PBKDF-scrypt]]. Slower (low-RAM) devices can still participate by outsourcing the scrypt step to an "scrypt helper". To provide at least minimal protection against a compromise of this helper, we sandwich the scrypt step between two PBKDF2 steps. The complete protocol looks like this:<br />
<br />
[[File:PICL-IdPAuth-stretch-KDF.png|Stretching KDF]]<br />
<br />
Our initial parameter choices for this stretching are to use 20000 iterations of PBKDF2 on each side (40k in all), and to run scrypt with N/r/p set to 64k/8/1. This requires roughly 60MB of RAM for about 1.3 seconds on a 1.8GHz Intel Atom linux box (it will run faster, but use the same memory, on more modern CPUs). We are still studying performance on various platforms to decide what parameters to use in V1: lowering them speeds up the login process, but reduces security (by reducing the cost of an dictionary attack).<br />
<br />
Since the stretching is expected to take a second or two, the client can optimistically start this process (using default parameters) before receiving the auth/start 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 "mainSalt is added *after* the stretching, to enable this parallelism (at a tiny cost in security).<br />
<br />
After "stretchedPW" is derived, a second HKDF call is used to derive "srpPW" and "unwrapBKey" which will be used later.<br />
<br />
[[File:PICL-IdPAuth-main-KDF.png|masterKey KDF]]<br />
<br />
== SRP Protocol Details ==<br />
<br />
The PiCL client uses the SRP protocol (http://srp.stanford.edu/) to prove that it knows the (stretched) account password without revealing the actual password (or information enabling a brute-force attack) to the server or any eavesdroppers. <br />
<br />
SRP is somewhat underspecified. We use SRP-6a, with SHA256 as the hash, and the 2048-bit modulus defined in RFC 5053 Appendix A. We consistently zero-pad all string values to 256 bytes (2048 bits), and use H(A+B+S) as the key-confirmation message "M1". These details, plus the SRP design papers and RFCs 2945 and 5054, should be enough to build a compatible implementation. The diagrams below and the test vectors at the end of this page can be used to verify compatibility.<br />
<br />
The server should use Jed's SRP module from https://github.com/jedp/node-srp . The client might use SJCL (http://crypto.stanford.edu/sjcl/) or native code (NSS).<br />
<br />
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).<br />
<br />
Note that SRP-6a uses a "k" value which basically encodes the group being used ("N" and "g"). Since all PICL accounts use the same 2048-bit group, they will all use the same "k" value (not to be confused with the per-session shared-secret "K" key that emerges from the protocol). This group's "k" integer is (as a base-10 number):<br />
259003859907<br />
09503006915442163037721228467470<br />
35652616593381637186118123578112<br />
<br />
=== SRP Verifier Calculation ===<br />
<br />
When the client first creates the account, it must combine the account email address, the stretched password (srpPW), and a randomly-generated srpSalt, to compute the srpVerifier. The server will use this verifier later, to check whether or not the client really knows the password.<br />
<br />
If the server is compromised and an attacker learns the srpVerifier for a given account, they cannot use this to directly log in (the verifier is not "password-equivalent"), but it does allow them to perform an offline brute-force attack against the user's password. In this respect, it is similar to a traditional hashed password. We make these attacks somewhat more expensive by performing the client-side stretching described above, instead of using the raw user password in the SRP calculation.<br />
<br />
[[File:PICL-IdPAuth-SRP-Verifier.png|client-side SRP Verifier calculation]]<br />
<br />
Test vectors are provided at the end of this page. The protocol requires that several values ("a", "b", "srpSalt") are chosen randomly, but for illustrative purposes, the test vectors use carefully-crafted non-random values. For the user's sake, please ensure implementations use proper random values instead of simply copying the test vectors.<br />
<br />
The client sends srpSalt and srpVerifier to the server when it creates the account. It will also re-compute the 'x' value (as an integer) during sign-in. The server will convert the srpVerifier string back into an integer ('v') for use during its own sign-in calculations.<br />
<br />
=== SRP Server-side Sign-In Flow ===<br />
<br />
When the user connects a new device to their account, they use the /auth/start API to start the SRP protocol. This sends the account email address to the server. The server looks up the stored srpVerifier for this account, creates a random 'b' integer, performs some math to compute the "B" number, then converts B into a string known as "srpB". "srpB" is returned to the client, along with srpSalt and the key-stretching parameters. "b" and "srpB" are retained for the subsequent /auth/finish call. '''Note that it is critical that the "b" integer remain secret on the server.''' The server also allocates a random loginSRPToken to connect the two calls.<br />
<br />
[[File:PICL-IdPAuth-SRP-Server.png|server-side SRP]]<br />
<br />
Later, /auth/start will be called with the client's srpA string and its M1 key-confirmation string. srpA is combined with srpB to calculate the "u" integer. srpA is also turned into an integer and used to compute the shared-secret "S" integer. S is then used to compute the shared key "srpK", which is the output of the SRP process.<br />
<br />
=== SRP Client Calculation ===<br />
<br />
While the client is waiting for the response to /auth/start, it begins its key-stretching calculations. Everything else must wait until the response to /auth/start arrives, which includes the key-stretching parameters (which are retroactively confirmed), srpSalt, and the server's generated srpB value.<br />
<br />
[[File:PICL-IdPAuth-SRP-Client.png|client-side SRP]]<br />
<br />
Once the client knows srpSalt, it computes the same "x" integer as it did in the middle of the srpVerifier calculation. It also converts srpB into an integer named "B". Then it creates a random "a" integer, uses it to compute the string "srpA", then combines srpA with the server's srpB to compute the "u" integer. It then combines the static "k", the password-derived "x", the combined "u", and the server's "B", together with some magic math, to derive the "S" integer. If everything went well, the client will compute the same "S" value as the server did. If not (the password was wrong, or the client is talking to a fake server that doesn't really know srpVerifier), then the two "S" values will not match.<br />
<br />
('''Again, it is critical that the client keep its "a" and "x" integers secret, both during and after the protocol run.''')<br />
<br />
To safely tell if the "S" values match, both client and server combine srpA, srpB, and their (independently) generated "S" strings to form a string named "M1". The client sends M1 (along with srpA) in the /session/auth/finish message. The server compares the client's copy of M1 against its own. If they match, the client knew the password and the server can safely respond with the encrypted account data. If they do not match, the client (or a man-in-the-middle attacker) did not know the password, and the client should increment a counter that can trigger defenses against online guessing attacks. The server must then return an error to the client, and '''not''' use or reveal srpK (or the correct M1) in any way.<br />
<br />
Both client and server also hash "S" into "srpK". This is the shared session key, from which specific message encryption and MAC keys are derived (as described below).<br />
<br />
=== SRP Notes ===<br />
<br />
The SRP "g" (generator) and "N" (prime modulus) should use the 2048-bit value from RFC 5054 Appendix A. 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.<br />
<br />
There are several places in SRP where integers (usually in the range 1..N-1) are converted into bytestrings, either for transmission over a wire, or to be passed into a hash function. The SRP spec is somewhat ambiguous about padding here: if the integer happens to be less than 2^2040, the simplest toString() approach will yield a '''255''' byte string, not a 256 byte string. PiCL consistently uses padding, so compatible implementations must prepend one or more NUL bytes to these short strings before transmission or hashing. The examples above were brute-forced to ensure that "srpVerifier", "srpA", "srpB", and "S" all wind up with leading zeros, to exercise the padding code in compatible implementations. If you are having problems getting your code to match these results, add some assertions to test that the stringified integers being put into hashes are exactly 256 bytes long.<br />
<br />
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 (/auth/finish).<br />
<br />
The server receives "A" in /auth/finish, 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 keyFetchToken+sessionToken as described below, returning the encrypted/MACed bundle in the response to /auth/finish.<br />
<br />
Outstanding crypto questions:<br />
<br />
* How exactly should the "a" and "b" integers be generated? The issue is of how much bias SRP can tolerate. Ideally these integers are uniformly distributed from 1 to N-1 (inclusive). The only way to obtain a purely uniform distribution from a source of random bytes is try-try-again: pick a (integral-number-of-bytes) number, compare it to the desired range, try again if it falls outside the range. If you get unlucky, this can take a lot of guesses, depending upon how close the range is to a power of two. ECDSA implementations tend to pick a number twice as long as the modulus and then modulo it down (rand(2^4096) % N), which yields a tiny fraction of a bit of bias. The cheaper approach is to do the same with a number of equal length (rand(2^2048) % N), which imposes more bias. Some SRP implementations appear to be satisfied by rand(2^256). We need more review here.<br />
* The original SRP papers defined M1=H(A+B+S) as we use here, but other implementation (in particular RFC2945) uses a curious construct that includes the username, the salt, and an odd XOR combination of N and g. We need to decide what to use for M1.<br />
<br />
== /auth/finish ==<br />
<br />
The client-side SRP calculation results in two values that are sent to the server in the /auth/finish 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.<br />
<br />
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.<br />
<br />
The server then allocates a single-use 32-byte random token named "authToken". It encrypts the token with the session key, and returns a success message with the encrypted bundle.<br />
<br />
All tokens have an associated tokenID, described below. The server needs to maintain a table that maps the tokenID to the token itself, so it can derive other values from the token later. The tokens are also associated with a specific account, so later API requests do not specify an email address or account ID.<br />
<br />
== Decrypting the /auth/finish Response ==<br />
<br />
The SRP session key ("srpK") is used to derive two other keys: respHMACkey and respXORkey.<br />
<br />
[[File:PICL-IdPAuth-encrypt-authToken.png|Decrypting the authToken]]<br />
<br />
The respXORkey is used to encrypt the authToken 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.<br />
<br />
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 returns the authToken value.<br />
<br />
= Creating a Session =<br />
<br />
For login, the single-use authToken is spent on a call to /session/create .<br />
This allocates two new (random 32-byte) tokens: a long-lived "sessionToken", and a single-use "keyFetchToken". The /session/create call returns an encrypted bundle containing the two tokens.<br />
<br />
[[File:PICL-IdPAuth-encrypt-sessionToken.png|Decrypting the sessionToken and keyFetchToken]]<br />
<br />
The authToken is used to derive four values:<br />
<br />
* tokenID<br />
* reqHMACkey<br />
* respHMACkey<br />
* respXORkey<br />
<br />
The client uses tokenID and reqHMACkey for a HAWK (https://github.com/hueniverse/hawk/) request to the "POST /session/create" API, using tokenID as "credentials.id" and reqHMACkey as "credentials.key". The server uses tokenID to look up the corresponding token, then derives reqHMACkey to validate the request. The server allocates sessionToken and keyFetchToken, concatenates them, encrypts the pair by XORing it with the derived respXORkey, and attaches a MAC generated with respHMACkey. The encrypted MACed bundle is returned to the client.<br />
<br />
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 keyFetchToken and sessionToken values.<br />
<br />
Each authToken is single-use: once a successful request has been made with it, the authToken and its corresponding ID is removed from the server's memory, and subsequent attempts to use it will return a "no such token" error. The token is consumed even if the request fails (e.g. the MAC did not match).<br />
<br />
Since the authToken can be used by multiple APIs, the server ought to maintain a table that maps the various flavors of tokenIDs (computed for the different APIs) back to the authToken. When a HAWK request with one of these IDs appears, it should look up the tokenID in the corresponding table, retrieve the authToken, compute the associated values (reqHMACkey, etc), create the response, then delete the entire row.<br />
<br />
The server can support multiple sessions per account (typically one per client device, plus perhaps others for account-management portals). There can also be multiple outstanding keyFetchTokens. The sessionToken lasts forever (until revoked by a password change or explicit revocation command), and can be used an unlimited number of times. The keyFetchToken expires after 60 seconds, and is single-use.<br />
<br />
= Obtaining keys kA and kB =<br />
<br />
Clients which have exchanged an authToken for either a sessionToken or an accountResetToken will also receive a keyFetchToken. This single-use token allows the client to retrieve kA and wrap(kB), which enables the client to encrypt and decrypt browser data (bookmarks, open-tabs, etc) correctly. As above, the keyFetchToken is used to derive tokenID, reqHMACkey, respHMACkey, and respXORkey, which are used in a HAWK request to the "GET /account/keys" API.<br />
<br />
The server pulls kA and wrap(kB) from the account table, concatenates them, encrypts the pair by XORing it with the derived respXORkey, and attaches a MAC generated with respHMACkey.<br />
<br />
[[File:PICL-IdPAuth-keys-server.png|keyFetchToken: server encrypts keys]]<br />
<br />
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 and wrap(kB) values.<br />
<br />
[[File:PICL-IdPAuth-keys-client.png|keyFetchToken: client decrypts keys]]<br />
<br />
Finally, the server-provided wrap(kB) value is simply XORed with the password-derived unwrapBKey (both are 32-byte strings) to obtain kB. There is no MAC on wrap(kB).<br />
<br />
[[File:PICL-IdPAuth-key-unwrap.png|unwrapping kB]]<br />
<br />
"kA" and "kB" enable the browser to encrypt/decrypt synchronized data records. They will be used to derive separate encryption and HMAC keys for each data collection (bookmarks, form-fill data, saved-password, open-tabs, etc). This will allow the user to share some data, but not everything, with a third party. The client may intentionally forget kA and kB (only retaining the derived keys) to reduce the power available to someone who steals their device.<br />
<br />
Note that /account/keys will not succeed until the account's email address has been verified. Also note that each keyFetchToken is single-use and short-lived.<br />
<br />
= Signing Certificates =<br />
<br />
The sessionToken is used to derive two values:<br />
<br />
* tokenID<br />
* request HMAC key<br />
<br />
<br />
[[File:PICL-IdPAuth-use-session.png|Using the sessionToken, signing certificates]]<br />
<br />
The requestHMACkey is used in a HAWK request to provide integrity over many APIs, including /certificate/sign. requestHMACkey 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.<br />
<br />
For /certificate/sign, it is critical to enable payload verification by setting options.payload=true (on both client and server). Otherwise a man-in-the-middle could submit their own public key, get it signed, and then delete the user's data on the storage servers.<br />
<br />
The following keyserver APIs require a HAWK-protected request that uses the sessionToken. In addition, some require that the account be in the "verified" state:<br />
<br />
* GET /account/devices<br />
* POST /session/destroy<br />
* GET /recovery_email/status<br />
* POST /recovery_email/resend_code<br />
* POST /certificate/sign (requires "verified" account)<br />
<br />
= Resetting The Account =<br />
<br />
The account may be reset in two circumstances: when the user changes their password, and when the user forgets their password. In both cases, the client first obtains an "accountResetToken". This token is then used to change the SRP Verifier and either reset or replace the wrap(kB) value.<br />
<br />
== Changing the Password ==<br />
<br />
When the user wishes to change their password (i.e. they still know the old password), they first use the /auth/start+/auth/finish SRP protocol safely obtain an "authToken". They they use the "/password/change/start" API to exchange the authToken for an accountResetToken and a keyFetchToken.<br />
<br />
[[File:PICL-IdPAuth-encrypt-passwordChange.png|Server encrypts passwordChange response]]<br />
<br />
The accountResetToken will be used below to set the new password safely. The keyFetchToken should be used first, to obtain kB, so the subsequent account reset can replace wrap(kB) with a new value. This allows the password-changing client to retain their class-B data.<br />
<br />
Requiring an authToken proves that the user has provided the correct account password recently. When the account is reset, all active sessions and tokens will be cancelled (disconnecting all devices from the account). The client should immediately establish a new session as described above.<br />
<br />
This API is only used when the user knows their old password: if they have forgotten the password, use the "/password/forgot" APIs below.<br />
<br />
== Handling a Forgotten Password ==<br />
<br />
When the user has forgotten their password, they can use one of their "recovery methods" to obtain an accountResetToken. For now, this means we send a random code to the email address associated with their account. The user must copy this code from the email into their client, whereupon the client will get an accountResetToken that can be delivered to the API below.<br />
<br />
Note that, since the forgotten-password client never learns kB, any class-B data will be lost. This is necessary to protect class-B data from attackers who can read the users's email but do not know the account password (including those who compromise the IdP and the keyserver itself). When using /account/reset below, they will set wrap(kB) to a string of all zeros, which means the server should generate a new random wrap(kB) (just as it does during account creation).<br />
<br />
The /password/forgot/send_code API is used to ask the server to send a recovery code. This takes a recovery method, which for now is just an email address. This API is unauthenticated (after all, the user who has forgotten their password knows nothing but their email address). The server marks the corresponding account as "pending recovery", allocates a random forgotPasswordToken for the account, creates a recovery code, and sends the code (with instructions) via email. The API returns forgotPasswordToken to the client.<br />
<br />
The user must copy the recovery code into the same browser where they started the process. The client then submits the code to /password/forgot/verify_code along with the forgotPasswordToken they received. If they match, the server allocates a accountResetToken and returns it to the client. If they do not match, the server increments a counter (which is used to decide if an online guessing attack is happening).<br />
<br />
forgotPasswordToken can be used three times before it is exhausted. If the user guesses incorrectly this often, the client must call send_code again to get a new token and code. Each account has at most one token+code active at a time.<br />
<br />
The recovery code is initially a random 8-digit decimal number. If an attacker tries to sign in as someone else, hits the "forgot my password" button, then submits a guess to /password/forgot/verify_code, they will have a 1-in-100-million chance of success. If the server detects too many wrong guesses, it should increase the length of new codes. Another defensive technique is to require that users click an email link before being given the code: the server is told when the link is clicked, so the code will not be enabled until the email has been read. It remains to be seen whether this will be sufficient.<br />
<br />
The exact thresholds are TBD, but a nominal goal is to keep the chances of any attack succeeding to below 1-in-a-million per year. To achieve this, we can tolerate 100 verify_code failures in a single year before we must increase the length of the code.<br />
<br />
== Using accountResetToken ==<br />
<br />
An accountResetToken, obtained through either of the methods above, is used to invoke the /account/reset API. If the request is accepted, the server replaces the account password with a new one, updates the wrap(kB) value, cancels all active sessions and tokens (disconnecting all devices from the account), and sends a "your password has been changed" email to the user.<br />
<br />
If the client knew the old password, it can supply a new wrap(kB) value that will yield the same kB key as before, so no data will be lost. If the client did *not* know the old password, then it will supply a wrap(kB) of all-zeros, which tells the server to generate a new random wrap(kB) (just like during account creation), which means the class-B data will be lost.<br />
<br />
The client puts their new password through the same stretching procedure as described in the new-account section above, resulting in a new srpVerifier and unwrapBKey. If they knew the old kB, they XOR it with the new unwrapBKey to obtain a new wrap(kB).<br />
<br />
/account/reset needs request confidentiality, since the arguments include the newly wrapped kB value and the new SRP verifier, both of which enable a brute-force attack against the password. HAWK provides request integrity. The response is a single "ok" or "fail", conveyed by the HTTP headers, so we do not require response confidentiality, and can live without response integrity.<br />
<br />
So the single-use resetToken is used to derive three values:<br />
<br />
* tokenID<br />
* request HMAC key<br />
* request XOR key<br />
<br />
<br />
[[File:PICL-IdPAuth-encrypt-resetAccount.png|Client encrypts resetAccount request]]<br />
<br />
The request data will contain wrap(kB) and the new SRP verifier, concatenated together. Since we always pad the SRP verifier to the full (256-byte) group length, both pieces are fixed-length. We generate enough reqXORkey bytes to cover both values.<br />
<br />
The request data is XORed with requestXORkey, then delivered in the body of a HAWK request that uses tokenID as credentials.id and requestHMACkey as credentials.key . Note: it is critical to include the request body in the HAWK integrity check (options.payload=true, on both client and server), otherwise a man-in-the-middle could substitute their own SRP verifier, giving them control over the account (access to the user's class-A data, and a brute-force attack on their password).<br />
<br />
The client submits other values in the same request:<br />
<br />
* stretchParams<br />
* mainKDFSalt<br />
* srpSalt<br />
<br />
<br />
These values do not require confidentiality, so are not included in the encrypted bundle. They are still protected by the HAWK integrity check. Note that the server should assert that both salts are different than the previously stored values.<br />
<br />
After using /account/reset, clients should immediately perform the login protocol from above. If the old password was forgotten, this is necessary to fetch kA. In either case, a new sessionToken is required, since old sessions and tokens are revoked by /account/reset. Clients should retain the new srpPassword value during this process to avoid needing to run the lengthy key-stretching routine a second time.<br />
<br />
= Deleting The Account =<br />
<br />
When the user wishes to completely delete their account, the browser needs to perform two actions:<br />
<br />
* contact the storage servers and delete all records and collections<br />
* contact the keyserver and delete the account information<br />
<br />
The user should be prompted for their password as confirmation (i.e. a browser in the normal attached-and-synchronizing state should not be able to erase the account information: it must acquire a new authToken first).<br />
<br />
The device then obtains an authToken as described above, then spends it on a HAWK-protected request to the /account/destroy endpoint. This request contains no body and returns only a success code.<br />
<br />
[[File:PICL-IdPAuth-deleteAccount.png|Deleting the Account]]<br />
<br />
= Crypto Notes =<br />
<br />
Strong entropy is needed in the following places:<br />
<br />
* (client) creation of private "a" value inside SRP<br />
* (server) initial creation of kA and wrap(kB)<br />
* (server) creation of private "B" value inside SRP<br />
* (server) creation of signToken and resetToken<br />
<br />
<br />
On the server, code should get entropy from /dev/urandom via a function that uses it, like "crypto.randomBytes()" in node.js or "os.urandom()" in python. On the client, code should combine local entropy with some fetched from the keyserver via getEntropy(), to guard against failures in the local entropy pool. Something like HKDF(SKM=localEntropy+remoteEntropy, salt="", context=KW("mergeEntropy")).<br />
<br />
An HKDF-based stream cipher is used to protect the response for getToken2(), and the request for resetAccount(). HKDF is used to create a number of random bytes equal to the length of the message, then these are XORed with the plaintext to produce the ciphertext. An HMAC is then computed from the ciphertext, to protect the integrity of the message.<br />
<br />
HKDF, like any KDF, is defined to produce output that is indistinguishable from random data ("The HKDF Scheme", http://eprint.iacr.org/2010/264.pdf , by Hugo Krawczyk, section 3). XORing a plaintext with a random keystream to produce ciphertext is a simple and secure approach to data encryption, epitomized by AES-CTR or a stream cipher (http://cr.yp.to/snuffle/design.pdf). HKDF is not the fastest way to generate such a keystream, but it is safe, easy to specify, and easy to implement (just HMAC and XOR).<br />
<br />
Each keystream must be unique. SRP is defined to produce a random session key for each run (as long as at least one of the sides provides a random ephemeral key). We define resetToken to be a single-use randomly-generated value. Hence our two HKDF-XOR keystreams will be unique.<br />
<br />
A slightly more-traditional alternative would be to use AES-CTR (with the same HMAC-SHA256 used here), with a randomly-generated IV. This is equally secure, but requires implementors to obtain an AES library (with CTR mode, which does not seem to be universal). An even more traditional technique would be AES-CBC, which introduces the need for padding and a way to specify the length of the plaintext. The additional specification complexity, plus the library load, leads me to prefer HKDF+XOR.<br />
<br />
kB is equal to the XOR of wrapKey (which is a deterministic function of the user's email address, password, mainSalt, and the stretching parameters) and the server's randomly-generated wrap(kB) value, making kB a random value too. Using XOR as a wrapping function allows us to avoid sending kB or wrap(kB) in the initial createAccount arguments, which are not protected by SRP, and thus would enable an eavesdropper to mount a dictionary attack on the password (using wrap(kB) as their oracle).<br />
<br />
Likewise, allowing the server to generate kA avoids exposure during createAccount. The only point of vulnerability is a forgotten-password resetAccount() message, if an eavesdropper can learn the resetToken. In this case, the attacker might either use the resetToken themselves (then use signToken to learn kA and wrap(kB)), or passively decrypt the resetAccount() arguments to retrieve just wrap(kB). Given wrap(kB) and some encrypted browser data, the attacker can guess passwords (and derive kB, etc) until the data decrypts properly.<br />
<br />
To make this technique safe, any time kB or the password is changed, the mainSalt should be changed too. Otherwise knowledge of both wrap(old-kB) and old-kB would reveal wrapKey, making it easy to deduce the new kB. Changing mainSalt causes wrapKey to change too, preventing this.<br />
<br />
mainSalt is incorporated at the end of the stretching process to allow it to proceed in parallel with the getToken1() call that retrieves the salt. The inputs to the lengthy stretch come entirely from the user (email and password) or are optimistically (stretching parameters). This speedup seems more important than the minor security benefit of including the salt at the beginning of the stretch.<br />
<br />
There is no MAC on wrap(kB). If the keyserver chooses to deliver a bogus wrap(kB) or kA, the client will discover the problem a moment later when it talks to a storage server and attempts to retrieve data from an unrecognized collection-ID (since we intend to derive collection-IDs from the key used to encrypt their data, which will be derived from kA or kB as appropriate). It might be useful to add a checksum to kA and wrap(kB) to detect accidental corruption (e.g. store and deliver kA+SHA256(kA)), but this doesn't protect against intentional changes. We omit this checksum for now, assuming that disks will be reliable enough to let us never experience such failures.<br />
<br />
We use scrypt without a per-user salt. This is safe because the "password" input to scrypt is already diversified by the user's email address. The intention is to allow the scrypt-helper to run on anonymous data, so that an attacker who compromises this helper cannot easily learn the email addresses of the partially-stretched K1 values that it is receiving, confounding their attack.<br />
<br />
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. <br />
For /certificate/sign, we do not need request confidentiality or response confidentiality, since the client's pubkey and the resulting certificate will both be exposed over a similar SSL connection to the storage server later. And it is sufficient to rely on the response integrity provided by SSL, since the client can verify the returned certificate for itself. For the other keyserver APIs protected by HAWK, these properties are either unnecessary, or are provided by additional mechanisms.<br />
<br />
<br />
= Proof-Of-Work =<br />
<br />
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(). The server can control how much work is required.<br />
<br />
The getToken1() call looks for a "X-PiCL-PoW:" HTTP header. Most of the time, clients don't supply this header. But if the server responds to the getToken1() call with an error that indicates PoW is required, clients must create a valid PoW string and include it as the value of an "X-PiCL-PoW:" header in their next call to getToken1().<br />
<br />
The server's error message includes two parameters. The first is a "prefix string": the client's PoW string is required to begin with this prefix. The second is a "threshold hash". SHA256(PoWString) is required to be lexicographically earlier than the thresholdHashString (i.e. the numerical value of its hash must be closer to zero than the threshold). The client is expected to concatenate the prefix with a counter, then repeatedly increment the counter and hash the result until they meet the threshold, then re-submit their getToken1() request with the combined prefix+counter string in the header. If the client has spent more than e.g. 10 seconds doing this, the client should probably help the user cancel the operation and try again.<br />
<br />
When a server is under a DoS attack (either via some manual configuration tool or sensed automatically), it should start requiring valid unique X-PiCL-PoW headers. The server should initially require very little work, by using a threshold hash with just a few leading zero bits. If this is insufficient to reduce the attack volume, the threshold should be lowered, requiring even more work (from both the attacker and legitimate clients).<br />
<br />
The server should create a prefix string that contains a parseable timestamp and a random nonce (e.g. "%d-%d-" % (int(time.time()), b32encode(os.urandom(8)))). The server should also decide on a cutoff time (perhaps ten minutes ago). Each server must then maintain a table of "old PoW strings" to prevent replay attacks (these do not need to be shared among all servers: an in-RAM cache is fine).<br />
<br />
When the server receives a proposed PoW string, it first splits off the leading timestamp, and if the timestamp is older than the cutoff time, it rejects the string (either by dropping the connection, or returning a new "PoW required" error if it's feeling nice). Then it hashes the whole string and compares it against the threshold, rejecting those which fail to meet the threshold. Finally, for strings that pass the hash threshold, it checks the "old strings" table, and rejects any that appear on that list.<br />
<br />
If the PoW string makes it past all these checks, the server should add the string to the "old strings" table, then accept the request (i.e. compute an srpB value and add a session-id table entry for the request).<br />
<br />
The old-strings table check should be optimized to reject present strings quickly (i.e. if we are under attack, we should expect to see lots of duplicates of the same string, and must minimize the work we do when this occurs).<br />
<br />
The server can remove values from the old-strings table that have timestamps older than the cutoff time. The server can also discard values at other times (to avoid consuming too much memory), without losing anything but protection against resource consumption.<br />
<br />
Other notes:<br />
<br />
The server-side code for this can be deferred until we care to have a response to a DoS attack. However the client-side code for this must be present from day one, otherwise we won't be able to turn on the defense later without fear of disabling legitimate old clients.<br />
<br />
The server should perform as little work as possible before rejecting a token. Every extra CPU cycle it spends in this path is increasing the DoS attack amplification factor.<br />
<br />
The nonce in the prefix string exists to make sure that two successive clients get different prefixes, and thus do not come up with the same counter value (and inadvertently create identical strings, looking like a replay attack). If this proved annoying or expensive, we could instead obligate clients to produce their own nonce.<br />
<br />
TBD: Is this worth it? Should the PoW string go into an HTTP header? (I want it to be cheap to extract, and not clutter logs). Should the error response be a distinctive HTTP error code so our monitoring tools can easily count them? We can also use this feature to slow down online guessing attacks (i.e. trigger it either when getToken1 is called too much or when getToken2 produces too many errors). Since getToken1() includes an email address, we could also requires PoWs for some addresses (e.g. those we know to be under attack) but not others.<br />
<br />
= Glossary =<br />
<br />
This defines some of the jargon we've developed for this protocol.<br />
<br />
* data classes: each type of browser data (bookmarks, passwords, history, etc) can be assigned, by the user, to either class-A or class-B<br />
* class-A: data assigned to this class can be recovered, even if the user forgets their password, by proving control over an email address and resetting the account. It can also be read by Mozilla (since it runs the keyserver and knows kA), or by the user's IdP (by resetting the account without the user's permission).<br />
* class-B: data in this class cannot be recovered if the password is forgotten. It cannot be read by the IdP. Mozilla (via the keyserver) cannot read this data, but can attempt a brute-force dictionary attack against the password.<br />
* kA: the master key for data stored as "class-A", a 32-byte binary string. Individual encryption keys for different datatypes are derived from kA.<br />
* kB: the master key for data stored as "class-B", a 32-byte binary string.<br />
* wrap(kB): an encrypted copy of kB. The keyserver stores wrap(kB) and never sees kB itself. The client (browser) uses a key derived from the user's password to decrypt wrap(kB), obtaining the real kB.<br />
* sessionToken: a long-lived per-device token which allows the device to obtained signed BrowserID certificates for the account's identity (GUID@picl-something.org). This token remains valid until the user revokes it (either by changing their password, or triggering some kind of "revoke a specific device" or "revoke all devices" function).<br />
<br />
= Test Vectors =<br />
<br />
The following example uses a non-ASCII email address of "andré@example.org" (with an accented "e", UTF8 encoding is 616e6472c3a9406578616d706c652e6f7267) and a non-ascii password of "pässwörd" (with accents on "a" and "o", UTF8 encoding is 70c3a4737377c3b67264).<br />
<br />
These test vectors were produced by the python code in https://github.com/warner/picl-spec-crypto . The diagrams may lag behind the latest version of that code.<br />
<br />
== stretch-KDF ==<br />
<br />
email:<br />
616e6472c3a94065<br />
78616d706c652e6f<br />
7267<br />
<br />
password:<br />
70c3a4737377c3b6<br />
7264<br />
<br />
K1 (scrypt input):<br />
f84913e3d8e6d624<br />
689d0a3e9678ac8d<br />
cc79d2c2f3d96414<br />
88cd9d6ef6cd83dd<br />
<br />
K2 (scrypt output):<br />
5b82f146a6412692<br />
3e4167a0350bb181<br />
feba61f63cb17140<br />
12b19cb0be0119c5<br />
<br />
stretchedPW:<br />
c16d46c31bee242c<br />
b31f916e9e38d60b<br />
76431d3f5304549c<br />
c75ae4bc20c7108c<br />
<br />
== main-KDF ==<br />
<br />
mainSalt (normally random):<br />
00f0000000000000<br />
0000000000000000<br />
0000000000000000<br />
000000000000034d<br />
<br />
srpPW:<br />
00f9b71800ab5337<br />
d51177d8fbc682a3<br />
653fa6dae5b87628<br />
eeec43a18af59a9d<br />
<br />
unwrapBKey:<br />
6ea660be9c89ec35<br />
5397f89afb282ea0<br />
bf21095760c8c500<br />
9bbcc894155bbe2a<br />
<br />
internal x (base 10):<br />
8192518690918<br />
99580124814080709381476194749939<br />
03899664126296984459627523279550<br />
<br />
internal x (hex):<br />
b5200337cc3f3f92<br />
6cdddae0b2d31029<br />
c069936a844aff58<br />
779a545be89d0abe<br />
<br />
v (verifier as number) (base 10):<br />
114649<br />
57230405843056840989945621595830<br />
71784395917725741221739574165799<br />
54316134303691657140298181419198<br />
87853709633756255809680435884948<br />
69849281177012209169281795507853<br />
57610332070005048463659745521969<br />
83218225819721112680718485091921<br />
64608360806562626442477160609654<br />
43167308814558974899899506977051<br />
96721477608178869100211706638584<br />
53875100985456239693728258285562<br />
04889672594983678412848291529879<br />
88548996842770025110751388952323<br />
22170663943486107183421205517476<br />
84831590615660554713667726412525<br />
73641352721966728239512914666806<br />
49625530438034148797508015907639<br />
67594925530663571631035463732161<br />
30193328802116982288883318596822<br />
<br />
== SRP Verifier ==<br />
<br />
k (base 10):<br />
259003859907<br />
09503006915442163037721228467470<br />
35652616593381637186118123578112<br />
<br />
srpSalt (normally random):<br />
00f1000000000000<br />
0000000000000000<br />
0000000000000000<br />
0000000000000179<br />
<br />
srpVerifier:<br />
00173ffa0263e63c cfd6791b8ee2a40f<br />
048ec94cd95aa8a3 125726f9805e0c82<br />
83c658dc0b607fbb 25db68e68e93f265<br />
8483049c68af7e82 14c49fde2712a775<br />
b63e545160d64b00 189a86708c69657d<br />
a7a1678eda0cd79f 86b8560ebdb1ffc2<br />
21db360eab901d64 3a75bf1205070a57<br />
91230ae56466b8c3 c1eb656e19b794f1<br />
ea0d2a077b3a7553 50208ea0118fec8c<br />
4b2ec344a05c66ae 1449b32609ca7189<br />
451c259d65bd15b3 4d8729afdb5faff8<br />
af1f3437bbdc0c3d 0b069a8ab2a959c9<br />
0c5a43d42082c774 90f3afcc10ef5648<br />
625c0605cdaace6c 6fdc9e9a7e6635d6<br />
19f50af773452247 0502cab26a52a198<br />
f5b00a2798589165 07b0b4e9ef9524d6<br />
<br />
== SRP B ==<br />
<br />
private b (normally random) (base 10):<br />
1198277<br />
66042000957856349411550091527197<br />
08125226960544768993643095227800<br />
29105361555030352774505625606029<br />
71778328003253459733139844872578<br />
33596964143417216389157582554932<br />
02841499373672188315534280693274<br />
23189198736863575142046053414928<br />
39548731879043135718309539706489<br />
29157321423483527294767988835942<br />
53343430842313006332606344714480<br />
99439808861069316482621424231409<br />
08830704769167700098392968117727<br />
43420990997238759832829219109897<br />
32876428831985487823417312772399<br />
92628295469389578458363237146486<br />
38545526799188280210660508721582<br />
00403102624831815596140094933216<br />
29832845626116777080504444704039<br />
04739431335617585333671378812943<br />
<br />
private b (hex):<br />
00f3000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 000000000000000f<br />
<br />
transmitted srpB:<br />
0022ce5a7b9d8127 7172caa20b0f1efb<br />
4643b3becc535664 73959b07b790d3c3<br />
f08650d5531c19ad 30ebb67bdb481d1d<br />
9cf61bf272f84398 48fdda58a4e6abc5<br />
abb2ac496da5098d 5cbf90e29b4b110e<br />
4e2c033c70af7392 5fa37457ee13ea3e<br />
8fde4ab516dff1c2 ae8e57a6b264fb9d<br />
b637eeeae9b5e43d faba9b329d3b8770<br />
ce89888709e02627 0e474eef822436e6<br />
397562f284778673 a1a7bc12b6883d1c<br />
21fbc27ffb3dbeb8 5efda279a69a1941<br />
4969113f10451603 065f0a0126666456<br />
51dde44a52f4d8de 113e2131321df1bf<br />
4369d2585364f9e5 36c39a4dce33221b<br />
e57d50ddccb4384e 3612bbfd03a268a3<br />
6e4f7e01de651401 e108cc247db50392<br />
<br />
== SRP A ==<br />
<br />
private a (normally random) (base 10):<br />
1193346<br />
47663227291363113405741243413916<br />
43482736314616601220006703889414<br />
28162541137108417166380088052095<br />
43910927476491099816542561560345<br />
50331133015255005622124012256352<br />
06121987030570656676375703406470<br />
63422988042473190059156975005813<br />
46381864669664357382020200036915<br />
26156674010218162984912976536206<br />
14440782978764393137821956464627<br />
16314542157937343986808167341567<br />
89864323268060014089757606109012<br />
50649711198896213496068605039486<br />
22864591676298304745954690086093<br />
75374681084741884719851454277570<br />
80362211874088739962880012800917<br />
05751238004976540634839106888223<br />
63866455314898189520502368799907<br />
19946264951520393624479315579863<br />
<br />
private a (hex):<br />
00f2000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 0000000000000000<br />
0000000000000000 000000000000d3d7<br />
<br />
transmitted srpA:<br />
007da76cb7e77af5 ab61f334dbd5a958<br />
513afcdf0f47ab99 271fc5f7860fe213<br />
2e5802ca79d2e5c0 64bb80a38ee08771<br />
c98a937696698d87 8d78571568c98a1c<br />
40cc6e7cb101988a 2f9ba3d65679027d<br />
4d9068cb8aad6ebf f0101bab6d52b5fd<br />
fa81d2ed48bba119 d4ecdb7f3f478bd2<br />
36d5749f2275e948 4f2d0a9259d05e49<br />
d78a23dd26c60bfb a04fd346e5146469<br />
a8c3f010a627be81 c58ded1caaef2363<br />
635a45f97ca0d895 cc92ace1d09a99d6<br />
beb6b0dc0829535c 857a419e834db128<br />
64cd6ee8a843563b 0240520ff0195735<br />
cd9d316842d5d3f8 ef7209a0bb4b54ad<br />
7374d73e79be2c39 75632de562c59647<br />
0bb27bad79c3e2fc ddf194e1666cb9fc<br />
<br />
== SRP key-agreement ==<br />
<br />
u:<br />
b284aa1064e87751 50da6b5e2147b47c<br />
a7df505bed94a6f4 bb2ad873332ad732<br />
<br />
S:<br />
0092aaf0f527906a a5e8601f5d707907<br />
a03137e1b601e04b 5a1deb02a981f4be<br />
037b39829a27dba5 0f1b27545ff2e287<br />
29c2b79dcbdd32c9 d6b20d340affab91<br />
a626a8075806c26f e39df91d0ad979f9<br />
b2ee8aad1bc783e7 097407b63bfe58d9<br />
118b9b0b2a7c5c4c debaf8e9a460f4bf<br />
6247b0da34b760a5 9fac891757ddedca<br />
f08eed823b090586 c63009b2d740cc9f<br />
5397be89a2c32cdc fe6d6251ce11e44e<br />
6ecbdd9b6d93f30e 90896d2527564c7e<br />
b9ff70aa91acc0ba c1740a11cd184ffb<br />
989554ab58117c21 96b353d70c356160<br />
100ef5f4c28d19f6 e59ea2508e8e8aac<br />
6001497c27f362ed bafb25e0f045bfdf<br />
9fb02db9c908f103 40a639fe84c31b27<br />
<br />
M1:<br />
27949ec1e0f16256<br />
33436865edb037e2<br />
3eb6bf5cb91873f2<br />
a2729373c2039008<br />
<br />
srpK:<br />
e68fd0112bfa31dc<br />
ffc8e9c96a1cbadb<br />
4c3145978ff35c73<br />
e5bf8d30bbc7499a<br />
<br />
== /auth ==<br />
<br />
srpK:<br />
e68fd0112bfa31dc<br />
ffc8e9c96a1cbadb<br />
4c3145978ff35c73<br />
e5bf8d30bbc7499a<br />
<br />
respHMACkey:<br />
6584613597ef012f<br />
f1752b7869f01d03<br />
c72547a7b7199681<br />
531d9df1991edf23<br />
<br />
respXORkey:<br />
455835926ae37a1b<br />
627bd16affbeeab6<br />
27ecc737121826ca<br />
4a2bac2c100bf417<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
plaintext:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
ciphertext:<br />
253957f10e861c7c<br />
0a12bb0193d384d9<br />
579db544666d50bd<br />
3252d6576c768a68<br />
<br />
MAC:<br />
a98c87f5769ab4cc<br />
ca3df863faeb217e<br />
b16ddc29d712b301<br />
12b446324ee806d6<br />
<br />
response:<br />
253957f10e861c7c<br />
0a12bb0193d384d9<br />
579db544666d50bd<br />
3252d6576c768a68<br />
a98c87f5769ab4cc<br />
ca3df863faeb217e<br />
b16ddc29d712b301<br />
12b446324ee806d6<br />
<br />
== /session ==<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
tokenID:<br />
6dcae8ff8f55a793<br />
a0fa1ed31115451b<br />
4df233b3a0641cc6<br />
18ecadfd1fe4a691<br />
<br />
reqHMACkey:<br />
1640a4e6bc8c8e54<br />
858be9960a8b0740<br />
fa06effdf169246f<br />
52012ae868fc6c48<br />
<br />
respHMACkey:<br />
7f3e075e74523ced<br />
fa817c2fa4ae97e1<br />
e51da38d7a992b66<br />
8a35c86af946b155<br />
<br />
respXORkey:<br />
02977a9167830705<br />
74b610cc25320262<br />
175b45fbd7b26438<br />
f9e200abc029f14e<br />
f38399314b172f1e<br />
e928fcdcd194ab19<br />
92433cab0e94569d<br />
bf623b46dd9fbf55<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
sessionToken:<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
plaintext:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
ciphertext:<br />
8216f812e3068182<br />
fc3f9a47a9bf8ced<br />
87cad7684327f2af<br />
617b9a305cb46fd1<br />
53223b92efb289b9<br />
418156777d3905b6<br />
22f28e18ba21e02a<br />
07db81fd612201ea<br />
<br />
MAC:<br />
639fd132f637abd3<br />
ecd2482ccf11ed76<br />
8cfd6979e1954046<br />
1e8ef5204e66c542<br />
<br />
response:<br />
8216f812e3068182<br />
fc3f9a47a9bf8ced<br />
87cad7684327f2af<br />
617b9a305cb46fd1<br />
53223b92efb289b9<br />
418156777d3905b6<br />
22f28e18ba21e02a<br />
07db81fd612201ea<br />
639fd132f637abd3<br />
ecd2482ccf11ed76<br />
8cfd6979e1954046<br />
1e8ef5204e66c542<br />
<br />
== /account/keys ==<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
tokenID:<br />
d010c94c753c012c<br />
d6801e8beb1aa6cc<br />
3da9ea3de3de1dee<br />
32785dbd99a579e8<br />
<br />
reqHMACkey:<br />
1707b05908acc4dc<br />
cda5b8304d9500d0<br />
8c53e00c31672a53<br />
490dfb5ef2934060<br />
<br />
respHMACkey:<br />
31d0c12186b76897<br />
c3351878a65097cf<br />
d595da4ce48e69a2<br />
485ff1a77c71b0d0<br />
<br />
respXORkey:<br />
eed35591e1f1c43b<br />
7cd604e371b9cfb7<br />
a980c9a36fa737c6<br />
a48c5d60a89fc291<br />
4ec1a2150a0777b7<br />
9a1e8499058cd17a<br />
ebc1441db8b3bf18<br />
2cd0aefa92482692<br />
<br />
kA:<br />
2021222324252627<br />
28292a2b2c2d2e2f<br />
3031323334353637<br />
38393a3b3c3d3e3f<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
plaintext:<br />
2021222324252627<br />
28292a2b2c2d2e2f<br />
3031323334353637<br />
38393a3b3c3d3e3f<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
ciphertext:<br />
cef277b2c5d4e21c<br />
54ff2ec85d94e198<br />
99b1fb905b9201f1<br />
9cb5675b94a2fcae<br />
0e80e0564e4231f0<br />
d257ced249c19f35<br />
bb90164eece6e94f<br />
7489f4a1ce1578cd<br />
<br />
MAC:<br />
86f1c57d2e7f6c97<br />
8181684e189b710f<br />
dd26a3f34e3aaed8<br />
64be9577ae81a256<br />
<br />
response:<br />
cef277b2c5d4e21c<br />
54ff2ec85d94e198<br />
99b1fb905b9201f1<br />
9cb5675b94a2fcae<br />
0e80e0564e4231f0<br />
d257ced249c19f35<br />
bb90164eece6e94f<br />
7489f4a1ce1578cd<br />
86f1c57d2e7f6c97<br />
8181684e189b710f<br />
dd26a3f34e3aaed8<br />
64be9577ae81a256<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
unwrapBKey:<br />
6ea660be9c89ec35<br />
5397f89afb282ea0<br />
bf21095760c8c500<br />
9bbcc894155bbe2a<br />
<br />
kB:<br />
2ee722fdd8ccaa72<br />
1bdeb2d1b76560ef<br />
ef705b04349d9357<br />
c3e592cf4906e075<br />
<br />
== use session (certificate/sign, etc) ==<br />
<br />
sessionToken:<br />
a0a1a2a3a4a5a6a7<br />
a8a9aaabacadaeaf<br />
b0b1b2b3b4b5b6b7<br />
b8b9babbbcbdbebf<br />
<br />
tokenID:<br />
639503a218ffbb62<br />
983e9628be5cd64a<br />
0438d0ae81b2b9da<br />
deb900a83470bc6b<br />
<br />
reqHMACkey:<br />
3a0188943837ab22<br />
8fe74e759566d0e4<br />
837cbcc7494157aa<br />
c4da82025b2811b2<br />
<br />
== /password/change ==<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
tokenID:<br />
cafc36360afd92de<br />
5ca21800022a9af1<br />
3a5766b91bd82fd4<br />
0eaa5b6e01489796<br />
<br />
reqHMACkey:<br />
b07c0cf4553e44ff<br />
fe991caa2546b50d<br />
895fb9ac8f8746d2<br />
d29119d9616de193<br />
<br />
respHMACkey:<br />
d2ddfefd1913fa34<br />
48e18abda9b54c92<br />
43fd51bf14dc9091<br />
2179269c0e958a04<br />
<br />
respXORkey:<br />
dcc5425e13b876ea<br />
f1d3aa95a4735622<br />
46994088d86adb5a<br />
526d9f1f5d170254<br />
456dd26dcc54483e<br />
f489d55097b69028<br />
8826f0cf1985a6ad<br />
e3e83461517c8d49<br />
<br />
keyFetchToken:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
<br />
accountResetToken:<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
plaintext:<br />
8081828384858687<br />
88898a8b8c8d8e8f<br />
9091929394959697<br />
98999a9b9c9d9e9f<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
ciphertext:<br />
5c44c0dd973df06d<br />
795a201e28fed8ad<br />
d608d21b4cff4dcd<br />
caf40584c18a9ccb<br />
85ac10ae08918ef9<br />
3c401f9b5b7b5ee7<br />
58f7221ccd50707a<br />
3b31eeba8da15396<br />
<br />
MAC:<br />
cc3053fe922268d7<br />
9c0dd6eb74bd40f5<br />
07ae2d587483b864<br />
8ef771b699dd39d9<br />
<br />
response:<br />
5c44c0dd973df06d<br />
795a201e28fed8ad<br />
d608d21b4cff4dcd<br />
caf40584c18a9ccb<br />
85ac10ae08918ef9<br />
3c401f9b5b7b5ee7<br />
58f7221ccd50707a<br />
3b31eeba8da15396<br />
cc3053fe922268d7<br />
9c0dd6eb74bd40f5<br />
07ae2d587483b864<br />
8ef771b699dd39d9<br />
<br />
== /account/reset ==<br />
<br />
accountResetToken:<br />
c0c1c2c3c4c5c6c7<br />
c8c9cacbcccdcecf<br />
d0d1d2d3d4d5d6d7<br />
d8d9dadbdcdddedf<br />
<br />
tokenID:<br />
a6857e5d53d35073<br />
d50ef2ce2c4dd747<br />
32bb2eae1af5bf79<br />
618ed945e1310792<br />
<br />
reqHMACkey:<br />
47fab27352ee6b48<br />
33938d76519bbdb8<br />
ac7293f8b5e74335<br />
6fdd1d5edf39f52d<br />
<br />
reqXORkey:<br />
82ed612313a11673 95108d7d379b2029<br />
7a539ce9d3861e95 1bf5a9b9cdbfb332<br />
bd6aba056ce0c568 2c5a93963446b1b4<br />
7397c8c24f3a1d67 2a0ddc856474f5b1<br />
33ab884ce33335c1 5578a1a7302933cb<br />
458fbee0a5e52414 c914beb97568a30c<br />
28364dc8fb03ae7c 76a2f324a9a1cee6<br />
71b74aa8906d0e03 39fb52a1bf2b1ef5<br />
ab5d883295db62af 20701cb3af42a09e<br />
c76cda585ab5644b 7250ef7b780537e5<br />
b3e784d37a118bd6 57a0fe29ec6e5cd3<br />
325be8e1d8a3dd71 b360ea266757e463<br />
ada6b0a7a85a8ac0 eed618d9f6ee91ab<br />
1d2f714f224d67db 46843c4e3339de15<br />
efe0297a45f9fe0d 6d768b5c589a290f<br />
11f03237192cc0a3 a02645a810d83bb1<br />
84d582bfb15d2393 3fa4805374da62c6<br />
a2c887b157285c6a 79b47156c9abe02e<br />
<br />
wrapkB:<br />
4041424344454647<br />
48494a4b4c4d4e4f<br />
5051525354555657<br />
58595a5b5c5d5e5f<br />
<br />
newSRPv:<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
1111111111111111<br />
<br />
plaintext:<br />
4041424344454647 48494a4b4c4d4e4f<br />
5051525354555657 58595a5b5c5d5e5f<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
1111111111111111 1111111111111111<br />
<br />
ciphertext:<br />
c2ac236057e45034 dd59c7367bd66e66<br />
2a02ceba87d348c2 43acf3e291e2ed6d<br />
ac7bab147df1d479 3d4b82872557a0a5<br />
6286d9d35e2b0c76 3b1ccd947565e4a0<br />
22ba995df22224d0 4469b0b6213822da<br />
549eaff1b4f43505 d805afa86479b21d<br />
39275cd9ea12bf6d 67b3e235b8b0dff7<br />
60a65bb9817c1f12 28ea43b0ae3a0fe4<br />
ba4c992384ca73be 31610da2be53b18f<br />
d67dcb494ba4755a 6341fe6a691426f4<br />
a2f695c26b009ac7 46b1ef38fd7f4dc2<br />
234af9f0c9b2cc60 a271fb377646f572<br />
bcb7a1b6b94b9bd1 ffc709c8e7ff80ba<br />
0c3e605e335c76ca 57952d5f2228cf04<br />
fef1386b54e8ef1c 7c679a4d498b381e<br />
00e12326083dd1b2 b13754b901c92aa0<br />
95c493aea04c3282 2eb5914265cb73d7<br />
b3d996a046394d7b 68a56047d8baf13f<br />
<br />
== /account/destroy ==<br />
<br />
authToken:<br />
6061626364656667<br />
68696a6b6c6d6e6f<br />
7071727374757677<br />
78797a7b7c7d7e7f<br />
<br />
tokenID:<br />
b2512ff41c4e6d8a<br />
beb3bda37e326f51<br />
cf4efdbf90e50e77<br />
029be2563884b9fe<br />
<br />
reqHMACkey:<br />
75cfa782c19e41f9<br />
c7e125f3dc4c3bf1<br />
0a77c93a9999e06f<br />
b2646b3038e4ea44<br />
<br />
= Keyserver Protocol Summary =<br />
<br />
* POST /account/create (email,srpV,srpSalt) -> ok (server sends verification email)<br />
** creates a user account<br />
* GET /account/devices [sessionToken] () -> list of devices<br />
* GET /account/keys [keyFetchToken,needs-verf] () -> kA/wrap(kB)<br />
** single-use, only if email is verified, encrypted results<br />
* POST /account/reset [authed+encrypted by accountResetToken] (wrap(kB),srpV,srpSalt) -> ok<br />
** single-use, does not require email to be verified, revoke all tokens for account, send notification email to user<br />
* POST /account/delete [authToken] () -> ok, account deleted<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authToken] () -> keyFetchToken, sessionToken<br />
* POST /session/destroy [sessionToken] () -> ok<br />
** for detaching a device, destroy all tokens<br />
* POST /recovery_email/status [sessionToken] () -> "verified" status of email<br />
** use "Accept: text/event-stream" header for server-sent-events; server will send "update" event with the new content of the resource any time it changes.<br />
* POST /recovery_email/resend_code [sessionToken] () -> re-send verification email<br />
* POST /recovery_email/verify_code (code) -> set "verified" flag<br />
** this code will come from a clickable link and is an unauthenticated endpoint<br />
** this could maybe take the recovery method if that would be helpful<br />
** sets verified flag on recovery method<br />
* POST /certificate/sign [sessionToken,needs-verf] (pubkey) -> cert<br />
** only if recovery email is verified<br />
* POST /password/change/start [authToken,needs-verf] () -> accountResetToken, keyFetchToken<br />
* POST /password/forgot/send_code () -> forgotPasswordToken<br />
** sends code to recovery method (email for now, maybe SMS later)<br />
** this is a short code, not a clickable link<br />
* POST /password/forgot/resend_code (forgotPasswordToken) -> re-sends code<br />
* POST /password/forgot/verify_code (forgotPasswordToken, code) -> accountResetToken<br />
** sets verified flag on recovery method<br />
* POST /get_random_bytes<br />
<br />
<br />
== Typical Client Flows ==<br />
<br />
Create account<br />
<br />
* POST /account/create (email,srpV,srpSalt) -> ok (server sends verification email)<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authed with authToken]() -> keyFetchToken, sessionToken<br />
* GET /recovery_email/status [sessionToken] () -> "verified" status<br />
** (optional, only if user requests resend) POST /recovery_email/resend_code [sessionToken]() -> ok<br />
** POST /recovery_email/verify_code (code) -> ok<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
* POST /certificate/sign [sessionToken] (pubkey) -> cert<br />
<br />
<br />
Attach to new device<br />
<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /session/create [authToken] () -> keyFetchToken, sessionToken<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
** (if unverified-error, do waitUntilEmailVerified, then try again)<br />
* POST /certificate/sign [sessionToken] (pubkey) -> cert<br />
<br />
<br />
Forgot password<br />
<br />
* POST /password/forgot/send_code (email) -> forgotPasswordToken<br />
* POST /password/forgot/verify_code (forgotPasswordToken, code) -> accountResetToken<br />
* POST /account/reset [authed+encrypted by accountResetToken] (0000,srpV,srpSalt) -> ok<br />
* GOTO "Attach to new device"<br />
<br />
<br />
Change Password<br />
<br />
* POST /auth/start (email) -> srpToken,SRP stuff<br />
* POST /auth/finish (srpToken,SRP stuff,deviceInfo) -> authToken<br />
* POST /password/change/start [authToken] () -> accountResetToken, keyFetchToken<br />
* GET /account/keys [keyFetchToken] () -> kA/wrap(kB)<br />
* POST /account/reset [authed+encrypted by accountResetToken] (wrap(kB),srpV,srpSalt) -> ok<br />
* GOTO "Attach to new device"</div>Warner