Skip to content

Device Enrollment API

Alex Rao edited this page Jun 7, 2021 · 3 revisions

This document outlines the protocol for enrolling a new device for an existing user.

This document uses the following notation:

  • D: Device. The device that is being enrolled
  • U: User. The already-registered user that is initiating this enrollment
  • S: Server. The server, which has access to the user information

Unless otherwise stated, all connections are over TLS (i.e., HTTPS for HTTP connections, etc)

Note that, because of the nature of this messaging service, User Authentication is a subset of device enrollment; the user will only authenticate when enrolling (or re-enrolling) a device.

Step 0: Registration

Before this protocol can be initiated, the user must first have registered an account on the server S. At a minimum, this registration produces the following side effects:

  • S knows the U's ID (i.e., their handle)
  • S also knows U's salted + hashed password. S does NOT have access to the original password.

Device Enrollment

At this point, D is completely unknown to S. D does know how to contact the server (i.e., the server URL). D also has access to U's ID and plaintext password. Finally, D has an intrinsic property that can be used to uniquely identify it (but is not necessarily kept secret); in most cases, a MAC address would suffice.

In this document, any strings that are denoted as base-64 encoded are stored and used that way. For example, the salt is defined as base64-encoded; it is always used in this representation; calculating the password hash is h(s, p) with the salt as a base-64 encoded string. When it is said to be 32-bytes or 16 bytes, this means the underlying data is that many bytes; the base-64 representation will likely be longer!

This step proceeds as follows:

  1. D sends a POST request to /api/devices with a JSON payload conforming to the following:
{
  "handle": string;
  "deviceID": string;
}
  • handle: The ID of user U
  • deviceID: The ID (see above) of the device D
  1. S responds with a JSON payload conforming to the following format:
{
  "salt": string;
  "hashAlgorithm": HashAlgorithm;
  "nonce": string;
}
  • salt: The 32-byte salt used in the password hashing process, as a base64-encoded string.
  • hashAlgorithm: The Hash Algorithm used to hash the password. This is an Enum; their values can be found at the end of this document.
  • nonce: A 16-byte, base64-encoded string that represents a NONCE.

Note that, at this point, the existence of the user account is not known; if the user account does not exist, a random (but otherwise valid) salt is sent. However, the hash that results from this (i.e., in step 3) will never pass validation on the server side. This is done so that it is impossible to know if a user account actually exists.

  1. D calculates its hash as follows. Suppose plaintext password p, salt s, hashing algorithm h, device ID d, and nonce n. Then, the hash is calculated by: hash <= h(h(s, p), d, n).

  2. D sends a PUT request to /api/devices with the following payload:

{
  "handle": string;
  "deviceID": string;
  "info"?:
  {
    "name"?: string;
    "os"?: string;
    "type"?: "mobile"|"tablet"|"desktop"|"other";
  },
  "hash": string;
}
  • handle and deviceID are the same as before
  • info is an optional field where the device can more fully describe itself; all fields are optional.
  • hash is the hash calculated in the previous step, encoded as a base64 string.

Server S calculates this hash as well, using the nonce it previously sent to check that the hashes match. If they do NOT match, an HTTP 401 Unauthorized is sent back, and the operation is aborted. Otherwise, an HTTP 200 status code is sent back along with a new nonce, n2.

  1. At this point, the device and server now have a shared secret; namely, the following hash: h(h(s, p), d, n2). The server stores this as a unique "Device Fingerprint", along with the device ID and optionally provided device information, tied to the user; the device also stores this fingerprint locally.

Notice that the nonce is discarded; if the server or the device loses the shared secret, the enrollment process must be repeated fully.

Thus, the user object could look something similar to this:

{
  "handle": "JDoe",
  "devices":
  {
    "device1ID":
    {
      "hash": "aef3232...",
      "name": "My iPhone",
      "os": "iOS 14.5.1",
      "type": "mobile"
    }
  }
}

For all future communications, the device can use this fingerprint as a "fast pass" to skip this process, providing the fingerprint, device ID, and handle.

Clone this wiki locally