Key management APIs (#894)

Spec the e2e key-management APIs.
pull/977/head
Richard van der Hoff 7 years ago committed by GitHub
parent 97a7717d38
commit da6938b818

@ -0,0 +1,68 @@
# Copyright 2016 OpenMarket Ltd
#
# 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: DeviceKeys
description: Device identity keys
properties:
user_id:
type: string
description: |-
The ID of the user the device belongs to. Must match the user ID used
when logging in.
example: "@alice:example.com"
device_id:
type: string
description: |-
The ID of the device these keys belong to. Must match the device ID used
when logging in.
example: "JLAFKJWSCS"
algorithms:
type: array
items:
type: string
description: |-
The encryption algorithms supported by this device.
example: ["m.olm.curve25519-aes-sha256", "m.megolm.v1.aes-sha"]
keys:
type: object
description: |-
Public identity keys. The names of the properties should be in the
format ``<algorithm>:<device_id>``. The keys themselves should be
encoded as specified by the key algorithm.
additionalProperties:
type: string
example:
"curve25519:JLAFKJWSCS": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI"
"ed25519:JLAFKJWSCS": "lEuiRJBit0IG6nUf5pUzWTUEsRVVe/HJkoKuEww9ULI"
signatures:
type: object
description: |-
Signatures for the device key object. A map from user ID, to a map from
``<algorithm>:<device_id>`` to the signature.
The signature is calculated using the process described at `Signing
JSON`_.
additionalProperties:
type: object
additionalProperties:
type: string
example:
"@alice:example.com":
"ed25519:JLAFKJWSCS": "dSO80A01XiigH3uBiDVx/EjzaoycHcjq9lfQX0uWsqxl2giMIiSPR8a4d291W1ihKJL/a+myXS367WT6NAIcBA"
required:
- user_id
- device_id
- algorithms
- keys
- signatures

@ -0,0 +1,336 @@
# Copyright 2016 OpenMarket Ltd
#
# 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 Client Config 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:
"/keys/upload":
post:
summary: Upload end-to-end encryption keys.
description: |-
Publishes end-to-end encryption keys for the device.
security:
- accessToken: []
parameters:
- in: body
name: keys
description: |-
The keys to be published
schema:
type: object
properties:
device_keys:
description: |-
Identity keys for the device. May be absent if no new
identity keys are required.
allOf:
- $ref: definitions/device_keys.yaml
one_time_keys:
type: object
description: |-
One-time public keys for "pre-key" messages. The names of
the properties should be in the format
``<algorithm>:<key_id>``. The format of the key is determined
by the key algorithm.
May be absent if no new one-time keys are required.
additionalProperties:
type:
- string
- object
example:
"curve25519:AAAAAQ": "/qyvZvwjiTxGdGU0RCguDCLeR+nmsb3FfNG3/Ve4vU8"
signed_curve25519:AAAAHg:
key: "zKbLg+NrIjpnagy+pIY6uPL4ZwEG2v+8F9lmgsnlZzs"
signatures:
"@alice:example.com":
ed25519:JLAFKJWSCS: "FLWxXqGbwrb8SM3Y795eB6OA8bwBcoMZFXBqnTn58AYWZSqiD45tlBVcDa2L7RwdKXebW/VzDlnfVJ+9jok1Bw"
signed_curve25519:AAAAHQ:
key: "j3fR3HemM16M7CWhoI4Sk5ZsdmdfQHsKL1xuSft6MSw"
signatures:
"@alice:example.com":
ed25519:JLAFKJWSCS: "IQeCEPb9HFk217cU9kw9EOiusC6kMIkoIRnbnfOh5Oc63S1ghgyjShBGpu34blQomoalCyXWyhaaT3MrLZYQAA"
responses:
200:
description:
The provided keys were sucessfully uploaded.
schema:
type: object
properties:
one_time_key_counts:
type: object
additionalProperties:
type: integer
description: |-
For each key algorithm, the number of unclaimed one-time keys
of that type currently held on the server for this device.
example:
curve25519: 10
signed_curve25519: 20
required:
- one_time_key_counts
tags:
- End-to-end encryption
"/keys/query":
post:
summary: Download device identity keys.
description: |-
Returns the current devices and identity keys for the given users.
security:
- accessToken: []
parameters:
- in: body
name: query
description: |-
Query defining the keys to be downloaded
schema:
type: object
properties:
timeout:
type: integer
description: |-
The time (in milliseconds) to wait when downloading keys from
remote servers. 10 seconds is the recommended default.
example: 10000
device_keys:
type: object
description: |-
The keys to be downloaded. A map from user ID, to a list of
device IDs, or to an empty list to indicate all devices for the
corresponding user.
additionalProperties:
type: array
items:
type: string
description: "device ID"
example:
"@alice:example.com": []
required:
- device_keys
responses:
200:
description:
The device information
schema:
type: object
properties:
failures:
type: object
description: |-
If any remote homeservers could not be reached, they are
recorded here. The names of the properties are the names of
the unreachable servers.
If the homeserver could be reached, but the user or device
was unknown, no failure is recorded. Instead, the corresponding
user or device is missing from the ``device_keys`` result.
additionalProperties:
type: object
example: {}
device_keys:
type: object
description: |-
Information on the queried devices. A map from user ID, to a
map from device ID to device information. For each device,
the information returned will be the same as uploaded via
``/keys/upload``, with the addition of an ``unsigned``
property.
additionalProperties:
type: object
additionalProperties:
allOf:
- $ref: definitions/device_keys.yaml
properties:
unsigned:
title: UnsignedDeviceInfo
type: object
description: |-
Additional data added to the device key information
by intermediate servers, and not covered by the
signatures.
properties:
device_display_name:
type: string
description:
The display name which the user set on the device.
example:
"@alice:example.com":
JLAFKJWSCS: {
"user_id": "@alice:example.com",
"device_id": "JLAFKJWSCS",
"algorithms": [
"m.olm.curve25519-aes-sha256",
"m.megolm.v1.aes-sha"
],
"keys": {
"curve25519:JLAFKJWSCS": "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI",
"ed25519:JLAFKJWSCS": "lEuiRJBit0IG6nUf5pUzWTUEsRVVe/HJkoKuEww9ULI"
},
"signatures": {
"@alice:example.com": {
"ed25519:JLAFKJWSCS": "dSO80A01XiigH3uBiDVx/EjzaoycHcjq9lfQX0uWsqxl2giMIiSPR8a4d291W1ihKJL/a+myXS367WT6NAIcBA"
}
},
"unsigned": {
"device_display_name": "Alice's mobile phone"
}
}
tags:
- End-to-end encryption
"/keys/claim":
post:
summary: Claim one-time encryption keys.
description: |-
Claims one-time keys for use in pre-key messages.
security:
- accessToken: []
parameters:
- in: body
name: query
description: |-
Query defining the keys to be claimed
schema:
type: object
properties:
timeout:
type: integer
description: |-
The time (in milliseconds) to wait when downloading keys from
remote servers. 10 seconds is the recommended default.
example: 10000
one_time_keys:
type: object
description: |-
The keys to be claimed. A map from user ID, to a map from
device ID to algorithm name.
additionalProperties:
type: object
additionalProperties:
type: string
description: algorithm
example: "signed_curve25519"
example:
"@alice:example.com": { "JLAFKJWSCS": "curve25519" }
required:
- one_time_keys
responses:
200:
description:
The claimed keys
schema:
type: object
properties:
failures:
type: object
description: |-
If any remote homeservers could not be reached, they are
recorded here. The names of the properties are the names of
the unreachable servers.
If the homeserver could be reached, but the user or device
was unknown, no failure is recorded. Instead, the corresponding
user or device is missing from the ``one_time_keys`` result.
additionalProperties:
type: object
example: {}
one_time_keys:
type: object
description: |-
One-time keys for the queried devices. A map from user ID, to a
map from ``<algorithm>:<key_id>`` to the key object.
additionalProperties:
type: object
additionalProperties:
type:
- string
- object
example:
"@alice:example.com":
JLAFKJWSCS:
signed_curve25519:AAAAHg:
key: "zKbLg+NrIjpnagy+pIY6uPL4ZwEG2v+8F9lmgsnlZzs"
signatures:
"@alice:example.com":
ed25519:JLAFKJWSCS: "FLWxXqGbwrb8SM3Y795eB6OA8bwBcoMZFXBqnTn58AYWZSqiD45tlBVcDa2L7RwdKXebW/VzDlnfVJ+9jok1Bw"
tags:
- End-to-end encryption
"/keys/changes":
get:
summary: Query users with recent device key updates.
description: |-
Gets a list of users who have updated their device identity keys since a
previous sync token.
The server should include in the results any users who:
* currently share a room with the calling user (ie, both users have
membership state ``join``); *and*
* added new device identity keys or removed an existing device with
identity keys, between ``from`` and ``to``.
security:
- accessToken: []
parameters:
- in: query
name: from
type: string
description: |-
The desired start point of the list. Should be the ``next_batch`` field
from a response to an earlier call to |/sync|. Users who have not
uploaded new device identity keys since this point, nor deleted
existing devices with identity keys since then, will be excluded
from the results.
required: true
x-example: "s72594_4483_1934"
- in: query
name: to
type: string
description: |-
The desired end point of the list. Should be the ``next_batch``
field from a recent call to |/sync| - typically the most recent
such call. This may be used by the server as a hint to check its
caches are up to date.
required: true
x-example: "s75689_5632_2435"
responses:
200:
description:
The list of users who updated their devices.
schema:
type: object
properties:
changes:
type: array
items:
type: string
description: |-
The Matrix User IDs of all users who updated their device
identity keys.
example: ["@alice:example.com", "@bob:example.org"]
tags:
- End-to-end encryption

@ -242,6 +242,12 @@ paths:
description: |-
Information on the send-to-device messages for the client
device, as defined in |send_to_device_sync|_.
device_lists:
title: DeviceLists
type: object
description: |-
Information on end-to-end device updates, as specified in
|device_lists_sync|_.
examples:
application/json: |-
{

@ -46,6 +46,8 @@
- Allow guest accounts to use a number of endpoints which are required for
end-to-end encryption.
(`#751 <https://github.com/matrix-org/matrix-doc/pull/751>`_).
- Add key distribution APIs, for use with end-to-end encryption.
(`#894 <https://github.com/matrix-org/matrix-doc/pull/894>`_).
- Spec clarifications:

@ -1436,3 +1436,5 @@ have to wait in milliseconds before they can try again.
.. |/user/<user_id>/account_data/<type>| replace:: ``/user/<user_id>/account_data/<type>``
.. _/user/<user_id>/account_data/<type>: #put-matrix-client-%CLIENT_MAJOR_VERSION%-user-userid-account-data-type
.. _`Unpadded Base64`: ../appendices.html#unpadded-base64

@ -17,6 +17,290 @@ End-to-End Encryption
.. _module:e2e:
End to end encryption is being worked on and will be coming soon.
Matrix optionally supports end-to-end encryption, allowing rooms to be created
whose conversation contents is not decryptable or interceptable on any of the
participating homeservers.
You can read about what's underway at http://matrix.org/speculator/spec/drafts%2Fe2e/client_server/unstable.html#end-to-end-encryption.
.. WARNING::
End to end encryption is being worked on and will be coming soon. This
section is incomplete. You can read more about what's underway at
http://matrix.org/speculator/spec/drafts%2Fe2e/client_server/unstable.html#end-to-end-encryption.
Key Distribution
----------------
Encryption and Authentication in Matrix is based around public-key
cryptography. The Matrix protocol provides a basic mechanism for exchange of
public keys, though an out-of-band channel is required to exchange fingerprints
between users to build a web of trust.
Overview
~~~~~~~~
.. code::
1) Bob publishes the public keys and supported algorithms for his
device. This may include long-term identity keys, and/or one-time
keys.
+----------+ +--------------+
| Bob's HS | | Bob's Device |
+----------+ +--------------+
| |
|<=============|
/keys/upload
2) Alice requests Bob's public identity keys and supported algorithms.
+----------------+ +------------+ +----------+
| Alice's Device | | Alice's HS | | Bob's HS |
+----------------+ +------------+ +----------+
| | |
|=================>|==============>|
/keys/query <federation>
3) Alice selects an algorithm and claims any one-time keys needed.
+----------------+ +------------+ +----------+
| Alice's Device | | Alice's HS | | Bob's HS |
+----------------+ +------------+ +----------+
| | |
|=================>|==============>|
/keys/claim <federation>
Key algorithms
~~~~~~~~~~~~~~
The name ``ed25519`` corresponds to the `Ed25519`_ signature algorithm. The key
is a 32-byte Ed25519 public key, encoded using `unpadded Base64`_. Example:
.. code:: json
"SogYyrkTldLz0BXP+GYWs0qaYacUI0RleEqNT8J3riQ"
The name ``curve25519`` corresponds to the `Curve25519`_ ECDH algorithm. The
key is a 32-byte Curve25519 public key, encoded using `unpadded
Base64`_. Example:
.. code:: json
"JGLn/yafz74HB2AbPLYJWIVGnKAtqECOBf11yyXac2Y"
The name ``signed_curve25519`` also corresponds to the Curve25519 algorithm,
but keys using this algorithm are objects with the properties ``key`` (giving
the Base64-encoded 32-byte Curve25519 public key), and ``signatures`` (giving a
signature for the key object, as described in `Signing JSON`_). Example:
.. code:: json
{
"key":"06UzBknVHFMwgi7AVloY7ylC+xhOhEX4PkNge14Grl8",
"signatures": {
"@user:example.com": {
"ed25519:EGURVBUNJP": "YbJva03ihSj5mPk+CHMJKUKlCXCPFXjXOK6VqBnN9nA2evksQcTGn6hwQfrgRHIDDXO2le49x7jnWJHMJrJoBQ"
}
}
}
Device keys
~~~~~~~~~~~
Each device should have one Ed25519 signing key. This key should be generated
on the device from a cryptographically secure source, and the private part of
the key should never be exported from the device. This key is used as the
fingerprint for a device by other clients.
A device will generally need to generate a number of additional keys. Details
of these will vary depending on the messaging algorithm in use.
Algorithms generally require device identity keys as well as signing keys. Some
algorithms also require one-time keys to improve their secrecy and deniability.
These keys are used once during session establishment, and are then thrown
away.
For Olm version 1, each device requires a single Curve25519 identity key, and a
number of signed Curve25519 one-time keys.
Uploading keys
~~~~~~~~~~~~~~
A device uploads the public parts of identity keys to their homeserver as a
signed JSON object, using the |/keys/upload|_ API.
The JSON object must include the public part of the device's Ed25519 key, and
must be signed by that key, as described in `Signing JSON`_.
One-time keys are also uploaded to the homeserver using the |/keys/upload|_
API.
Devices must store the private part of each key they upload. They can
discard the private part of a one-time key when they receive a message using
that key. However it's possible that a one-time key given out by a homeserver
will never be used, so the device that generates the key will never know that
it can discard the key. Therefore a device could end up trying to store too
many private keys. A device that is trying to store too many private keys may
discard keys starting with the oldest.
Tracking the device list for a user
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Before Alice can send an encrypted message to Bob, she needs a list of each of
his devices and the associated identity keys, so that she can establish an
encryption session with each device. This list can be obtained by calling
|/keys/query|_, passing Bob's user ID in the ``device_keys`` parameter.
From time to time, Bob may add new devices, and Alice will need to know this so
that she can include his new devices for later encrypted messages. A naive
solution to this would be to call |/keys/query|_ before sending each message -
however, the number of users and devices may be large and this would be
inefficient.
It is therefore expected that each client will maintain a list of devices for a
number of users (in practice, typically each user with whom we share an
encrypted room). Furthermore, it is likely that this list will need to be
persisted between invocations of the client application (to preserve device
verification data and to alert Alice if Bob suddently gets a new
device).
Alice's client can maintain a list of Bob's devices via the following
process:
#. It first sets a flag to record that it is now tracking Bob's device list,
and a separate flag to indicate that its list of Bob's devices is
outdated. Both flags should be in storage which persists over client
restarts.
#. It then makes a request to |/keys/query|_, passing Bob's user ID in the
``device_keys`` parameter. When the request completes, it stores the
resulting list of devices in persistent storage, and clears the 'outdated'
flag.
#. During its normal processing of responses to |/sync|_, Alice's client
inspects the |device_lists|_ field. If it is tracking the device lists of
any of the listed users, then it marks the device lists for those users
outdated, and initiates another request to |/keys/query|_ for them.
#. Periodically, Alice's client stores the ``next_batch`` field of the result
from |/sync|_ in persistent storage. If Alice later restarts her client, it
can obtain a list of the users who have updated their device list while it
was offline by calling |/keys/changes|_, passing the recorded ``next_batch``
field as the ``from`` parameter. If the client is tracking the device list
of any of the users listed in the response, it marks them as outdated. It
combines this list with those already flagged as outdated, and initiates a
|/keys/query|_ requests for all of them.
.. Warning::
Bob may update one of his devices while Alice has a request to
``/keys/query`` in flight. Alice's client may therefore see Bob's user ID in
the ``device_lists`` field of the ``/sync`` response while the first request
is in flight, and initiate a second request to ``/keys/query``. This may
lead to either of two related problems.
The first problem is that, when the first request completes, the client will
clear the 'outdated' flag for Bob's devices. If the second request fails, or
the client is shut down before it completes, this could lead to Alice using
an outdated list of Bob's devices.
The second possibility is that, under certain conditions, the second request
may complete *before* the first one. When the first request completes, the
client could overwrite the later results from the second request with those
from the first request.
Clients MUST guard against these situations. For example, a client could
ensure that only one request to ``/keys/query`` is in flight at a time for
each user, by queuing additional requests until the first completes.
Alternatively, the client could make a new request immediately, but ensure
that the first request's results are ignored (possibly by cancelling the
request).
.. |device_lists| replace:: ``device_lists``
.. _`device_lists`: `device_lists_sync`_
Claiming one-time keys
~~~~~~~~~~~~~~~~~~~~~~
A client wanting to set up a session with another device can claim a one-time
key for that device. This is done by making a request to the |/keys/claim|_
API.
A homeserver should rate-limit the number of one-time keys that a given user or
remote server can claim. A homeserver should discard the public part of a one
time key once it has given that key to another user.
Protocol definitions
--------------------
{{keys_cs_http_api}}
.. anchor for link from /sync api spec
.. |device_lists_sync| replace:: End-to-end encryption
.. _device_lists_sync:
Extensions to /sync
~~~~~~~~~~~~~~~~~~~
This module adds an optional ``device_lists`` property to the |/sync|_
response, as specified below. The server need only populate this property for
an incremental ``/sync`` (ie, one where the ``since`` parameter was
specified). The client is expected to use |/keys/query|_ or |/keys/changes|_
for the equivalent functionality after an initial sync, as documented in
`Tracking the device list for a user`_.
.. todo: generate this from a swagger definition?
.. device_lists: { changed: ["@user:server", ... ]},
============ =========== =====================================================
Parameter Type Description
============ =========== =====================================================
device_lists DeviceLists Optional. Information on e2e device updates. Note:
only present on an incremental sync.
============ =========== =====================================================
``DeviceLists``
========= ========= =============================================
Parameter Type Description
========= ========= =============================================
changed [string] List of users who have updated their device identity keys
since the previous sync response.
========= ========= =============================================
Example response:
.. code:: json
{
"next_batch": "s72595_4483_1934",
"rooms": {"leave": {}, "join": {}, "invite": {}},
"device_lists": {
"changed": [
"@alice:example.com",
],
},
}
.. References
.. _ed25519: http://ed25519.cr.yp.to/
.. _curve25519: https://cr.yp.to/ecdh.html
.. _`Signing JSON`: ../appendices.html#signing-json
.. |m.olm.v1.curve25519-aes-sha2| replace:: ``m.olm.v1.curve25519-aes-sha2``
.. |/keys/upload| replace:: ``/keys/upload``
.. _/keys/upload: #post-matrix-client-%CLIENT_MAJOR_VERSION%-keys-upload
.. |/keys/query| replace:: ``/keys/query``
.. _/keys/query: #post-matrix-client-%CLIENT_MAJOR_VERSION%-keys-query
.. |/keys/claim| replace:: ``/keys/claim``
.. _/keys/claim: #post-matrix-client-%CLIENT_MAJOR_VERSION%-keys-claim
.. |/keys/changes| replace:: ``/keys/changes``
.. _/keys/changes: #get-matrix-client-%CLIENT_MAJOR_VERSION%-keys-changes

@ -12,11 +12,12 @@
.. See the License for the specific language governing permissions and
.. limitations under the License.
.. _module:to_device:
.. _`to-device`:
Send-to-Device messaging
========================
.. _module:to_device:
This module provides a means by which clients can exchange signalling messages
without them being stored permanently as part of a shared communication
history. A message is delivered exactly once to each client device.
@ -35,10 +36,13 @@ have the same event type. The device ID in the request body can be set to ``*``
to request that the message be sent to all known devices.
If there are send-to-device messages waiting for a client, they will be
returned by |/sync|_, as detailed in `Extensions to /sync`_. Clients should
returned by |/sync|_, as detailed in |Extensions|_. Clients should
inspect the ``type`` of each returned event, and ignore any they do not
understand.
.. |Extensions| replace:: Extensions to /sync
.. _Extensions: `send_to_device_sync`_
Server behaviour
----------------
Servers should store pending messages for local users until they are

@ -123,11 +123,6 @@ def get_json_schema_object_fields(obj, enforce_title=False):
logger.debug("Processing object with title '%s'", obj_title)
if enforce_title and not obj_title:
# Force a default titile of "NO_TITLE" to make it obvious in the
# specification output which parts of the schema are missing a title
obj_title = 'NO_TITLE'
additionalProps = obj.get("additionalProperties")
props = obj.get("properties")
if additionalProps and not props:
@ -151,14 +146,21 @@ def get_json_schema_object_fields(obj, enforce_title=False):
props[pretty_key] = props[key_name]
del props[key_name]
# Sometimes you just want to specify that a thing is an object without
# doing all the keys.
if not props:
return {
"type": obj_title,
"type": obj_title if obj_title else 'object',
"tables": [],
}
if enforce_title and not obj_title:
# Force a default titile of "NO_TITLE" to make it obvious in the
# specification output which parts of the schema are missing a title
obj_title = 'NO_TITLE'
required_keys = set(obj.get("required", []))
first_table_rows = []

Loading…
Cancel
Save