Loop/Architecture/Context
The rooms (conversations) in Loop are planned to include additional room context information, such as a URL that will be the topic of the conversation, a thumbnail of that URL, and a long-form description of the conversation purpose. Note that this is list is an initial proposal; it is probable that the context information associated with a room will evolve over time. The initial version of this feature is tracked in bug 1115340. The UX mock-up may be helpful in visualizing how this information is to be presented.
Information Privacy
The context information is user-supplied, so we need to carefully consider privacy handling. The design below serves this purpose by storing the information on the Loop server in an encrypted form. Each room will have its own symmetric key, which will be available to the room owner and anyone to whom he provides the room URL. These room keys will never be available to any server.
This section explains the means by which this achieved at the level of information flow. Details are provided in the sections below.
When a Loop client logs into the FxA server, it will obtain a user-specific symmetric key, kB, and an OAuth application identifier. The key kB is wrapped with the user's account login password. Upon obtaining the key, the user unwraps kB, combines it with the application identifier, and applies HKDF to obtain a new key, kBr. We then take this kBr and combine it with a name for our specific usage ("metadata") to create a new key, kRWrapper, used to wrap the Room Context Keys (kR, described below)
When a client creates a new room, it first generates a new symmetric room key, kR. The room context information is serialized as a JSON object, and encrypted using kR. The client also wraps kR with kRWrapper. The encrypted context information and the wrapped room key wrap(kR) are sent to the Loop server as part of the room creation request; these are stored as part of the room's information.
Upon receipt of the resulting room URL from the server, the client appends the room metadata key kR to the URL as a URL fragment before storing it locally.
Because all desktop clients can constitute their kBr upon logging in to FxA, any room context retrieved from the Loop server can be decoded by unwrapping the kR received as part of the room information and using it to decrypt the context.
Standalone clients receive the room metadata key kR in the fragment portion of the URL that was provided to them as the means of joining the room. Upon retrieving the encrypted room context information from the server, they use this kR to decode the room context information and display it to the user.

Further information on derivation of kB can be found at Identity/AttachedServices/KeyServerProtocol. The proposal for creating application specific keys (kBr) was originally made on the fxa-dev mailing list, and this proposal re-uses the terminology outlined in that message.
Note: The preceding design is predicated on having the ability to retrieve kB from FxA in time for the initial feature landing. This ability is being tracked on the FxA github repo. If this cannot be done in time, we will need to store room-keys client-side. This is sub-optimal, as it makes it impossible to create a room on one client and view or manipulate information on another.
Algorithm Details
To allow for evolving the underlying crypto algorithm, any encrypted context will be paired with an explicit indication of the algorithm in use. For the moment, we define only one algorithm, "AES-GCM". Key length is not explicitly included in the algorithm name, and is instead implied by the length of the accompanying key. For our initial implementation, we will be generating 128-bit keys; however, code should be forwards-compatible with longer key lengths.
For AES-GCM, the "context.value" field is formatted as follows:
Base64(IV || ciphertext || tag)
Where IV is 12 bytes in length, and tag is 16 bytes (128 bits) in length.
Encryption consists of selecting a random 12-byte IV value. This IV, the plaintext JSON representation of the room context fields, and kR are used as input to the AES-GCM encryption algorithm, which is configured to generate a 128-bit validation tag. The IV is then concatenated with the ciphertext and the validation tag. The resulting bytestring is Base64 encoded, and included as the "context.value" field in the appropriate Loop Server API call, alongside the wrapped room key wrap(kR) and the algorithm name ("AES-GCM").
Decryption consists of Base64 decoding the "context" field, splitting off the first 12 bytes for use as an IV, and splitting off the final 16 bytes as the validation tag. These are then used as input to the AES-GCM decryption algorithm (along with kR), the output of which is a JSON object containing fields that correspond to the various room context information fields.
The "key" field is encrypted, formatted, and decrypted similarly, but using kBr rather than kR as the encryption key, and using kR as the cleartext.
Loop Server API Changes
From a Loop Server API perspective, this change is nothing more than mechanically replacing "roomName" with "context", which is a itself a structure containing three fields: "value", "alg", and "wrappedKey". The values of these fields are opaque to the server, and are simply stored as part of the room's information, and returned to clients when the room information is fetched.
Note that there will be a transition period during which rooms that were created prior to the introduction of encrypted context will contain a "roomName" field instead of "context". The server will need to deal with this transition gracefully. The server may assume, but is not required to enforce, the constraint that "roomName" will never appear in a room alongside "context".
For the sake of efficiency, the Loop server can also safely assume that the "value" and "wrappedKey" fields are Base64 encoded. This allows, for example, decoding them and storing them as more compact binary fields. The resulting binary data will have very low entropy, so any attempts to compress the information will likely be futile.
As a final note about context information: the current UX calls for this context information to include small image thumbnails as part of the context. This means that context data for a single room will likely be on the order of 20 to 30kB in size, on average.
POST /rooms
This generates a new room and returns the associated URL that can be used to join the room.
POST /rooms HTTP/1.1 Accept: application/json Accept-Encoding: gzip, deflate Authorization: <stripped> Content-Type: application/json; charset=utf-8 Host: localhost:5000 { "context": { "value": "PWjHj89HBS-...ICUX3Iqd9ZsfDNLoUeAb5KGJgEtDy-7ag52rYY5mGgP2GQ==", "alg": "AES-GCM", "wrappedKey": "KLPCJEy8vewUeHFFLtvMNA" }, "expiresIn": 5, "roomOwner": "Alexis", "maxSize": 2 }
- context.value - The room context information, encrypted and Base64 encoded.
- context.alg - The encryption algorithm used to encrypt the context information.
- context.wrappedKey - The room key (kR), wrapped by the user's application-specific key kBr.
- expiresIn - The number of hours for which the room will exist.
- roomOwner - The user-friendly display name indicating the name of the room's owner.
- maxSize - The maximum number of users allowed in the room at one time.
NOTE: the roomName parameter no longer appears in the message; it has been moved into the "context" portion of the message.
HTTP/1.1 200 OK Connection: keep-alive Content-Type: application/json; charset=utf-8 Date: Wed, 16 Jul 2014 13:09:40 GMT Server-Authorization: <stripped> { "roomToken": "_nxD4V4FflQ", "roomUrl": "http://localhost:3000/rooms/_nxD4V4FflQ", "expiresAt": 1405534180 }
- roomToken - The token used to identify this room.
- roomUrl - A URL that can be given to other users to allow them to join the room. Note that the user must append the room key kR as a url fragment before using this URL.
- expiresAt - The date after which the room will no longer be valid (in seconds since the Unix epoch).
Server implementation note: The Loop server should contact the TokBox servers and retrieve a sessionId for the room at room creation time. This sessionId should be stored persistently with rest of the information associated with the room.
PATCH /rooms/{token}
The "PATCH /rooms/{token}" endpoint is used to update information about an existing room.
PATCH /rooms/_nxD4V4FflQ HTTP/1.1 Accept: application/json Accept-Encoding: gzip, deflate Authorization: <stripped> Content-Type: application/json; charset=utf-8 Host: localhost:5000 { "context": { "value": "PWjHj89HBS-...ICUX3Iqd9ZsfDNLoUeAb5KGJgEtDy-7ag52rYY5mGgP2GQ==", "alg": "AES-GCM", "wrappedKey": "KLPCJEy8vewUeHFFLtvMNA" }, "expiresIn": 24 }
The parameters for this endpoint are the same as for "POST /rooms". Any omitted parameters are not updated.
HTTP/1.1 200 OK Connection: keep-alive Content-Type: application/json; charset=utf-8 Date: Wed, 16 Jul 2014 13:09:40 GMT Server-Authorization: <stripped> { "expiresAt": 1405534180 }
- expiresAt - The date after which the room will no longer be valid (in seconds since the Unix epoch).
GET /rooms/{token}
This endpoint is used to retrieve information about a single room, including a list of room participants.
Because providing this information to users who are not in the room would be surprising for those in the room, we only allow the room owner and users in the room to access this method. Room owners are authenticated via their HAWK credentials, while room participants are authenticated via the sessionToken bearer token provided to them when they joined the room.
GET /rooms/3jKS_Els9IU HTTP/1.1 Accept: */* Accept-Encoding: gzip, deflate Authorization: <elided> Host: localhost:5000
- For a desktop client user, the "Authorization" header field is populated with the HAWK token (using a scheme of "Hawk"), just like it is for other requests.
- For the standalone client, the "Authorization" header field is encoded using Basic authentication. The user ID portion is the sessionToken provided to the user when they joined the room, and the password is blank.
HTTP/1.1 200 OK Connection: keep-alive Content-Length: 30 Content-Type: application/json; charset=utf-8 Date: Wed, 16 Jul 2014 13:23:04 GMT ETag: W/"1e-2896316483" Timestamp: 1405516984 { "roomToken": "3jKS_Els9IU", "context": { "value": "PWjHj89HBS-...ICUX3Iqd9ZsfDNLoUeAb5KGJgEtDy-7ag52rYY5mGgP2GQ==", "alg": "AES-GCM", "wrappedKey": "KLPCJEy8vewUeHFFLtvMNA" }, "roomUrl": "http://localhost:3000/rooms/3jKS_Els9IU", "roomOwner": "Alexis", "maxSize": 2, "clientMaxSize": 2, "creationTime": 1405517546, "ctime": 1405517824, "expiresAt": 1405534180, "participants": [ { "displayName": "Alexis", "account": "alexis@example.com", "roomConnectionId": "2a1787a6-4a73-43b5-ae3e-906ec1e763cb" }, { "displayName": "Adam", "roomConnectionId": "781f012b-f1ea-4ce1-9105-7cfc36fb4ec7" } ] }
- roomToken - The token that uniquely identifies this room
- context - Has the same meaning as in POST /rooms
- roomUrl - A URL that can be given to other users to allow them to join the room.
- roomOwner - The user-friendly display name indicating the name of the room's owner.
- maxSize - The maximum number of users allowed in the room at one time (as configured by the room owner).
- clientMaxSize - The current maximum number of users allowed in the room, as constrained by the clients currently participating in the session. If no client has a supported size smaller than "maxSize", then this will be equal to "maxSize". Under no circumstances can "clientMaxSize" be larger than "maxSize".
- creationTime - The time (in seconds since the Unix epoch) at which the room was created.
- expiresAt - The time (in seconds since the Unix epoch) at which the room goes away.
- participants - An array containing a list of the current room participants. Each participant is formatted with the same fields as described in #User Identification in a Room.
- ctime - Similar in spirit to the Unix filesystem "ctime" (change time) attribute. The time, in seconds since the Unix epoch, that any of the following happened to the room:
- The room was created
- The owner modified its attributes with "PATCH /rooms/{token}"
- A user joined the room
- A user left the room
GET /rooms
List all rooms associated with account
GET /rooms?version=<version> HTTP/1.1 Accept: */* Accept-Encoding: gzip, deflate Host: localhost:5000
- version - Optional; if present, indicates the version received from the simple push server; the server will limit results to those rooms that have changed on or after this time.
HTTP/1.1 200 OK Connection: keep-alive Content-Length: 30 Content-Type: application/json; charset=utf-8 Date: Wed, 16 Jul 2014 13:23:04 GMT ETag: W/"1e-2896316483" Timestamp: 1405516984 [ { "roomToken": "_nxD4V4FflQ", "context": { "value": "PWjHj89HBS-...ICUX3Iqd9ZsfDNLoUeAb5KGJgEtDy-7ag52rYY5mGgP2GQ==", "alg": "AES-GCM", "wrappedKey": "KLPCJEy8vewUeHFFLtvMNA" }, "roomUrl": "http://localhost:3000/rooms/_nxD4V4FflQ", "roomOwner": "Alexis", "maxSize": 2, "creationTime": 1405517546, "ctime": 1405517546, "expiresAt": 1405534180, "participants": [] }, { "roomToken": "QzBbvGmIZWU", "context": { "value": "7s-v8S37f_wpltgN9oA...wdNnoLZGxNkPJp72B8_rG-JKLi1Gd0Ar0La_2jIxbfbV5ztNXVTVjf==", "alg": "AES-GCM", "wrappedKey": "vB--kjDi837BSwbZpQzkHg" }, "roomUrl": "http://localhost:3000/rooms/QzBbvGmIZWU", "roomOwner": "Alexis", "maxSize": 2, "creationTime": 1405517546, "ctime": 1405517546, "expiresAt": 1405534180, "participants": [] }, { "roomToken": "3jKS_Els9IU", "context": { "value": "nF6F9fp4kg7...ps4zAiBh6Bat9qMfdKSlLhHCG-9BYorTos4p4doT3So3kXQDeLHp==", "alg": "AES-GCM", "wrappedKey": "cKnAvMBgA75QhANh04sT3g" }, "roomUrl": "http://localhost:3000/rooms/3jKS_Els9IU", "roomOwner": "Alexis", "maxSize": 2, "clientMaxSize": 2, "creationTime": 1405517546, "ctime": 1405517818, "expiresAt": 1405534180, "participants": [ { "displayName": "Alexis", "account": "alexis@example.com", "roomConnectionId": "2a1787a6-4a73-43b5-ae3e-906ec1e763cb" }, { "displayName": "Adam", "roomConnectionId": "781f012b-f1ea-4ce1-9105-7cfc36fb4ec7" } ] }, { "roomToken": "z9Hg4Bk19_Z", "deleted": true } ]
This body is an array of rooms, each of which is formatted as described in #GET_.2Frooms.2F.7Btoken.7D.
Additionally, if a "?version=<version>" parameter is included, then the list will include the rooms that have been deleted since that version. These entries will simply include the roomToken, plus a field "deleted" with a value of "true". None of the other fields need to be included for deleted rooms.