Confirmed users
358
edits
No edit summary |
No edit summary |
||
| Line 31: | Line 31: | ||
In initial discussions we've been calling this a "key escrow service", but to me (rfkelly) that conjures up too many big-brother clipper-chip-style associations. Since the idea is that Mozilla won't be able to obtain your sync key even if you enable this service, I think "key recovery service" has more accurate connotations. Thoughts? | In initial discussions we've been calling this a "key escrow service", but to me (rfkelly) that conjures up too many big-brother clipper-chip-style associations. Since the idea is that Mozilla won't be able to obtain your sync key even if you enable this service, I think "key recovery service" has more accurate connotations. Thoughts? | ||
== Key Encryption == | == Sync Key Encryption == | ||
Before uploading to the service, the client encrypts the sync key using its existing standard encryption routines. The encryption key is derived from the username and password using PBKDF2. Details follow. | Before uploading to the service, the client encrypts the sync key using its existing standard encryption routines. The encryption key is derived from the username and password using PBKDF2. Details follow. | ||
| Line 43: | Line 43: | ||
IV = get_random_bytes(32) | IV = get_random_bytes(32) | ||
ciphertext = AES-256(enc_key, IV, | ciphertext = AES-256-ENCRYPT(enc_key, IV, sync_key) | ||
hmac = HMAC256(enc_key, ciphertext) | hmac = HMAC256(enc_key, ciphertext) | ||
The details necessary to decrypt the sync key are serialized into a JSON structure, which is sent to the key recovery service for storage: | The details necessary to decrypt the sync key are serialized into a JSON structure, which is sent to the key recovery service for storage: | ||
{ | recov = { | ||
// Parameters for key derivation, as used by deriveKeyFromPassphrase | // Parameters for key derivation, as used by deriveKeyFromPassphrase | ||
"salt": "b64-encoded salt", | "salt": "b64-encoded salt", | ||
| Line 57: | Line 57: | ||
"hmac": "hex-encoded hmac", | "hmac": "hex-encoded hmac", | ||
} | } | ||
To recover the sync key, the client retrieves the above JSON from the recovery service and does: | |||
enc_key = PBKDF2(username + password, recov["salt"], 4096, 32) | |||
if HMAC256(enc_key, recov["ciphertext"]) != recov["hmac"]: | |||
ABORT! | |||
sync_key = AES-256-DECRYPT(enc_key, recov["IV"], recov["ciphertext"]) | |||
| Line 64: | Line 71: | ||
* should we mix the HMAC_INPUT string into the PBKDF2 inputs? | * should we mix the HMAC_INPUT string into the PBKDF2 inputs? | ||
== Server | == Authentication == | ||
Anyone who can access the stored recovery data for a user can run a dictionary or brute-force attack against their password. So, we should only allow retrieval of the recovery data when authenticated as the user. | |||
However, since the server component is intended to run from a high-security restricted-access environment, it should be as simple and light-weight as possible. It will therefore offload responsibility for authentication to a separate service so that it doesn't have to handle passwords. | |||
To access the recovery service, the user must provide an "auth token" to prove their credentials. I see two possibilities: server-generated tokens and user-generated tokens. | |||
=== Server-Generated Tokens === | |||
The recovery service shares a private key with a separate "authentication service" which generates signed auth tokens for the user. The user provides their account credentials to the authentication services and obtains a signed auth token in return: | |||
GET https://server/path/authentication/token | |||
Authorization: Basic XXXYYYZZZ | |||
=> 200 OK | |||
<username>:<timestamp>:<hmac> | |||
(Obviously it would be better to use Digest-Auth or SRP or something that doesn't reveal the password to the server; this is just an example.) | |||
Here the token contains a username to identify the user, a timestamp to allow tokens to be expired, and a HMAC signature using the shared private key. | |||
The user then presents this token to the recovery service and can retrieve, update or delete the stored recovery data: | |||
GET https://server/path/recovery/username | |||
=> 401 Unauthorized | |||
GET https://server/path/recovery/username | |||
X-Auth-Token: <username>:<timestamp>:<hmac> | |||
=> 200 OK | |||
Content-Type: text/json | |||
{ ...recovery data here... } | |||
The recovery service checks the provided auth token to make sure the signature is good, and that it "new enough" according to its own local time. It can thus authenticate users quickly and simply without needing to consult any external services during the request. | |||
== | === User-Generated Tokens === | ||
== | Instead of relying on another server and a single shared signing key, each user could upload their own individual signing key when they store the recovery data. | ||
E.g. take a hash of the user's password as the "user auth key" and upload that along with the recovery data. When going to retrieve the data, the client generates its own auth token and signs it with the previously-uploaded key. | |||
The advantage is that this simplifies the workflow for the client, and removes one component which could provide an additional attack surface. | |||
The disadvantage is that it can't be used to authenticate the initial upload of the recovery data, so there's a bootstrapping problem. | |||
=== Wider Implications === | |||
Since this service effectively reduces the security of the user's sync data to the security of their account password, we need to consider the wider implications for password management across all Services products. | |||
Account management and authorization in Services currently uses HTTP-Basic-Auth, and hence transmits the password to Mozilla in the clear. Thus, users of the recovery service are trivially vulnerable to us snooping on them, or to anyone who manages to compromise any of our servers. That's bad. | |||
Ideally, we would move to a system that can provide authentication without the server learning the user's password. HTTP-Digest-Auth at a minimum. Something like the Secure Remote Password Protocol would be even better, but there's no current standard for integrating this into the HTTP-Auth workflow. | |||
In any case, such a move is largely orthogonal to the development of the key recovery service itself. | |||