initial version of spec for key backups

pull/977/head
Hubert Chathi 5 years ago
parent c823bdbf70
commit e658b17070

@ -0,0 +1,50 @@
# Copyright 2019 The Matrix.org Foundation C.I.C.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
type: object
title: KeyBackupData
description: "The key data"
properties:
first_message_index:
description: |-
The index of the first message in the session that the key can decrypt.
type: integer
example: 1
forwarded_count:
description: |-
The number of times this key has been forwarded.
type: integer
example: 0
is_verified:
description: |-
Whether the device backing up the key was verified the device that the key
is from.
type: boolean
example: false
session_data:
description: |-
Algorithm-dependent data. See the documentation for the backup
algorithms in `Server-side key backups`_ for more information on the
expected format of the data.
type: object
example: {
"ephemeral": "base64+ephemeral+key",
"ciphertext": "base64+ciphertext+of+JSON+data",
"mac": "base64+mac+of+ciphertext"
}
required:
- first_message_index
- forwarded_count
- is_verified
- session_data

@ -0,0 +1,774 @@
# Copyright 2019 The Matrix.org Foundation C.I.C.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
swagger: '2.0'
info:
title: "Matrix Client-Server Key Backup API"
version: "1.0.0"
host: localhost:8008
schemes:
- https
- http
basePath: /_matrix/client/%CLIENT_MAJOR_VERSION%
consumes:
- application/json
produces:
- application/json
securityDefinitions:
$ref: definitions/security.yaml
paths:
"/room_keys/version":
post:
summary: Create a new backup.
description: |-
Creates a new backup
operationId: postRoomKeysVersion
security:
- accessToken: []
parameters:
- in: body
name: version
description: "The backup configuration"
schema:
type: object
properties:
algorithm:
description: The algorithm used for storing backups.
type: string
enum: ["m.megolm_backup.v1.curve25519-aes-sha2"]
example: "m.megolm_backup.v1.curve25519-aes-sha2"
auth_data:
description: |-
Algorithm-dependent data. See the documentation for the backup
algorithms in `Server-side key backups`_ for more information on the
expected format of the data.
type: object
example: {
"public_key": "abcdefg",
"signatures": {
"@alice:example.org": {
"ed25519:deviceid": "signature"
}
}
}
required:
- algorithm
- auth_data
responses:
200:
description:
The version of the new backup.
schema:
type: object
properties:
version:
type: string
description: The backup version
example: "1"
required:
- version
tags:
- End-to-end encryption
"/room_keys/version/{version}":
get:
summary: Get information about an existing backup.
description: |-
Get information about an existing backup.
operationId: getRoomKeysVersion
security:
- accessToken: []
parameters:
- in: path
type: string
name: version
description: |-
Optional. The backup version to get. If omitted, the current backup
is returned.
required: false
x-example: "1"
responses:
200:
description:
The information about the requested backup.
schema:
type: object
properties:
algorithm:
type: string
description: The algorithm used for storing backups.
type: string
enum: ["m.megolm_backup.v1.curve25519-aes-sha2"]
example: "m.megolm_backup.v1.curve25519-aes-sha2"
auth_data:
description: |-
Algorithm-dependent data. See the documentation for the backup
algorithms in `Server-side key backups`_ for more information on the
expected format of the data.
type: object
example: {
"public_key": "abcdefg",
"signatures": {
"@alice:example.org": {
"ed25519:deviceid": "signature"
}
}
}
count:
description: The number of key stored in the backup.
type: integer
example: 42
etag:
description: |-
An opaque string representing stored keys in the backup.
Clients can compare it with the ``etag`` value they received
in the request of their last key storage request. If not
equal, another client has modified the backup.
type: string
example: "anopaquestring"
version:
type: string
description: The backup version
example: "1"
required:
- algorithm
- auth_data
- count
- etag
- version
404:
description:
The backup specified does not exist
examples:
application/json: {
"errcode": "M_NOT_FOUND",
"error": "Unknown backup version"
}
schema:
"$ref": "definitions/errors/error.yaml"
tags:
- End-to-end encryption
put:
summary: Update information about an existing backup.
description: |-
Update information about an existing backup. Only ``auth_data`` can be modified.
operationId: putRoomKeysVersion
security:
- accessToken: []
parameters:
- in: path
type: string
name: version
description: |-
The backup version to update.
required: true
x-example: "1"
- in: body
name: version
description: "The backup configuration"
schema:
type: object
properties:
algorithm:
description: |-
The algorithm used for storing backups. Must be the same as
the algorithm currently used by the backup.
type: string
enum: ["m.megolm_backup.v1.curve25519-aes-sha2"]
example: "m.megolm_backup.v1.curve25519-aes-sha2"
auth_data:
description: |-
Algorithm-dependent data. See the documentation for the backup
algorithms in `Server-side key backups`_ for more information on the
expected format of the data.
type: object
example: {
"public_key": "abcdefg",
"signatures": {
"@alice:example.org": {
"ed25519:deviceid": "signature"
}
}
}
version:
description: |-
The backup version. If present, must be the same as the
version in the path parameter.
type: string
example: "1"
required:
- algorithm
- auth_data
responses:
200:
description: The update succeeded
schema:
type: object
properties: {}
404:
description: The backup specified does not exist
examples:
application/json: {
"errcode": "M_NOT_FOUND",
"error": "Unknown backup version"
}
schema:
"$ref": "definitions/errors/error.yaml"
tags:
- End-to-end encryption
"/room_keys/keys/{roomId}/{sessionId}":
put:
summary: Store a key in the backup
description: |-
Store a key in the backup.
operationId: postRoomKeysKeyRoomIdSessionId
security:
- accessToken: []
parameters:
- in: query
type: string
name: version
description: |-
The backup in which to store the key. Must be the current backup.
required: true
x-example: "1"
- in: path
type: string
name: roomId
description: The ID of the room that the key is for.
required: true
x-example: "!roomid:example.org"
- in: path
type: string
name: sessionId
description: The ID of the megolm session that the key is for.
required: true
x-example: "sessionid"
- in: body
name: data
description: "The key data"
schema:
"$ref": "definitions/key_backup_data.yaml"
responses:
200:
description: The update succeeded
schema:
type: object
properties:
etag:
description: |-
The new etag value representing stored keys in the backup.
See ``GET /room_keys/version/{version}`` for more details.
type: string
example: "abcdefg"
count:
description: The number of keys stored in the backup
type: integer
example: 10
required:
- etag
- count
403:
description: |-
The version specified does not match the current backup version.
The current version will be included in the ``current_version``
field.
examples:
application/json: {
"errcode": "M_WRONG_ROOM_KEYS_VERSION",
"error": "Wrong backup version.",
"current_version": "42"
}
schema:
"$ref": "definitions/errors/error.yaml"
tags:
- End-to-end encryption
get:
summary: Retrieve a key from the backup
description: |-
Retrieve a key from the backup.
operationId: getRoomKeysKeyRoomIdSessionId
security:
- accessToken: []
parameters:
- in: query
type: string
name: version
description: |-
The backup from which to retrieve the key
required: true
x-example: "1"
- in: path
type: string
name: roomId
description: The ID of the room that the requested key is for.
required: true
x-example: "!roomid:example.org"
- in: path
type: string
name: sessionId
description: The ID of the megolm session whose key is requested.
required: true
x-example: "sessionid"
responses:
200:
description: The key data
schema:
"$ref": "definitions/key_backup_data.yaml"
404:
description: The key or backup was not found.
examples:
application/json: {
"errcode": "M_NOT_FOUND",
"error": "Key not found."
}
schema:
"$ref": "definitions/errors/error.yaml"
delete:
summary: Delete a key from the backup
description: |-
Delete a key from the backup.
operationId: deleteRoomKeysKeyRoomIdSessionId
security:
- accessToken: []
parameters:
- in: query
type: string
name: version
description: |-
The backup from which to delete the key
required: true
x-example: "1"
- in: path
type: string
name: roomId
description: The ID of the room that the specified key is for.
required: true
x-example: "!roomid:example.org"
- in: path
type: string
name: sessionId
description: The ID of the megolm session whose key is to be deleted.
required: true
x-example: "sessionid"
responses:
200:
description: The update succeeded
schema:
type: object
properties:
etag:
description: |-
The new etag value representing stored keys in the backup.
See ``GET /room_keys/version/{version}`` for more details.
type: string
example: "abcdefg"
count:
description: The number of keys stored in the backup
type: integer
example: 10
required:
- etag
- count
404:
description: |-
The backup was not found.
examples:
application/json: {
"errcode": "M_NOT_FOUND",
"error": "Unknown backup version"
}
schema:
"$ref": "definitions/errors/error.yaml"
"/room_keys/keys/{roomId}":
put:
summary: Store several keys in the backup for a given room.
description: |-
Store a key in the backup.
operationId: postRoomKeysKeyRoomId
security:
- accessToken: []
parameters:
- in: query
type: string
name: version
description: |-
The backup in which to store the keys. Must be the current backup.
required: true
x-example: "1"
- in: path
type: string
name: roomId
description: The ID of the room that the keys are for.
required: true
x-example: "!roomid:example.org"
- in: body
description: "The backup data"
name: backupData
schema:
type: object
properties:
sessions:
type: object
description: |-
A map of session IDs to key data.
additionalProperties:
allOf:
- $ref: "definitions/key_backup_data.yaml"
example: {
"sessionid1": {
"ephemeral": "base64+ephemeral+key",
"ciphertext": "base64+ciphertext+of+JSON+data",
"mac": "base64+mac+of+ciphertext"
}
}
responses:
200:
description: The update succeeded
schema:
type: object
properties:
etag:
description: |-
The new etag value representing stored keys in the backup.
See ``GET /room_keys/version/{version}`` for more details.
type: string
example: "abcdefg"
count:
description: The number of keys stored in the backup
type: integer
example: 10
required:
- etag
- count
403:
description: |-
The version specified does not match the current backup version.
The current version will be included in the ``current_version``
field.
examples:
application/json: {
"errcode": "M_WRONG_ROOM_KEYS_VERSION",
"error": "Wrong backup version.",
"current_version": "42"
}
schema:
"$ref": "definitions/errors/error.yaml"
404:
description: |-
The backup was not found.
examples:
application/json: {
"errcode": "M_NOT_FOUND",
"error": "Unknown backup version"
}
schema:
"$ref": "definitions/errors/error.yaml"
tags:
- End-to-end encryption
get:
summary: Retrieve the keys from the backup for a given room
description: |-
Retrieve the keys from the backup for a given room
operationId: getRoomKeysKeyRoomId
security:
- accessToken: []
parameters:
- in: query
type: string
name: version
description: |-
The backup from which to retrieve the key
required: true
x-example: "1"
- in: path
type: string
name: roomId
description: The ID of the room that the requested key is for.
required: true
x-example: "!roomid:example.org"
responses:
200:
description: |-
The key data. If no keys are found, then an object with an empty
``sessions`` property will be returned (``{"sessions": {}}``).
schema:
type: object
properties:
sessions:
type: object
description: |-
A map of session IDs to key data.
additionalProperties:
allOf:
- $ref: "definitions/key_backup_data.yaml"
example: {
"sessionid1": {
"ephemeral": "base64+ephemeral+key",
"ciphertext": "base64+ciphertext+of+JSON+data",
"mac": "base64+mac+of+ciphertext"
}
}
404:
description: |-
The backup was not found.
examples:
application/json: {
"errcode": "M_NOT_FOUND",
"error": "Unknown backup version"
}
schema:
"$ref": "definitions/errors/error.yaml"
delete:
summary: Delete a key from the backup
description: |-
Delete a key from the backup.
operationId: deleteRoomKeysKeyRoomId
security:
- accessToken: []
parameters:
- in: query
type: string
name: version
description: |-
The backup from which to delete the key
required: true
x-example: "1"
- in: path
type: string
name: roomId
description: The ID of the room that the specified key is for.
required: true
x-example: "!roomid:example.org"
responses:
200:
description: The update succeeded
schema:
type: object
properties:
etag:
description: |-
The new etag value representing stored keys in the backup.
See ``GET /room_keys/version/{version}`` for more details.
type: string
example: "abcdefg"
count:
description: The number of keys stored in the backup
type: integer
example: 10
required:
- etag
- count
404:
description: |-
The backup was not found.
examples:
application/json: {
"errcode": "M_NOT_FOUND",
"error": "Unknown backup version"
}
schema:
"$ref": "definitions/errors/error.yaml"
"/room_keys/keys":
put:
summary: Store several keys in the backup.
description: |-
Store several keys in the backup.
operationId: postRoomKeysKey
security:
- accessToken: []
parameters:
- in: query
type: string
name: version
description: |-
The backup in which to store the keys. Must be the current backup.
required: true
x-example: "1"
- in: body
description: "The backup data"
name: backupData
schema:
type: object
properties:
rooms:
type: object
description: |-
A map of room IDs to session IDs to key data.
additionalProperties:
type: object
additionalProperties:
allOf:
- $ref: "definitions/key_backup_data.yaml"
example: {
"!room:example.org": {
"sessions": {
"sessionid1": {
"ephemeral": "base64+ephemeral+key",
"ciphertext": "base64+ciphertext+of+JSON+data",
"mac": "base64+mac+of+ciphertext"
}
}
}
}
responses:
200:
description: The update succeeded
schema:
type: object
properties:
etag:
description: |-
The new etag value representing stored keys in the backup.
See ``GET /room_keys/version/{version}`` for more details.
type: string
example: "abcdefg"
count:
description: The number of keys stored in the backup
type: integer
example: 10
required:
- etag
- count
403:
description: |-
The version specified does not match the current backup version.
The current version will be included in the ``current_version``
field.
examples:
application/json: {
"errcode": "M_WRONG_ROOM_KEYS_VERSION",
"error": "Wrong backup version.",
"current_version": "42"
}
schema:
"$ref": "definitions/errors/error.yaml"
404:
description: |-
The backup was not found.
examples:
application/json: {
"errcode": "M_NOT_FOUND",
"error": "Unknown backup version"
}
schema:
"$ref": "definitions/errors/error.yaml"
tags:
- End-to-end encryption
get:
summary: Retrieve the keys from the backup for a given room
description: |-
Retrieve the keys from the backup for a given room
operationId: getRoomKeysKeyRoomId
security:
- accessToken: []
parameters:
- in: query
type: string
name: version
description: |-
The backup from which to retrieve the keys. If omitted, the keys are
retrieved from the current backup.
x-example: "1"
- in: path
type: string
name: roomId
description: The ID of the room that the requested key is for.
required: true
x-example: "!roomid:example.org"
responses:
200:
description: |-
The key data. If no keys are found, then an object with an empty
``rooms`` property will be returned (``{"rooms": {}}``).
schema:
type: object
properties:
rooms:
type: object
description: |-
A map of room IDs to session IDs to key data.
additionalProperties:
type: object
additionalProperties:
allOf:
- $ref: "definitions/key_backup_data.yaml"
example: {
"!room:example.org": {
"sessions": {
"sessionid1": {
"ephemeral": "base64+ephemeral+key",
"ciphertext": "base64+ciphertext+of+JSON+data",
"mac": "base64+mac+of+ciphertext"
}
}
}
}
404:
description: The backup was not found.
examples:
application/json: {
"errcode": "M_NOT_FOUND",
"error": "Unknown backup version."
}
schema:
"$ref": "definitions/errors/error.yaml"
delete:
summary: Delete a key from the backup
description: |-
Delete a key from the backup.
operationId: deleteRoomKeysKeyRoomId
security:
- accessToken: []
parameters:
- in: query
type: string
name: version
description: |-
The backup from which to delete the key
required: true
x-example: "1"
- in: path
type: string
name: roomId
description: The ID of the room that the specified key is for.
required: true
x-example: "!roomid:example.org"
responses:
200:
description: The update succeeded
schema:
type: object
properties:
etag:
description: |-
The new etag value representing stored keys in the backup.
See ``GET /room_keys/version/{version}`` for more details.
type: string
example: "abcdefg"
count:
description: The number of keys stored in the backup
type: integer
example: 10
required:
- etag
- count
404:
description: |-
The backup was not found.
examples:
application/json: {
"errcode": "M_NOT_FOUND",
"error": "Unknown backup version"
}
schema:
"$ref": "definitions/errors/error.yaml"

@ -767,6 +767,126 @@ previously-received ``request`` message with the same ``request_id`` and
A reasonable strategy is for a user's client to only send keys requested by the A reasonable strategy is for a user's client to only send keys requested by the
verified devices of the same user. verified devices of the same user.
Server-side key backups
~~~~~~~~~~~~~~~~~~~~~~~
Devices may upload encrypted copies of keys to the server. When a device tries
to read a message that it does not have keys for, it may request the key from
the server and decrypt it. Backups are per-user, and users may replace backups
with new backups.
In contrast with `Key requests`_, Server-side key backups do not require another
device to be online from which to request keys. However, as the session keys are
stored on the server encrypted, it requires users to enter a decryption key to
decrypt the session keys.
To create a backup, a client will call ``POST /room_keys/version`` and define
how the keys are to be encrypted through the backup's ``auth_data``; other
clients can discover the backup by calling ``GET /room_keys/version``. Keys
are encrypted according to the backups ``auth_data`` and added to the backup by
calling ``PUT /room_keys/keys?version=$v`` or one of its variants, and can be
retrieved by calling ``GET /room_keys/keys?version=$v`` or one of its variants.
Backups can also be deleted using ``DELETE /room_keys/version``, or individual
keys can be deleted using ``DELETE /room_key/keys?version=$v`` or one of its
variants.
Clients must only store keys in backups after they have ensured that the
``auth_data`` is trusted, either by checking the signatures on it, or by
deriving the public key from a private key that it obtained from a trusted
source.
When a client uploads a key for a session that the server already has a key
for, the server will choose to either keep the existing key or replace it with
the new key based on the key metadata as follows:
- if the keys have different values for ``is_verified``, then it will keep the
key that has ``is_verified`` set to ``true``;
- if they have the same values for ``is_verified``, then it will keep the key
with a lower ``first_message_index``;
- and finally, is ``is_verified`` and ``first_message_index`` are equal, then
it will keep the key with a lower ``forwarded_count``.
Recovery key
<<<<<<<<<<<<
If the recovery key (the private half of the backup encryption key) is
presented to the user to save, it is presented as a string constructed as
follows:
1. The 256-bit curve25519 private key is prepended by the bytes ``0x8B`` and
``0x01``
2. All the bytes in the string above, including the two header bytes, are XORed
together to form a parity byte. This parity byte is appended to the byte
string.
3. The byte string is encoded using base58, using the same mapping as is used
for Bitcoin addresses.
4. A space should be added after every 4th character.
When reading in a recovery key, clients must disregard whitespace, and perform
the reverse of steps 1 through 3.
Backup algorithm: ``m.megolm_backup.v1.curve25519-aes-sha2``
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
When a backup is created with the ``algorithm`` set to
``m.megolm_backup.v1.curve25519-aes-sha2``, the ``auth_data`` should have the
following format:
``AuthData``
.. table::
:widths: auto
========== =========== ======================================================
Parameter Type Description
========== =========== ======================================================
public_key string Required. The curve25519 public key used to encrypt
the backups, encoded in unpadded base64.
signatures {string: Optional. Signatures of the ``auth_data``, as Signed
{string: JSON
string}}
========== =========== ======================================================
The ``session_data`` field in the backups is constructed as follows:
1. Encode the session key to be backed up as a JSON object with the properties:
.. table::
:widths: auto
=============================== ======== =========================================
Parameter Type Description
=============================== ======== =========================================
algorithm string Required. The end-to-end message
encryption algorithm that the key is
for. Must be ``m.megolm.v1.aes-sha2``.
sender_key string Required. Unpadded base64-encoded
device curve25519 key.
sender_claimed_keys {string: Required. Object containing the
string} identity key for the sending device.
forwarding_curve25519_key_chain [string] Required. Zero or more curve25519 keys
for devices who forwarded the session key.
session_key string Required. Unpadded base64-encoded
session key in `session-sharing format
<https://gitlab.matrix.org/matrix-org/olm/blob/master/docs/megolm.md#session-sharing-format>`_.
=============================== ======== =========================================
2. Generate an ephemeral curve25519 key, and perform an ECDH with the ephemeral
key and the backup's public key to generate a shared secret. The public
half of the ephemeral key, encoded using unpadded base64, becomes the ``ephemeral``
property of the ``session_data``.
3. Using the shared secret, generate 80 bytes by performing an HKDF using
SHA-256 as the hash, with a salt of 32 bytes of 0, and with the empty string
as the info. The first 32 bytes are used as the AES key, the next 32 bytes
are used as the MAC key, and the last 16 bytes are used as the AES
initialization vector.
4. Stringify the JSON object, and encrypt it using AES-CBC-256 with PKCS#7
padding. This encrypted data, encoded using unpadded base64, becomes the
``ciphertext`` property of the ``session_data``.
5. Pass the raw encrypted data (prior to base64 encoding) through HMAC-SHA-256
using the MAC key generated above. The first 8 bytes of the resulting MAC
are base64-encoded, and become the ``mac`` property of the ``session_data``.
Key exports Key exports
~~~~~~~~~~~ ~~~~~~~~~~~
@ -1089,6 +1209,11 @@ Key management API
{{keys_cs_http_api}} {{keys_cs_http_api}}
Key Backup API
~~~~~~~~~~~~~~
{{key_backup_cs_http_api}}
.. anchor for link from /sync api spec .. anchor for link from /sync api spec
.. |device_lists_sync| replace:: End-to-end encryption .. |device_lists_sync| replace:: End-to-end encryption

Loading…
Cancel
Save