From 99a1b8df2d6fdd4e231d2a61be98d174f6fa11d7 Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Fri, 24 Jul 2020 17:38:31 -0400 Subject: [PATCH 1/9] initial version of device dehydration --- proposals/xxxx-device-dehydration.md | 117 +++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 proposals/xxxx-device-dehydration.md diff --git a/proposals/xxxx-device-dehydration.md b/proposals/xxxx-device-dehydration.md new file mode 100644 index 00000000000..8de70450165 --- /dev/null +++ b/proposals/xxxx-device-dehydration.md @@ -0,0 +1,117 @@ +# MSCxxxx: Device Dehydration + +End-to-end encryption in Matrix relies on the sending device being able to send +megolm sessions to the recipients' devices using to-device messages. When a +user logs into a new device, they can obtain the megolm sessions using key +backup or key sharing if another of their devices had previously received the +session. However, when a user has no logged-in devices when a message is sent, +they are unable to receive incoming megolm sessions. + +One solution to this is have a dehydrated device stored (encrypted) +server-side, which may be rehydrated and used when the user creates a new +login rather than creating a new device from scratch. The new login will +receive any to-device messages that were sent to the dehydrated device. + +## Proposal + +### Rehydrating a device + +A new parameter, `restore_device` is added to `POST /login`, indicating that the +client can restore a previously stored device. If the parameter is not +present, it defaults to `false`. If the server has a stored device that can be +used, it will respond with: + +```json +{ + "user_id": "full MXID", + "home_server": "home server address", + "device_data": "base64+encoded+device+data", + "device_id": "ID of dehydrated device", + "dehydration_token": "request+token" +} +``` + +If the server does not have a stored device, or does not understand device +dehydration, then it will respond as if a normal login request were made. + +The client will try to decrypt the device data (see below for encryption). The +client will then make a `POST /restore_device` request, with the +`dehydration_token` body parameter set to the token received from the server. +If it was successful and it wishes to use the device, then it will set the +`rehydrate` body parameter set to `true`. Otherwise, it will set `rehydrate` +to `false`. The server will return an object with properties: + +- `user_id`: the user's full MXID +- `access_token`: the access token the client will use +- `home_server`: the home server's address +- `device_id`: the device ID for the client to use. + +The client will use the device ID given to determine if it should use the +dehydrated device, or if it should use a new device. Even if the client was +able to successfully decrypt the device data, it may not able to allowed to use +it. For example, two clients may race in trying to dehydrate the device; only +one client should use the dehydrated device. + +### Dehydrating a device + +To upload a new dehydrated device, a client will use `POST /device/dehydrate`. +Each user has at most one dehydrated device; uploading a new dehydrated device +will remove any previously-set dehydrated device. + +```json +{ + "device_data": "base64+encoded+device+data", + "initial_device_name": "foo bar", +} +``` + +Result: + +```json +{ + "device_id": "deviceid" +} +``` + +After the dehydrated device is uploaded, the client will upload the encryption +keys using `POST /keys/upload/{device_id}`, where the `device_id` parameter is +the device ID given in the response to `/device/dehydrate`. + +FIXME: synapse already supports `POST /keys/upload/{device_id}`, but requires +that the given device ID matches the device ID of the client that made the +call. We need to (re-)add the endpoint, and allow uploading keys for the +dehydrated device. + +### Device dehydration format + +FIXME: should we just reuse libolm's pickle format? + +## Potential issues + +FIXME: + +## Alternatives + +Rather than uploading a dehydrated device to the server, we could instead have +the sender resend the megolm session in the case where a user had no active +devices at the time that a message was sent. However this does not solve the +issue for users who happen to never be online at the same time. But this could +be done in addition to the solution proposed here. + +The sender could also send the megolm session to a the user using a public key +using some per-user mechanism. + +## Security considerations + +If the dehydrated device is encrypted using a weak password or key, an attacker +could access it and read the user's encrypted messages. + +## Unstable prefix + +While this MSC is in development, the `POST /restore_device` endpoint will be +reached at `POST /unstable/org.matrix.mscxxxx/restore_device`, and the `POST +/device/dehydrate` endpoint will be reached at `POST +/unstable/org.matrix.mscxxxx/device/dehydrate`. The `restore_device` parameter +for `POST /login` will be called `org.matrix.mscxxxx.restore_device`. + +FIXME: change `mscxxxx` to the right number From 30864b5abd933c45acf65a8ae10580ae1e562199 Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Fri, 24 Jul 2020 17:41:46 -0400 Subject: [PATCH 2/9] use MSC number --- ...evice-dehydration.md => 2697-device-dehydration.md} | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) rename proposals/{xxxx-device-dehydration.md => 2697-device-dehydration.md} (94%) diff --git a/proposals/xxxx-device-dehydration.md b/proposals/2697-device-dehydration.md similarity index 94% rename from proposals/xxxx-device-dehydration.md rename to proposals/2697-device-dehydration.md index 8de70450165..c8ce2e3a125 100644 --- a/proposals/xxxx-device-dehydration.md +++ b/proposals/2697-device-dehydration.md @@ -1,4 +1,4 @@ -# MSCxxxx: Device Dehydration +# MSC2697: Device Dehydration End-to-end encryption in Matrix relies on the sending device being able to send megolm sessions to the recipients' devices using to-device messages. When a @@ -109,9 +109,7 @@ could access it and read the user's encrypted messages. ## Unstable prefix While this MSC is in development, the `POST /restore_device` endpoint will be -reached at `POST /unstable/org.matrix.mscxxxx/restore_device`, and the `POST +reached at `POST /unstable/org.matrix.msc2697/restore_device`, and the `POST /device/dehydrate` endpoint will be reached at `POST -/unstable/org.matrix.mscxxxx/device/dehydrate`. The `restore_device` parameter -for `POST /login` will be called `org.matrix.mscxxxx.restore_device`. - -FIXME: change `mscxxxx` to the right number +/unstable/org.matrix.msc2697/device/dehydrate`. The `restore_device` parameter +for `POST /login` will be called `org.matrix.msc2697.restore_device`. From d452bafc9c071bdc1b45e67183466ccb205f3170 Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Mon, 27 Jul 2020 16:39:55 -0400 Subject: [PATCH 3/9] Apply suggestions from code review Co-authored-by: Matthew Hodgson --- proposals/2697-device-dehydration.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proposals/2697-device-dehydration.md b/proposals/2697-device-dehydration.md index c8ce2e3a125..b823eeecac1 100644 --- a/proposals/2697-device-dehydration.md +++ b/proposals/2697-device-dehydration.md @@ -1,7 +1,7 @@ # MSC2697: Device Dehydration End-to-end encryption in Matrix relies on the sending device being able to send -megolm sessions to the recipients' devices using to-device messages. When a +megolm sessions to the recipients' devices. When a user logs into a new device, they can obtain the megolm sessions using key backup or key sharing if another of their devices had previously received the session. However, when a user has no logged-in devices when a message is sent, @@ -27,7 +27,7 @@ used, it will respond with: "home_server": "home server address", "device_data": "base64+encoded+device+data", "device_id": "ID of dehydrated device", - "dehydration_token": "request+token" + "dehydration_token": "opaque+token" } ``` From 724b8098d3fbdf84482cbeb34e055d50185bf168 Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Wed, 5 Aug 2020 15:19:54 -0400 Subject: [PATCH 4/9] add potential issues, and clarify what happens in a race --- proposals/2697-device-dehydration.md | 40 ++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/proposals/2697-device-dehydration.md b/proposals/2697-device-dehydration.md index b823eeecac1..a617c911419 100644 --- a/proposals/2697-device-dehydration.md +++ b/proposals/2697-device-dehydration.md @@ -50,7 +50,9 @@ The client will use the device ID given to determine if it should use the dehydrated device, or if it should use a new device. Even if the client was able to successfully decrypt the device data, it may not able to allowed to use it. For example, two clients may race in trying to dehydrate the device; only -one client should use the dehydrated device. +one client should use the dehydrated device. In the case of a race, the server +will give the dehydrated device's ID to one client, and generate a new device +ID for any other clients. ### Dehydrating a device @@ -88,7 +90,41 @@ FIXME: should we just reuse libolm's pickle format? ## Potential issues -FIXME: +### One-time key exhaustion + +The dehydrated device may run out of one-time keys, since it is not backed by +an active client that can replenish them. Once a device has run out of +one-time keys, no new olm sessions can be established with it, which means that +devices that have not already shared megolm keys with the dehydrated device +will not be able to share megolm keys. This issue is not unique to dehydrated +devices; this also occurs when devices are offline for an extended period of +time. + +This could be addressed by modifying olm to operate using [Signal's +x3dh](https://signal.org/docs/specifications/x3dh/), in which Bob has both a +sign prekey (which is replaced periodically), and one-time prekeys. + +To reduce the chances of one-time key exhaustion, if the user has an active +client, it can periodically replace the dehydrated device with a new dehydrated +device with new one-time keys. If a client does this, then it runs the risk of +losing any megolm keys that were sent to the dehydrated device, but the client +would likely have received those megolm keys itself. + +Alternatively, the client could perform a `/sync` for the dehydrated device, +dehydrate the olm sessions, and upload new one-time keys. By doing this +instead of overwriting the dehydrated device, the device can receive megolm +keys from more devices. However, this would require additional server-side +changes above what this proposal provides, so this approach is not possible for +the moment. + +### Accumulated to-device messages + +If a dehydrated device is not rehydrated for a long time, then it may +accumulate many to-device messages from other clients sending it megolm +sessions. This may result in a slower initial sync when the device eventually +does get rehydrated, due to the number of messages that it will retrieve. +Again, this can be addressed by periodically replacing the dehydrated device, +or by performing a `/sync` for the dehydrated device and updating it. ## Alternatives From 7687c3b43dd37bd9a123510d9980e17d982236f1 Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Mon, 17 Aug 2020 18:17:24 -0400 Subject: [PATCH 5/9] add some clarifications, and make device_data an object instead of a string --- proposals/2697-device-dehydration.md | 80 +++++++++++++++++----------- 1 file changed, 49 insertions(+), 31 deletions(-) diff --git a/proposals/2697-device-dehydration.md b/proposals/2697-device-dehydration.md index a617c911419..dabfbf592bf 100644 --- a/proposals/2697-device-dehydration.md +++ b/proposals/2697-device-dehydration.md @@ -19,17 +19,15 @@ receive any to-device messages that were sent to the dehydrated device. A new parameter, `restore_device` is added to `POST /login`, indicating that the client can restore a previously stored device. If the parameter is not present, it defaults to `false`. If the server has a stored device that can be -used, it will respond with: +used, it will respond with the same response as a normal login, with the +following exceptions: -```json -{ - "user_id": "full MXID", - "home_server": "home server address", - "device_data": "base64+encoded+device+data", - "device_id": "ID of dehydrated device", - "dehydration_token": "opaque+token" -} -``` +- no `access_token` is provided +- a `device_data` property is provided, containing information about the + dehydrated device that the client uses to try to rehydrate the device +- a `dehydration_token` is provided, giving a token that the client uses to + complete the login. The token may be expired if it is not used within a + reasonable amount of time (for example, 10 minutes). If the server does not have a stored device, or does not understand device dehydration, then it will respond as if a normal login request were made. @@ -39,20 +37,30 @@ client will then make a `POST /restore_device` request, with the `dehydration_token` body parameter set to the token received from the server. If it was successful and it wishes to use the device, then it will set the `rehydrate` body parameter set to `true`. Otherwise, it will set `rehydrate` -to `false`. The server will return an object with properties: - -- `user_id`: the user's full MXID -- `access_token`: the access token the client will use -- `home_server`: the home server's address -- `device_id`: the device ID for the client to use. - -The client will use the device ID given to determine if it should use the -dehydrated device, or if it should use a new device. Even if the client was -able to successfully decrypt the device data, it may not able to allowed to use -it. For example, two clients may race in trying to dehydrate the device; only -one client should use the dehydrated device. In the case of a race, the server -will give the dehydrated device's ID to one client, and generate a new device -ID for any other clients. +to `false`. + +The server will return an object in the same format as the response for a +normal login. The client will use the device ID given to determine if it +should use the dehydrated device, or if it should use a new device. Even if +the client was able to successfully decrypt the device data, it may not able to +allowed to use it. For example, two clients may race in trying to dehydrate +the device; only one client should use the dehydrated device. In the case of a +race, the server will give the dehydrated device's ID to one client, and +generate a new device ID for any other clients. Another potential reason that +the client may not be allowed to use the dehydrated device is if the device was +deleted. + +If the dehydrated device is not used (whether because the client set +`rehydrate` to `false`, or because the server indicated that the client is not +able to use the dehydrated device), the server will use the `device_id` and +`initial_device_display_name` parameters from the client's original call to +`/login`, if they were provided, to create a device for the user. If the +dehydrated device is used, the server will set the device's name to the +`initial_device_display_name` from the client's original call to `/login`, if +it was provided. + +After `POST /restore_device` returns, the client is logged in and may proceed +as normal. ### Dehydrating a device @@ -62,8 +70,8 @@ will remove any previously-set dehydrated device. ```json { - "device_data": "base64+encoded+device+data", - "initial_device_name": "foo bar", + "device_data": {"device": "data"}, + "initial_device_display_name": "foo bar", } ``` @@ -86,7 +94,14 @@ dehydrated device. ### Device dehydration format -FIXME: should we just reuse libolm's pickle format? +The `device_data` property is an object that has an `algorithm` field +indicating what other fields are present. + +#### `m.dehydration.v1.olm` + +- `passphrase`: Optional. Indicates how to generate the decryption key from a + passphrase. It is in the same format with Secure Secret Storage. +- `account`: Required. ... FIXME: should we just reuse libolm's pickle format? ## Potential issues @@ -100,9 +115,8 @@ will not be able to share megolm keys. This issue is not unique to dehydrated devices; this also occurs when devices are offline for an extended period of time. -This could be addressed by modifying olm to operate using [Signal's -x3dh](https://signal.org/docs/specifications/x3dh/), in which Bob has both a -sign prekey (which is replaced periodically), and one-time prekeys. +This may be addressed by using fallback keys as described in +[MSC2732](https://github.com/matrix-org/matrix-doc/pull/2732). To reduce the chances of one-time key exhaustion, if the user has an active client, it can periodically replace the dehydrated device with a new dehydrated @@ -135,7 +149,11 @@ issue for users who happen to never be online at the same time. But this could be done in addition to the solution proposed here. The sender could also send the megolm session to a the user using a public key -using some per-user mechanism. +using some per-user mechanism. This would require changes to both the sender +and receiver (whereas this proposal only requires changes to the receiver), and +would require developing a system by which the sender could determine whether +the public key may be trusted (whereas this proposal the existing cross-signing +mechanism). ## Security considerations From 64af8844161d30f3d8e134c7117c1ba284bafd50 Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Tue, 25 Aug 2020 23:15:13 -0400 Subject: [PATCH 6/9] fix wording --- proposals/2697-device-dehydration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/2697-device-dehydration.md b/proposals/2697-device-dehydration.md index dabfbf592bf..5496e77f036 100644 --- a/proposals/2697-device-dehydration.md +++ b/proposals/2697-device-dehydration.md @@ -42,7 +42,7 @@ to `false`. The server will return an object in the same format as the response for a normal login. The client will use the device ID given to determine if it should use the dehydrated device, or if it should use a new device. Even if -the client was able to successfully decrypt the device data, it may not able to +the client was able to successfully decrypt the device data, it may not be allowed to use it. For example, two clients may race in trying to dehydrate the device; only one client should use the dehydrated device. In the case of a race, the server will give the dehydrated device's ID to one client, and From b5367706bdbc73af76fae12aac9d619c1562c8c9 Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Thu, 24 Sep 2020 16:03:21 -0400 Subject: [PATCH 7/9] clarification --- proposals/2697-device-dehydration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/2697-device-dehydration.md b/proposals/2697-device-dehydration.md index 5496e77f036..1455d869cc7 100644 --- a/proposals/2697-device-dehydration.md +++ b/proposals/2697-device-dehydration.md @@ -145,7 +145,7 @@ or by performing a `/sync` for the dehydrated device and updating it. Rather than uploading a dehydrated device to the server, we could instead have the sender resend the megolm session in the case where a user had no active devices at the time that a message was sent. However this does not solve the -issue for users who happen to never be online at the same time. But this could +issue for users who happen to never be logged in at the same time. But this could be done in addition to the solution proposed here. The sender could also send the megolm session to a the user using a public key From 1edb2ac3e159735501c4b6287b14d1c7ba7e299a Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Mon, 5 Oct 2020 21:21:43 -0400 Subject: [PATCH 8/9] update to agree with the latest implementation --- proposals/2697-device-dehydration.md | 142 +++++++++++++++------------ 1 file changed, 79 insertions(+), 63 deletions(-) diff --git a/proposals/2697-device-dehydration.md b/proposals/2697-device-dehydration.md index 1455d869cc7..09052ef988e 100644 --- a/proposals/2697-device-dehydration.md +++ b/proposals/2697-device-dehydration.md @@ -14,63 +14,20 @@ receive any to-device messages that were sent to the dehydrated device. ## Proposal -### Rehydrating a device - -A new parameter, `restore_device` is added to `POST /login`, indicating that the -client can restore a previously stored device. If the parameter is not -present, it defaults to `false`. If the server has a stored device that can be -used, it will respond with the same response as a normal login, with the -following exceptions: - -- no `access_token` is provided -- a `device_data` property is provided, containing information about the - dehydrated device that the client uses to try to rehydrate the device -- a `dehydration_token` is provided, giving a token that the client uses to - complete the login. The token may be expired if it is not used within a - reasonable amount of time (for example, 10 minutes). - -If the server does not have a stored device, or does not understand device -dehydration, then it will respond as if a normal login request were made. - -The client will try to decrypt the device data (see below for encryption). The -client will then make a `POST /restore_device` request, with the -`dehydration_token` body parameter set to the token received from the server. -If it was successful and it wishes to use the device, then it will set the -`rehydrate` body parameter set to `true`. Otherwise, it will set `rehydrate` -to `false`. - -The server will return an object in the same format as the response for a -normal login. The client will use the device ID given to determine if it -should use the dehydrated device, or if it should use a new device. Even if -the client was able to successfully decrypt the device data, it may not be -allowed to use it. For example, two clients may race in trying to dehydrate -the device; only one client should use the dehydrated device. In the case of a -race, the server will give the dehydrated device's ID to one client, and -generate a new device ID for any other clients. Another potential reason that -the client may not be allowed to use the dehydrated device is if the device was -deleted. - -If the dehydrated device is not used (whether because the client set -`rehydrate` to `false`, or because the server indicated that the client is not -able to use the dehydrated device), the server will use the `device_id` and -`initial_device_display_name` parameters from the client's original call to -`/login`, if they were provided, to create a device for the user. If the -dehydrated device is used, the server will set the device's name to the -`initial_device_display_name` from the client's original call to `/login`, if -it was provided. - -After `POST /restore_device` returns, the client is logged in and may proceed -as normal. - ### Dehydrating a device -To upload a new dehydrated device, a client will use `POST /device/dehydrate`. +To upload a new dehydrated device, a client will use `PUT /dehydrated_device`. Each user has at most one dehydrated device; uploading a new dehydrated device will remove any previously-set dehydrated device. +`PUT /dehydrated_device` + ```json { - "device_data": {"device": "data"}, + "device_data": { + "algorithm": "m.dehydration.v1.olm" + "other_fields": "other_values" + }, "initial_device_display_name": "foo bar", } ``` @@ -79,29 +36,87 @@ Result: ```json { - "device_id": "deviceid" + "device_id": "dehydrated device's ID" } ``` After the dehydrated device is uploaded, the client will upload the encryption keys using `POST /keys/upload/{device_id}`, where the `device_id` parameter is -the device ID given in the response to `/device/dehydrate`. +the device ID given in the response to `PUT /dehydrated_device`. The request +and response formats for `POST /keys/upload/{device_id}` are the same as those +for `POST /keys/upload` with the exception of the addition of the `device_id` +path parameter. + +Note: Synapse already supports `POST /keys/upload/{device_id}` as this was used +in some old clients. However, synapse requires that the given device ID +matches the device ID of the client that made the call. So this will be +changed to allow uploading keys for the dehydrated device. + +### Rehydrating a device + +To rehydrate a device, a client first logs in as normal and then calls `GET +/dehydrated_device` to see if a dehydrated device is available. If a device is +available, the server will respond with the dehydrated device's device ID and +the dehydrated device data. + +`GET /dehydrated_device` + +Response: + +```json +{ + "device_id": "dehydrated device's ID", + "device_data": { + "algorithm": "m.dehydration.v1.olm", + "other_fields": "other_values" + } +} +``` + +If no dehydrated device is available, the server responds with a 404. + +If the client is able to decrypt the data and wants to use the dehydrated +device, the client calls `POST /dehydrated_device/claim` to tell the server +that it wishes to do so. The request includes the device ID of the dehydrated +device to ensure that it is still available. + +If the requested device is still the current dehydrated device and has not +already been claimed by another client, the server responds with a successful +response. The server also changes the device ID associated with the client's +access token to the device ID of the dehydrated device, and will change the +device display name for the dehydrated device to the display name given when +the client logged in. + +`POST /dehydrated_device/claim` + +```json +{ + "device_id": "dehydrated device's ID" +} +``` + +Response: + +```json +{ + "success": true +} +``` -FIXME: synapse already supports `POST /keys/upload/{device_id}`, but requires -that the given device ID matches the device ID of the client that made the -call. We need to (re-)add the endpoint, and allow uploading keys for the -dehydrated device. +If the requested device ID does not belong to the user's current dehydrated +device or the dehydrated device has already been claimed, the server responds +with a 404. ### Device dehydration format The `device_data` property is an object that has an `algorithm` field indicating what other fields are present. -#### `m.dehydration.v1.olm` +#### `m.dehydration.v1.olm.libolm_pickle` - `passphrase`: Optional. Indicates how to generate the decryption key from a passphrase. It is in the same format with Secure Secret Storage. -- `account`: Required. ... FIXME: should we just reuse libolm's pickle format? +- `account`: Required. FIXME: libolm's pickle format ## Potential issues @@ -162,8 +177,9 @@ could access it and read the user's encrypted messages. ## Unstable prefix -While this MSC is in development, the `POST /restore_device` endpoint will be -reached at `POST /unstable/org.matrix.msc2697/restore_device`, and the `POST -/device/dehydrate` endpoint will be reached at `POST -/unstable/org.matrix.msc2697/device/dehydrate`. The `restore_device` parameter -for `POST /login` will be called `org.matrix.msc2697.restore_device`. +While this MSC is in development, the `/dehydrated_device` endpoints will be +reached at `/unstable/org.matrix.msc2697.v2/dehydrated_device`, and the +`/dehydrated_device/claim` endpoint will be reached at +`/unstable/org.matrix.msc2697.v2/dehydrated_device`. The dehydration algorithm +`m.dehydration.v1.olm.libolm_pickle` will be called +`org.matrix.msc2697.v1.olm.libolm_pickle`. From bf29f5034003ada0c0ac61a9f95bb6e286e15fb1 Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Tue, 6 Oct 2020 13:04:22 -0400 Subject: [PATCH 9/9] warn against calling anything else before rehydrating --- proposals/2697-device-dehydration.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/proposals/2697-device-dehydration.md b/proposals/2697-device-dehydration.md index 09052ef988e..6fdfbbea0f4 100644 --- a/proposals/2697-device-dehydration.md +++ b/proposals/2697-device-dehydration.md @@ -107,6 +107,12 @@ If the requested device ID does not belong to the user's current dehydrated device or the dehydrated device has already been claimed, the server responds with a 404. +Clients should not call any other endpoints before rehydrating a device. In +particular, if a client calls `/sync` while rehydrating, the client should not +expect the `/sync` to return sensible information. For example, it could +contain a mix of to-device messages sent to the old device ID and the new +device ID. + ### Device dehydration format The `device_data` property is an object that has an `algorithm` field