Merge remote-tracking branch 'matrix-org/master' into travis/c2s/read-markers

pull/977/head
Travis Ralston 6 years ago
commit 679ddabb53

@ -47,13 +47,15 @@ paths:
description: The lookup was successful.
examples:
application/json: {
"threepids": [
{
"medium": "email",
"address": "monkey@banana.island"
}
]
}
"threepids": [
{
"medium": "email",
"address": "monkey@banana.island",
"validated_at": 1535176800000,
"added_at": 1535336848756
}
]
}
schema:
type: object
properties:
@ -70,6 +72,19 @@ paths:
address:
type: string
description: The third party identifier address.
validated_at:
type: integer
format: int64
description: |-
The timestamp, in milliseconds, when the identifier was
validated by the identity service.
added_at:
type: integer
format: int64
description:
The timestamp, in milliseconds, when the homeserver
associated the third party identifier with the user.
required: ['medium', 'address', 'validated_at', 'added_at']
tags:
- User data
post:
@ -133,6 +148,41 @@ paths:
"$ref": "definitions/errors/error.yaml"
tags:
- User data
"/account/3pid/delete":
post:
summary: Deletes a third party identifier from the user's account
description: |-
Removes a third party identifier from the user's account. This might not
cause an unbind of the identifier from the identity service.
operationId: delete3pidFromAccount
security:
- accessToken: []
parameters:
- in: body
name: body
schema:
type: object
properties:
medium:
type: string
description: The medium of the third party identifier being removed.
enum: ["email", "msisdn"]
example: "email"
address:
type: string
description: The third party address being removed.
example: "example@domain.com"
required: ['medium', 'address']
responses:
200:
description: |-
The homeserver has disassociated the third party identifier from the
user.
schema:
type: object
properties: {}
tags:
- User data
"/account/3pid/email/requestToken":
post:
summary: Requests a validation token be sent to the given email address for the purpose of adding an email address to an account

@ -1,4 +1,5 @@
# Copyright 2016 OpenMarket Ltd
# Copyright 2018 New Vector Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -13,10 +14,10 @@
# limitations under the License.
properties:
events:
description: List of events
description: List of events.
items:
allOf:
- $ref: event.yaml
- $ref: event-schemas/schema/core-event-schema/event.yaml
type: object
type: array
type: object

@ -0,0 +1,27 @@
# Copyright 2018 New Vector 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.
properties:
events:
description: List of events.
items:
allOf:
- $ref: event-schemas/schema/core-event-schema/sync_room_event.yaml
type: object
required:
- event_id
#- room_id - Not in /sync
- sender
- origin_server_ts
type: array
type: object

@ -0,0 +1,28 @@
# Copyright 2018 New Vector 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.
properties:
events:
description: List of events.
items:
allOf:
- $ref: event-schemas/schema/core-event-schema/sync_state_event.yaml
type: object
required:
- event_id
#- room_id - Not in /sync
- sender
- origin_server_ts
- state_key
type: array
type: object

@ -1,4 +1,5 @@
# Copyright 2016 OpenMarket Ltd
# Copyright 2018 New Vector Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -12,14 +13,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
allOf:
- $ref: event_batch.yaml
- $ref: room_event_batch.yaml
properties:
limited:
description: True if the number of events returned was limited by the ``limit``
on the filter
on the filter.
type: boolean
prev_batch:
description: A token that can be supplied to the ``from`` parameter of the
rooms/{roomId}/messages endpoint
rooms/{roomId}/messages endpoint.
type: string
type: object

@ -117,6 +117,13 @@ paths:
A display name to assign to the newly-created device. Ignored
if ``device_id`` corresponds to a known device.
example: Jungle Phone
inhibit_login:
type: boolean
description: |-
If true, an ``access_token`` and ``device_id`` should not be
returned from this call, therefore preventing an automatic
login. Defaults to false.
example: false
responses:
200:
description: The account has been registered.
@ -141,6 +148,7 @@ paths:
description: |-
An access token for the account.
This access token can then be used to authorize other requests.
Required if the ``inhibit_login`` option is false.
home_server:
type: string
description: |-
@ -155,6 +163,8 @@ paths:
description: |-
ID of the registered device. Will be the same as the
corresponding parameter in the request, if one was specified.
Required if the ``inhibit_login`` option is false.
required: ['user_id']
400:
description: |-
Part of the request was invalid. This may include one of the following error codes:

@ -135,7 +135,7 @@ paths:
``timeline``, if ``since`` is not given, or
``full_state`` is true).
allOf:
- $ref: "definitions/event_batch.yaml"
- $ref: "definitions/state_event_batch.yaml"
timeline:
title: Timeline
type: object
@ -202,8 +202,35 @@ paths:
the room then the current state will be given as a
delta against the archived ``state`` not the
``invite_state``.
allOf:
- $ref: "definitions/event_batch.yaml"
properties:
events:
description: The StrippedState events that form the invite state.
items:
description: |-
A stripped down state event, with only the ``type``, ``state_key``,
``sender``, and ``content`` keys.
properties:
content:
description: The ``content`` for the event.
title: EventContent
type: object
state_key:
description: The ``state_key`` for the event.
type: string
type:
description: The ``type`` for the event.
type: string
sender:
description: The ``sender`` for the event.
type: string
required:
- type
- state_key
- content
- sender
title: StrippedState
type: object
type: array
leave:
title: Left rooms
type: object
@ -219,7 +246,7 @@ paths:
description: |-
The state updates for the room up to the start of the timeline.
allOf:
- $ref: "definitions/event_batch.yaml"
- $ref: "definitions/state_event_batch.yaml"
timeline:
title: Timeline
type: object
@ -270,6 +297,8 @@ paths:
description: |-
Information on end-to-end encryption keys, as specified
in |device_lists_sync|_.
required:
- next_batch
examples:
application/json: {
"next_batch": "s72595_4483_1934",
@ -321,7 +350,6 @@ paths:
{
"sender": "@alice:example.com",
"type": "m.room.message",
"age": 124524,
"txn_id": "1234",
"content": {
"body": "I am a fish",

@ -43,14 +43,14 @@ paths:
required: true
description: |-
The id of the user to get tags for. The access token must be
authorized to make requests for this user id.
authorized to make requests for this user ID.
x-example: "@alice:example.com"
- in: path
type: string
name: roomId
required: true
description: |-
The id of the room to get tags for.
The ID of the room to get tags for.
x-example: "!726s6s6q:example.com"
responses:
200:
@ -60,16 +60,26 @@ paths:
type: object
properties:
tags:
title: Tags
type: object
additionalProperties:
title: Tag
type: object
properties:
order:
type: number
format: float
description: |-
A number in a range ``[0,1]`` describing a relative
position of the room under the given tag.
additionalProperties: true
examples:
application/json: {
"tags": {
"m.favourite": {},
"u.Work": {"order": "1"},
"u.Customers": {}
}
"tags": {
"m.favourite": {"order": 0.1},
"u.Work": {"order": 0.7},
"u.Customers": {}
}
}
tags:
- User data
"/user/{userId}/rooms/{roomId}/tags/{tag}":
@ -87,14 +97,14 @@ paths:
required: true
description: |-
The id of the user to add a tag for. The access token must be
authorized to make requests for this user id.
authorized to make requests for this user ID.
x-example: "@alice:example.com"
- in: path
type: string
name: roomId
required: true
description: |-
The id of the room to add a tag to.
The ID of the room to add a tag to.
x-example: "!726s6s6q:example.com"
- in: path
type: string
@ -102,7 +112,7 @@ paths:
required: true
description: |-
The tag to add.
x-example: "work"
x-example: "u.work"
- in: body
name: body
required: true
@ -110,8 +120,17 @@ paths:
Extra data for the tag, e.g. ordering.
schema:
type: object
properties:
order:
type: number
format: float
description: |-
A number in a range ``[0,1]`` describing a relative
position of the room under the given tag.
additionalProperties: true
example: {
"order": "1"}
"order": 0.25
}
responses:
200:
description:
@ -119,8 +138,7 @@ paths:
schema:
type: object
examples:
application/json: {
}
application/json: {}
tags:
- User data
delete:
@ -137,14 +155,14 @@ paths:
required: true
description: |-
The id of the user to remove a tag for. The access token must be
authorized to make requests for this user id.
authorized to make requests for this user ID.
x-example: "@alice:example.com"
- in: path
type: string
name: roomId
required: true
description: |-
The id of the room to remove a tag from.
The ID of the room to remove a tag from.
x-example: "!726s6s6q:example.com"
- in: path
type: string
@ -152,15 +170,14 @@ paths:
required: true
description: |-
The tag to remove.
x-example: "work"
x-example: "u.work"
responses:
200:
description:
The tag was successfully removed
The tag was successfully removed.
schema:
type: object
examples:
application/json: {
}
application/json: {}
tags:
- User data

@ -31,8 +31,16 @@ paths:
post:
summary: Searches the user directory.
description: |-
This API performs a server-side search over all users registered on the server.
It searches user ID and displayname case-insensitively for users that you share a room with or that are in public rooms.
Performs a search for users on the homeserver. The homeserver may
determine which subset of users are searched, however the homeserver
MUST at a minimum consider the users the requesting user shares a
room with and those who reside in public rooms (known to the homeserver).
The search MUST consider local users to the homeserver, and SHOULD
query remote users as part of the search.
The search is performed case-insensitively on user IDs and display
names preferably using a collation determined based upon the
``Accept-Language`` header provided in the request, if present.
operationId: searchUserDirectory
security:
- accessToken: []
@ -48,7 +56,7 @@ paths:
example: "foo"
limit:
type: integer
description: The maximum number of results to return (Defaults to 10).
description: The maximum number of results to return. Defaults to 10.
example: 10
required: ["search_term"]
responses:
@ -56,15 +64,15 @@ paths:
description: The results of the search.
examples:
application/json: {
"results": [
{
"user_id": "@foo:bar.com",
"display_name": "Foo",
"avatar_url": "mxc://bar.com/foo"
}
],
"limited": false
}
"results": [
{
"user_id": "@foo:bar.com",
"display_name": "Foo",
"avatar_url": "mxc://bar.com/foo"
}
],
"limited": false
}
schema:
type: object
required: ["results", "limited"]

@ -69,7 +69,8 @@ paths:
get:
summary: Check whether a long-term public key is valid.
description: |-
Check whether a long-term public key is valid.
Check whether a long-term public key is valid. The response should always
be the same, provided the key exists.
operationId: isPubKeyValid
parameters:
- in: query

@ -23,7 +23,8 @@ allOf:
hashes:
type: object
title: Event Hash
description: Hashes of the PDU, following the algorithm specified in `Signing Events`_.
description: |-
Content hashes of the PDU, following the algorithm specified in `Signing Events`_.
example: {
"sha256": "thishashcoversallfieldsincasethisisredacted"
}

@ -55,8 +55,8 @@ properties:
prev_events:
type: array
description: |-
Event IDs and hashes of the most recent events in the room that the homeserver was aware
of when it made this event.
Event IDs and reference hashes for the most recent events in the room
that the homeserver was aware of when it made this event.
items:
type: array
maxItems: 2
@ -86,7 +86,7 @@ properties:
auth_events:
type: array
description: |-
An event reference list containing the authorization events that would
Event IDs and reference hashes for the authorization events that would
allow this event to be in the room.
items:
type: array

@ -194,3 +194,126 @@ paths:
type: object
description: An empty object
example: {}
"/3pid/onbind":
put:
summary: |-
Notifies the server that a third party identifier has been bound to one
of its users.
description: |-
Used by Identity Servers to notify the homeserver that one of its users
has bound a third party identifier successfully, including any pending
room invites the Identity Server has been made aware of.
operationId: onBindThirdPartyIdentifier
parameters:
- in: body
name: body
type: object
required: true
schema:
type: object
properties:
medium:
type: string
description: |-
The type of third party identifier. Currently only "email" is
a possible value.
example: "email"
address:
type: string
description: |-
The third party identifier itself. For example, an email address.
example: "alice@domain.com"
mxid:
type: string
description: The user that is now bound to the third party identifier.
example: "@alice:matrix.org"
invites:
type: array
description: |-
A list of pending invites that the third party identifier has received.
items:
type: object
title: Third Party Invite
properties:
medium:
type: string
description: |-
The type of third party invite issues. Currently only
"email" is used.
example: "email"
address:
type: string
description: |-
The third party identifier that received the invite.
example: "alice@domain.com"
mxid:
type: string
description: The now-bound user ID that received the invite.
example: "@alice:matrix.org"
room_id:
type: string
description: The room ID the invite is valid for.
example: "!somewhere:example.org"
sender:
type: string
description: The user ID that sent the invite.
example: "@bob:matrix.org"
# TODO (TravisR): Make this reusable when doing IS spec changes
# also make sure it isn't lying about anything, like the key version
signed:
type: object
title: Identity Server Signatures
description: |-
Signature from the Identity Server using a long-term private
key.
properties:
mxid:
type: string
description: |-
The user ID that has been bound to the third party
identifier.
example: "@alice:matrix.org"
token:
type: string
# TODO: What is this actually?
description: A token.
example: "Hello World"
signatures:
type: object
title: Identity Server Signature
description: |-
The signature from the identity server. The ``string`` key
is the identity server's domain name, such as vector.im
additionalProperties:
type: object
title: Identity Server Domain Signature
description: The signature for the identity server.
properties:
"ed25519:0":
type: string
description: The signature.
example: "SomeSignatureGoesHere"
required: ['ed25519:0']
example: {
"vector.im": {
"ed25519:0": "SomeSignatureGoesHere"
}
}
required: ['mxid', 'token', 'signatures']
required:
- medium
- address
- mxid
- room_id
- sender
- signed
required: ['medium', 'address', 'mxid', 'invites']
responses:
200:
description: The homeserver has processed the notification.
examples:
application/json: {}
schema:
type: object
description: An empty object
example: {}

@ -0,0 +1,189 @@
# Copyright 2018 New Vector 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 Federation User Key Management API"
version: "1.0.0"
host: localhost:8448
schemes:
- https
basePath: /_matrix/federation/v1
consumes:
- application/json
produces:
- application/json
securityDefinitions:
$ref: definitions/security.yaml
paths:
"/user/keys/claim":
post:
summary: Claims one-time encryption keys for a user.
description: |-
Claims one-time keys for use in pre-key messages.
operationId: claimUserEncryptionKeys
security:
- signedRequest: []
parameters:
- in: body
name: body
type: object
required: true
schema:
type: object
properties:
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": "signed_curve25519"
}
}
required:
- one_time_keys
responses:
200:
description: The claimed keys
schema:
type: object
properties:
one_time_keys:
type: object
description: |-
One-time keys for the queried devices. A map from user ID, to a
map from devices to a map from ``<algorithm>:<key_id>`` to the key object.
additionalProperties:
type: object
additionalProperties:
type:
- string
- object
required: ['one_time_keys']
examples:
application/json: {
"one_time_keys": {
"@alice:example.com": {
"JLAFKJWSCS": {
"signed_curve25518:AAAAHg": {
"key": "zKbLg+NrIjpnagy+pIY6uPL4ZwEG2v+8F9lmgsnlZzs",
"signatures": {
"@alice:example.com": {
"ed25519:JLAFKJWSCS": "FLWxXqGbwrb8SM3Y795eB6OA8bwBcoMZFXBqnTn58AYWZSqiD45tlBVcDa2L7RwdKXebW/VzDlnfVJ+9jok1Bw"
}
}
}
}
}
}
}
"/user/keys/query":
post:
summary: Download device identity keys.
description: |-
Returns the current devices and identity keys for the given users.
operationId: queryUserEncryptionKeys
security:
- signedRequest: []
parameters:
- in: body
name: body
type: object
required: true
schema:
type: object
properties:
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:
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: ../client-server/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.
required: ['device_keys']
examples:
application/json: {
"device_keys": {
"@alice:example.com": {
"JLAFKJWSCS": {
"user_id": "@alice:example.com",
"device_id": "JLAFKJWSCS",
"algorithms": [
"m.olm.v1.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"
}
}
}
}
}

@ -0,0 +1,7 @@
r0.1.0
======
This is the first release of the Application Service specification. It
includes support for application services being able to interact with
homeservers and bridge third party networks, such as IRC, over to Matrix
in a standard and accessible way.

@ -0,0 +1 @@
Document the ``validated_at`` and ``added_at`` fields on ``GET /acount/3pid``.

@ -0,0 +1 @@
Add ``POST /account/3pid/delete``

@ -0,0 +1 @@
Clarify the homeserver's behaviour for searching users.

@ -0,0 +1 @@
Clarify the event fields used in the ``/sync`` response.

@ -0,0 +1 @@
Add an ``inhibit_login`` registration option.

@ -0,0 +1 @@
Recommend that servers set a Content Security Policy for the content repository.

@ -0,0 +1 @@
Add the other keys that redactions are expected to preserve.

@ -0,0 +1 @@
Clarify that clients should not be generating invalid HTML for formatted events.

@ -0,0 +1 @@
Clarify the room tag structure (thanks @KitsuneRal!)

@ -0,0 +1 @@
Add a note that clients may use the transaction ID to avoid flickering when doing local echo.

@ -3,7 +3,7 @@
"type": "m.tag",
"content": {
"tags": {
"u.work": {"order": 1}
"u.work": {"order": 0.9}
}
}
}

@ -10,5 +10,6 @@ properties:
type: string
required:
- type
- content
title: Event
type: object

@ -1,46 +1,14 @@
allOf:
- $ref: event.yaml
- $ref: sync_room_event.yaml
description: In addition to the Event fields, Room Events have the following additional
fields.
properties:
event_id:
description: The globally unique event identifier.
type: string
room_id:
description: The ID of the room associated with this event.
type: string
sender:
description: Contains the fully-qualified ID of the user who *sent*
this event.
description: |-
The ID of the room associated with this event. Will not be present on events
that arrive through ``/sync``, despite being required everywhere else.
type: string
origin_server_ts:
description: Timestamp in milliseconds on originating homeserver
when this event was sent.
type: integer
format: int64
unsigned:
description: Contains optional extra information about the event.
properties:
age:
description: The time in milliseconds that has elapsed since the event was
sent. This field is generated by the local homeserver, and may be incorrect
if the local time on at least one of the two servers is out of sync, which can
cause the age to either be negative or greater than it actually is.
type: integer
redacted_because:
description: Optional. The event that redacted this event, if any.
title: Event
type: object
transaction_id:
description: The client-supplied transaction ID, if the client being given
the event is the same one which sent it.
type: string
title: UnsignedData
type: object
required:
- event_id
- room_id
- sender
- origin_server_ts
title: Room Event
type: object

@ -1,23 +1,7 @@
allOf:
- $ref: room_event.yaml
- $ref: sync_state_event.yaml
description: In addition to the Room Event fields, State Events have the following
additional fields.
properties:
prev_content:
description: Optional. The previous ``content`` for this event. If there is no
previous content, this key will be missing.
title: EventContent
type: object
state_key:
description: A unique key which defines the overwriting semantics for this piece
of room state. This value is often a zero-length string. The presence of this
key makes this event a State Event.
State keys starting with an ``@`` are reserved for referencing user IDs, such
as room members. With the exception of a few events, state events set with a
given user's ID as the state key MUST only be set by that user.
type: string
required:
- state_key
title: State Event
type: object

@ -0,0 +1,60 @@
# Copyright 2018 New Vector 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.
# Note: this is technically not a core event schema, however it is included here
# to keep things sane. The short story is that /sync doesn't require a room_id to
# be on events, so we give it a whole event structure as a base for room_event.
# This base doesn't declare a room_id, which instead appears in the room_event.
allOf:
- $ref: event.yaml
description: In addition to the Event fields, Room Events have the following additional
fields.
properties:
event_id:
description: The globally unique event identifier.
type: string
sender:
description: Contains the fully-qualified ID of the user who sent this event.
type: string
origin_server_ts:
description: Timestamp in milliseconds on originating homeserver
when this event was sent.
type: integer
format: int64
unsigned:
description: Contains optional extra information about the event.
properties:
age:
description: The time in milliseconds that has elapsed since the event was
sent. This field is generated by the local homeserver, and may be incorrect
if the local time on at least one of the two servers is out of sync, which can
cause the age to either be negative or greater than it actually is.
type: integer
redacted_because:
description: Optional. The event that redacted this event, if any.
title: Event
type: object
transaction_id:
description: The client-supplied transaction ID, if the client being given
the event is the same one which sent it.
type: string
title: UnsignedData
type: object
required:
- event_id
- sender
- origin_server_ts
title: Room Event
type: object

@ -0,0 +1,39 @@
# Copyright 2018 New Vector 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.
# See sync_room_event.yaml for why this file is here.
allOf:
- $ref: sync_room_event.yaml
description: In addition to the Room Event fields, State Events have the following
additional fields.
properties:
prev_content:
description: Optional. The previous ``content`` for this event. If there is no
previous content, this key will be missing.
title: EventContent
type: object
state_key:
description: A unique key which defines the overwriting semantics for this piece
of room state. This value is often a zero-length string. The presence of this
key makes this event a State Event.
State keys starting with an ``@`` are reserved for referencing user IDs, such
as room members. With the exception of a few events, state events set with a
given user's ID as the state key MUST only be set by that user.
type: string
required:
- state_key
title: State Event
type: object

@ -18,7 +18,15 @@
"description": "The tags on the room and their contents.",
"additionalProperties": {
"title": "Tag",
"type": "object"
"type": "object",
"properties": {
"order": {
"type": "number",
"format": "float",
"description":
"A number in a range ``[0,1]`` describing a relative position of the room under the given tag."
}
}
}
}
}

@ -0,0 +1,526 @@
# State Resolution: Reloaded
Thoughts on the next iteration of the state resolution algorithm that aims to
mitigate currently known attacks
# Background
The state of a room at an event is a mapping from key to event, which is built
up and updated by sending state events into the room. All the information about
the room is encoded in the state, from metadata like the name and topic to
membership of the room to security policies like bans and join rules.
It is therefore important that─wherever possible─the view of the state of the
room is consistent across all servers. If different servers have different
views of the state then it can lead to the room bifurcating, due to differing
ideas on who is in the room, who is allowed to talk, etc.
The difficulty comes when the room DAG forks and then merges again (which can
happen naturally if two servers send events at the same time or when a network
partition is resolved). The state after the merge has to be resolved from the
state of the two branches: the algorithm to resolve this is called the _state
resolution algorithm_.
Since the result of state resolution must be consistent across servers, the
information that the algorithm can use is strictly limited to the information
that will always be available to all servers (including future servers that may
not even be in the room at that point) at any point in time where the
resolution needs to be calculated. In particular, this has the consequence that
the algorithm cannot use information from the room DAG, since servers are not
required to store events for any length of time.
**As such, the state resolution algorithm is effectively a pure function from
sets of state to a single resolved set of state.**
The final important property for state resolution is that it should not allow
malicious servers to avoid moderation action by forking and merging the room
DAG. For example, if a server gets banned and then forks the room before the
ban, any merge back should always ensure that the ban is still in the state.
# Current Algorithm
The current state resolution is known to have some undesirable properties,
which can be summarized into two separate cases:
1. Moderation evasion ─ where an attacker can avoid e.g. bans by forking and
joining the room DAG in particular ways.
1. State resets ─ where a server (often innocently) sends an event that points
to disparate parts of the graph, causing state resolution to pick old state
rather than later versions.
These have the following causes:
1. Conflicting state must pass auth checks to be eligible to be picked, but the
algorithm does not consider previous (superseded) state changes in a fork.
For example, where Alice gives Bob power and then Bob gives Charlie power on
one branch of a conflict, when the latter power level event is authed
against the original power level (where Bob didn't have power), it fails.
1. The algorithm relies on the deprecated and untrustable depth parameter to
try and ensure that the "most recent" state is picked. Without having a copy
of the complete room DAG the algorithm doesn't know that e.g. one topic
event came strictly after another in the DAG. For efficiency and storage
reasons servers are not required (or expected) to store the whole room DAG.
1. The algorithm always accepts events where there are no conflicting
alternatives in other forks. This means that if an admin changed the join
rules to `private`, then new joins on forks based on parts of the DAG which
predate that change would always be accepted without being authed against
the join_rules event.
# Desirable Properties
As well as the important properties listed in the "Background" section, there
are also some other properties that would significantly improve the experience
of end users, though not strictly essential. These include:
* Banning and changing power levels should "do the right thing", i.e. end
users shouldn't have to take extra steps to make the state resolution
produce the "right" results.
* Minimise occurences of "state resets". Servers will sometimes point to
disparate parts of the room DAG (due to a variety of reasons), which ideally
should not result in changes in the state.
* Be efficient; state resolution can happen a lot on some large rooms. Ideally
it would also support efficiently working on "state deltas" - i.e. the
ability to calculate state resolution incrementally from snapshots rather
than having to consider the full state of each fork each time a conflict is
resolved
# Ideas for New Algorithm
## Auth Chain
The _auth events_ of a given event is the set of events which justify why a
given event is allowed to be sent into a room (e.g. an m.room.create, an
m.room.power_levels and the sender's m.room.membership). The _auth chain_ of an
event is its auth events and their auth events, recursively. The auth chains of
a set of events in a given room form a DAG.
"Auth events" are events that can appear as auth events of an event. These
include power levels, membership etc.[^1]
Servers in a room are required to have the full auth chain for all events that
they have seen, and so the auth chain is available to be used by state
resolution algorithms.
## Unconflicted State
The current algorithm defines the notion of "unconflicted state" to be all
entries that for each set of state either has the same event or no entry. All
unconflicted state entries are included in the resolved state. This is
problematic due to the fact that any new entries introduced on forks always
appear in the resolved state, regardless of if they would pass the checks
applied to conflicted state.
The new algorithm could redefine "unconflicted state" to be all entries which
both exist and are the same in every state set (as opposed to previously where
the entry didn't need to exist in every state set).
## Replacing Depth
Since depth of an event cannot be reliably calculated without possessing the
full DAG, and cannot be trusted when provided by other servers, it can not be
used in future versions of state resolution. A potential alternative, however,
is to use "origin_server_ts". While it cannot be relied on to be accurate─an
attacker can set it to arbitrary values─it has the advantage over depth that
end users can clearly see when a server is using incorrect values. (Note that
server clocks don't need to be particularly accurate for the ordering to still
be more useful than other arbitrary orderings).
It can also be assumed that in most cases the origin_server_ts for a given
benign server will be mostly consistent. For example, if a server sends a join
and then a leave in the vast majority of cases the leave would have a greater
origin_server_ts.
This makes "origin_server_ts" a good candidate to be used as a last resort to
order events if necessary, where otherwise a different arbitrary ordering would
be used. However, it's important that there is some other mechanism to ensure
that malicious servers can't abuse origin_server_ts to ensure their state
always gets picked during resolution (In the proposal below we use the auth DAG
ordering to override users who set state with malicious origin_server_ts.)
## Ordering and Authing
Roughly, the current algorithm tries to ensure that moderation evasion doesn't
happen by ordering conflicted events by depth and (re)authing them
sequentially. The exact implementation has several issues, but the idea of
ensuring that state events from forks still need to pass auth subject to e.g.
bans and power level changes is a powerful one, as it reduces the utility of
maliciously forking.
For that to work we need to ensure that there is a suitable ordering that puts
e.g. bans before events sent in other forks. (However events can point to old
parts of the DAG, for a variety of reasons, and ideally in that case the
resolved state would closely match the recent state).
Similarly care should be taken when multiple changes to e.g. power levels happen
in a fork. If Alice gives Bob power (A), then Bob gives Charlie power (B) and
then Charlie, say, changes the ban level (C). If you try and resolve two state
sets one of which has A and the other has C, C will not pass auth unless B is
also taken into account. This case can be handled if we also consider the
difference in auth chains between the two sets, which in the previous example
would include B.
(This is also the root cause of the "Hotel California" issue, where left users
get spontaneously rejoined to rooms. This happens when a user has a sequence of
memberships changes of the form: leave (A), join (B) and then another leave (C).
In the current algorithm a resoluton of A and C would pick A, and a resolution
of A and B would then pick B, i.e. the join. This means that a suitably forked
graph can reset the state to B. This is fixed if when resolving A and C we also
consider B, since its in the auth chain of C.)
## Power Level Ordering
Actions that malicious servers would try and evade are actions that require
greater power levels to perform, for example banning, reducing power level,
etc. We define "power events" as events that have the potential to remove the
ability of another user to do something.[^2] (Note that they are a subset of
auth events.)
In all these cases it is desirable for those privileged actions to take
precedence over events in other forks. This can be achieved by first
considering "power events", and requiring the remaining events to pass auth
based on them.
## Mainline
An issue caused by servers not storing the full room DAG is that one can't tell
how two arbitrary events are ordered. The auth chain gives a partial ordering
to certain events, though far from complete; however, all events do contain a
reference to the current power levels in their auth events. As such if two
state events reference two different power levels events, and one power levels'
auth chain references the other, then there is a strong likelihood that the
event referencing the latter power level came after the other event.
A "mainline" is a list of power levels events created if you pick a particular
power levels event (usually the current resolved power level) and recursively
follow each power level referenced in auth_events back to the first power level
event.
The mainline can then be used to induce an ordering on events by looking at
where the power level referenced in their auth_events is in the mainline (or
recursively following the chain of power level events back until one is found
that appears in the mainline). This effectively partitions the room into
epochs, where a new epoch is started whenever a new power level is sent.
If this mainline ordering is combined with ordering by origin_server_ts, then
it gives an ordering that is correct for servers that don't lie about the time,
while giving a mechanism that can be used to deal if a server lied (by room
admins starting a new epoch).
The natural course of action for a room admin to take when noticing a
user/server is misbehaving is to ban them from the room, rather than changing
the power levels. It would therefore be useful if banning a user or server
started a new epoch as well. This would require being able to create a mainline
that includes power level events and bans[^3], which would suggest that power
level and ban events would need to point to the latest ban event as well. (This
would be significantly easier if we maintained a list of bans in a single
event, however there is concern that would limit the number of possible bans in
a room.)
# Proposed Algorithm
First we define:
* **"State sets"** are the sets of state that the resolution algorithm tries
to resolve, i.e. the inputs to the algorithm.
* **"Power events"** are events that have the potential to remove the ability
of another user to do something. These are power levels, join rules, bans
and kicks.
* The **"unconflicted state map"** is the state where the value of each key
exists and is the same in every state set. The **"conflicted state map"** is
everything else. (Note that this is subtly different to the definition used
in the existing algorithm, which considered the merge of a present event
with an absent event to be unconflicted rather than conflicted)
* The "**auth difference"** is calculated by first calculating the full auth
chain for each state set and taking every event that doesn't appear in every
auth chain.
* The **"full conflicted set"** is the union of the conflicted state map and
auth difference.
* The **"reverse topological power ordering"**[^4] of a set of events is an
ordering of the given events, plus any events in their auth chains that
appear in the auth difference, topologically ordered by their auth chains
with ties broken such that x < y if:
1. x's sender has a greater power level than y (calculated by looking at
their respective auth events, or if
2. x's origin_server_ts is less than y's, or if
3. x's event_id is lexicographically less than y's
This is also known as a lexicographical topological sort (i.e. this is the
unique topological ordering such that for an entry x all entries after it
must either have x in their auth chain or be greater than x as defined
above). This can be implemented using Kahn's algorithm.
* The **"mainline ordering"** based on a power level event P of a set of
events is calculated as follows:
1. Generate the list of power levels starting at P and recursively take the
power level from its auth events. This list is called the mainline,
ordered such that P is last.
1. We say the "closest mainline event" of an event is the first power level
event encountered in mainline when iteratively descending through the
power level events in the auth events.
1. Order the set of events such that x < y if:
1. The closest mainline event of x appears strictly before the closest
of y in the mainline list, or if
1. x's origin_server_ts is less than y's, or if
1. x's event_id lexicographically sorts before y's
* The **"iterative auth checks"** algorithm is where given a sorted list of
events, the auth check algorithm is applied to each event in turn. The state
events used to auth are built up from previous events that passed the auth
checks, starting from a base set of state. If a required auth key doesn't
exist in the state, then the one in the event's auth_events is used. (See
_Variations_ and _Attack Vectors_ below).
The algorithm proceeds as follows:
1. Take all power events and any events in their auth chains that appear in the
_full_ _conflicted set_ and order them by the _reverse topological power
ordering._
1. Apply the _iterative auth checks_ algorithm based on the unconflicted state
map to get a partial set of resolved state.
1. Take all remaining events that weren't picked in step 1 and order them by
the _mainline ordering_ based on the power level in the partially resolved
state.
1. Apply the _iterative auth checks algorithm_ based on the partial resolved
state.
1. Update the result with the _unconflicted state_ to get the final resolved
state[^5]. (_Note_: this is different from the current algorithm, which
considered different event types at distinct stages)
An example python implementation can be found on github
[here](https://github.com/matrix-org/matrix-test-state-resolution-ideas).
Note that this works best if we also change which events to include as an
event's auth_events. See the "Auth Events" section below.
## Discussion
Essentially, the algorithm works by producing a sorted list of all conflicted
events (and differences in auth chains), and applies the auth checks one by
one, building up the state as it goes. The list is produced in two parts: first
the power events and auth dependencies are ordered by power level of the
senders and resolved, then the remaining events are ordered using the
"mainline" of the resolved power levels and then resolved to produce the final
resolved state.
(This is equivalent to linearizing the full conflicted set of events and
reapplying the usual state updates and auth rules.)
### Variations
There are multiple options for what to use as the base state for _iterative
auth checks_ algorithm; while it needs to be some variation of auth events and
unconflicted events, it is unclear exactly which combination is best (and least
manipulatable by malicious servers).
Care has to be taken if we want to ensure that old auth events that appear in
the _auth chain difference_ can't supercede unconflicted state entries.
Due to auth chain differences being added to the resolved states during
_iterative auth checks_, we therefore need to re-apply the unconflicted state
at the end to ensure that they appear in the final resolved state. This feels
like an odd fudge that shouldn't be necessary, and may point to a flaw in the
proposed algorithm.
### State Resets
The proposed algorithm still has some potentially unexpected behaviour.
One example of this is when Alice sets a topic and then gets banned. If an event
gets created (potentially much later) that points to both before and after the
topic and ban then the proposed algorithm will resolve and apply the ban before
resolving the topic, causing the topic to be denied and dropped from the
resolved state. This will result in no topic being set in the resolved state.
### Auth Events
The algorithm relies heavily on the ordering induced by the auth chain DAG.
There are two types of auth events (not necessarily distinct):
* Those that give authorization to do something
* Those that revoke authorization to do something.
For example, invites/joins are in the former category, leaves/kicks/bans are in
the latter and power levels are both.
Assuming[^6] revocations always point to (i.e., have in their auth chain) the
authorization event that they are revoking, and authorization events point to
revocations that they are superseding, then the algorithm will ensure that the
authorization events are applied in order (so generally the "latest"
authorization state would win).
This helps ensure that e.g. an invite cannot be reused after a leave/kick,
since the leave (revocation) would have the invite in their auth chain.
This idea also relies on revocations replacing the state that granted
authorization to do an action (and vice versa). For example, in the current
model bans (basically) revoke the ability for a particular user from being able
to join. If the user later gets unbanned and then rejoins, the join would point
to the join rules as the authorization that lets them join, but would not
(necessarily) point to the unban. This has the effect that if a state resolution
happened between the new join and the ban, the unban would not be included in
the resolution and so the join would be rejected.
The changes to the current model that would be required to make the above
assumptions true would be, for example:
1. By default permissions are closed.
1. Bans would need to be a list in either the join rules event or a separate
event type which all membership events pointed to.
1. Bans would only revoke the ability to join, not automatically remove users
from the room.
1. Change the defaults of join_rules to be closed by default
### Efficiency and Delta State Resolution
The current (unoptimised) implementation of the algorithm is 10x slower than
the current algorithm, based on a single, large test case. While hopefully some
optimisations can be made, the ability to [incrementally calculate state
resolution via deltas](https://github.com/matrix-org/synapse/pull/3122) will
also mitigate some of the slow down.
Another aspect that should be considered is the amount of data that is required
to perform the resolution. The current algorithm only requires the events for
the conflicted set, plus the events from the unconflicted set needed to auth
them. The proposed algorithm also requires the events in the auth chain
difference (calculating the auth chain difference may also require more data to
calculate).
Delta state resolution is where if you have, say, two state sets and their
resolution, then you can use that result to work out the new resolution where
there has been a small change to the state sets. For the proposed algorithm, if
the following properties hold true then the result can be found by simply
applying steps 3 and 4 to the state deltas. The properties are:
1. The delta contains no power events
1. The origin_server_ts of all events in state delta are strictly greater than
those in the previous state sets
1. Any event that has been removed must not have been used to auth subsequent
events (e.g. if we replaced a member event and that user had also set a
topic)
These properties will likely hold true for most state updates that happen in a
room, allowing servers to use this more efficient algorithm the majority of the
time.
### Full DAG
It's worth noting that if the algorithm had access to the full room DAG that it
would really only help by ensuring that the ordering in "reverse topological
ordering" and "mainline ordering" respected the ordering induced by the DAG.
This would help, e.g., ensure the latest topic was always picked rather than
rely on origin_server_ts and mainline. As well as obviate the need to maintain
a separate auth chain, and the difficulties that entails (like having to
reapply the unconflicted state at the end).
### Attack Vectors
The main potential attack vector that needs to be considered is in the
_iterative auth checks_ algorithm, and whether an attacker could make use of
the fact that it's based on the unconflicted state and/or auth events of the
event.
# Appendix
The following is an example room DAG, where time flows down the page. We shall
work through resolving the state at both _Message 2_ and _Message 3_.
![alt_text](images/state-res.png)
(Note that green circles are events sent by Alice, blue circles sent by Bob and
black arrows point to previous events. The red arrows are the mainline computed
during resolution.)
First we resolve the state at _Message 2_. The conflicted event types are the
power levels and topics, and since the auth chains are the same for both state
sets the auth difference is the empty set.
Step 1: The _full conflicted set_ are the events _P2, P3, Topic 2 _and _Topic
3_, of which _P2_ and _P3_ are the only power events. Since Alice (the room
creator) has a greater power level than Bob (and neither _P2 _and _P3_ appear
in each other's auth chain), the reverse topological ordering is: [_P2, P3_].
Step 2: Now we apply the auth rules iteratively, _P2_ trivially passes based on
the unconflicted state, but _P3_ does not pass since after _P2_ Bob no longer
has sufficient power to set state. This results in the power levels resolving
to _P2_.
Step 3: Now we work out the mainline based on P2, which is coloured in red on
the diagram. We use the mainline to order _Topic 2_ and _Topic 3_. _Topic 2_
points to_ P1_, while the closest mainline to _Topic 3_ is also _P1_. We then
order based on the _origin_server_ts_ of the two events, let's assume that
gives us: [_Topic 2_, _Topic 3_].
Step 4: Iteratively applying the auth rules results in _Topic 2_ being allowed,
but _Topic 3 _being denied (since Bob doesn't have power to set state anymore),
so the topic is resolved to _Topic 2_.
This gives the resolved state at _Message 2_ to be _P2 _and _Topic 2_. (This is
actually the same result as the existing algorithm gives)
Now let's look at the state at _Message 3_.
Step 1: The full conflicted set are simple: _Topic 2_ and _Topic 4_. There are
no conflicted power events.
Step 2: N/A
Step 3: _Topic 2_ points to _P1_ in the mainline, and _Topic 4_ points to _P2_
in its auth events. Since _P2_ comes after _P1_ in the mainline, this gives an
ordering of [_Topic 2, Topic 4_].
Step 4: Iteratively applying the auth rules results in both topics passing the
auth checks, and so the last topic, _Topic 4_, is chosen.
This gives the resolved state at _Message 3_ to be _Topic 4_.
## Notes
[^1]: In the current room protocol these are: the create event, power levels,
membership, join rules and third party invites. See the
[spec](https://matrix.org/docs/spec/server_server/unstable.html#pdu-fields).
[^2]: In the current protocol these are: power levels, kicks, bans and join
rules.
[^3]: Future room versions may have a concept of server ban event that works
like existing bans, which would also be included
[^4]: The topology being considered here is the auth chain DAG, rather than the
room DAG, so this ordering is only applicable to events which appear in the
auth chain DAG.
[^5]: We do this so that, if we receive events with misleading auth_events, this
ensures that the unconflicted state at least is correct.
[^6]: This isn't true in the current protocol

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

@ -207,6 +207,13 @@ class MatrixSections(Sections):
apis = self.units.get("apis")
return template.render(apis=apis)
def render_unstable_warnings(self):
rendered = {}
blocks = self.units.get("unstable_warnings")
for var, text in blocks.items():
rendered["unstable_warning_block_" + var] = text
return rendered
def render_swagger_definition(self):
rendered = {}
template = self.env.get_template("schema-definition.tmpl")

@ -971,6 +971,22 @@ class MatrixUnits(Units):
return changelogs
def load_unstable_warnings(self, substitutions):
warning = """
.. WARNING::
You are viewing an unstable version of this specification. Unstable
specifications may change at any time without notice. To view the
current specification, please `click here <latest.html>`_.
"""
warnings = {}
for var in substitutions.keys():
key = var[1:-1] # take off the surrounding %-signs
if substitutions.get(var, "unstable") == "unstable":
warnings[key] = warning
else:
warnings[key] = ""
return warnings
def load_spec_targets(self):
with open(TARGETS, "r") as f:

@ -16,6 +16,8 @@
Application Service API
=======================
{{unstable_warning_block_APPSERVICE_RELEASE_LABEL}}
The Matrix client-server API and server-server APIs provide the means to
implement a consistent self-contained federated messaging fabric. However, they
provide limited means of implementing custom server-side behaviour in Matrix

@ -15,6 +15,8 @@
Client-Server API
=================
{{unstable_warning_block_CLIENT_RELEASE_LABEL}}
The client-server API provides a simple lightweight API to let clients send
messages, control rooms and synchronise conversation history. It is designed to
support both lightweight clients which store no state and lazy-load data from
@ -1328,16 +1330,30 @@ the following list:
- ``state_key``
- ``prev_content``
- ``content``
- ``hashes``
- ``signatures``
- ``depth``
- ``prev_events``
- ``prev_state``
- ``auth_events``
- ``origin``
- ``origin_server_ts``
- ``membership``
.. Note:
Some of the keys, such as ``hashes``, will appear on the federation-formatted
event and therefore the client may not be aware of them.
The content object should also be stripped of all keys, unless it is one of
one of the following event types:
- ``m.room.member`` allows key ``membership``
- ``m.room.create`` allows key ``creator``
- ``m.room.join_rules`` allows key ``join_rule``
- ``m.room.member`` allows key ``membership``.
- ``m.room.create`` allows key ``creator``.
- ``m.room.join_rules`` allows key ``join_rule``.
- ``m.room.power_levels`` allows keys ``ban``, ``events``, ``events_default``,
``kick``, ``redact``, ``state_default``, ``users``, ``users_default``.
- ``m.room.aliases`` allows key ``aliases``
- ``m.room.aliases`` allows key ``aliases``.
- ``m.room.history_visibility`` allows key ``history_visibility``.
The server should add the event causing the redaction to the ``unsigned``
property of the redacted event, under the ``redacted_because`` key. When a

@ -18,6 +18,8 @@
Identity Service API
====================
{{unstable_warning_block_IDENTITY_RELEASE_LABEL}}
The Matrix client-server and server-server APIs are largely expressed in Matrix
user identifiers. From time to time, it is useful to refer to users by other
("third-party") identifiers, or "3pid"s, e.g. their email address or phone
@ -179,9 +181,11 @@ An identity service has some long-term public-private keypairs. These are named
in a scheme ``algorithm:identifier``, e.g. ``ed25519:0``. When signing an
association, the standard `Signing JSON`_ algorithm applies.
In the event of key compromise, the identity service may revoke any of its keys.
An HTTP API is offered to get public keys, and check whether a particular key is
valid.
.. TODO: Actually allow identity services to revoke all keys
See: https://github.com/matrix-org/matrix-doc/issues/1633
.. In the event of key compromise, the identity service may revoke any of its keys.
An HTTP API is offered to get public keys, and check whether a particular key is
valid.
The identity service may also keep track of some short-term public-private
keypairs, which may have different usage and lifetime characteristics than the

@ -33,6 +33,10 @@ recipient's local homeserver, which must first transfer the content from the
origin homeserver using the same API (unless the origin and destination
homeservers are the same).
When serving content, the server SHOULD provide a ``Content-Security-Policy``
header. The recommended policy is ``default-src 'none'; script-src 'none';
plugin-types application/pdf; style-src 'unsafe-inline'; object-src 'self';``.
Client behaviour
----------------

@ -106,6 +106,12 @@ of tags they can render, falling back to other representations of the tags where
For example, a client may not be able to render tables correctly and instead could fall
back to rendering tab-delimited text.
In addition to not rendering unsafe HTML, clients should not emit unsafe HTML in events.
Likewise, clients should not generate HTML that is not needed, such as extra paragraph tags
surrounding text due to Rich Text Editors. HTML included in events should otherwise be valid,
such as having appropriate closing tags, appropriate attributes (considering the custom ones
defined in this specification), and generally valid structure.
.. Note::
A future iteration of the specification will support more powerful and extensible
message formatting options, such as the proposal `MSC1225 <https://github.com/matrix-org/matrix-doc/issues/1225>`_.
@ -167,9 +173,14 @@ message which they receive from the event stream. The echo of the same message
from the event stream is referred to as "remote echo". Both echoes need to be
identified as the same message in order to prevent duplicate messages being
displayed. Ideally this pairing would occur transparently to the user: the UI
would not flicker as it transitions from local to remote. Flickering cannot be
fully avoided in the current client-server API. Two scenarios need to be
considered:
would not flicker as it transitions from local to remote. Flickering can be
reduced through clients making use of the transaction ID they used to send
a particular event. The transaction ID used will be included in the event's
``unsigned`` data as ``transaction_id`` when it arrives through the event stream.
Clients unable to make use of the transaction ID are more likely to experience
flickering due to the following two scenarios, however the effect can be mitigated
to a degree:
- The client sends a message and the remote echo arrives on the event stream
*after* the request to send the message completes.

@ -39,7 +39,7 @@ with an ``order`` of ``0.2`` would be displayed before a room with an ``order``
of ``0.7``. If a room has a tag without an ``order`` key then it should appear
after the rooms with that tag that have an ``order`` key.
The name of a tag MUST not exceed 255 bytes.
The name of a tag MUST NOT exceed 255 bytes.
The tag namespace is defined as follows:

@ -40,6 +40,7 @@ with ``content.membership`` = ``invite``, as well as a
``content.third_party_invite`` property which contains proof that the invitee
does indeed own that third party identifier.
Events
------
@ -55,41 +56,79 @@ A client asks a server to invite a user by their third party identifier.
Server behaviour
----------------
All homeservers MUST verify the signature in the event's
``content.third_party_invite.signed`` object.
When a homeserver inserts an ``m.room.member`` ``invite`` event into the graph
because of an ``m.room.third_party_invite`` event,
that homesever MUST validate that the public
key used for signing is still valid, by checking ``key_validity_url`` from the ``m.room.third_party_invite``. It does
this by making an HTTP GET request to ``key_validity_url``:
.. TODO: Link to identity server spec when it exists
Upon receipt of an ``/invite``, the server is expected to look up the third party
identifier with the provided identity server. If the lookup yields a result for
a Matrix User ID then the normal invite process can be initiated. This process
ends up looking like this:
::
+---------+ +-------------+ +-----------------+
| Client | | Homeserver | | IdentityServer |
+---------+ +-------------+ +-----------------+
| | |
| POST /invite | |
|------------------------------------>| |
| | |
| | GET /lookup |
| |--------------------------------------------------->|
| | |
| | User ID result |
| |<---------------------------------------------------|
| | |
| | Invite process for the discovered User ID |
| |------------------------------------------ |
| | | |
| |<----------------------------------------- |
| | |
| Complete the /invite request | |
|<------------------------------------| |
| | |
However, if the lookup does not yield a bound User ID, the homeserver must store
the invite on the identity server and emit a valid ``m.room.third_party_invite``
event to the room. This process ends up looking like this:
::
+---------+ +-------------+ +-----------------+
| Client | | Homeserver | | IdentityServer |
+---------+ +-------------+ +-----------------+
| | |
| POST /invite | |
|------------------------------------>| |
| | |
| | GET /lookup |
| |-------------------------------------------------------------->|
| | |
| | "no users" result |
| |<--------------------------------------------------------------|
| | |
| | POST /store-invite |
| |-------------------------------------------------------------->|
| | |
| | Information needed for the m.room.third_party_invite |
| |<--------------------------------------------------------------|
| | |
| | Emit m.room.third_party_invite to the room |
| |------------------------------------------- |
| | | |
| |<------------------------------------------ |
| | |
| Complete the /invite request | |
|<------------------------------------| |
| | |
Schema::
=> GET $key_validity_url?public_key=$public_key
<= HTTP/1.1 200 OK
{
"valid": true|false
}
Example::
key_validity_url = https://identity.server/is_valid
public_key = ALJWLAFQfqffQHFqFfeqFUOEHf4AIHfefh4
=> GET https://identity.server/is_valid?public_key=ALJWLAFQfqffQHFqFfeqFUOEHf4AIHfefh4
<= HTTP/1.1 200 OK
{
"valid": true
}
All homeservers MUST verify the signature in the event's
``content.third_party_invite.signed`` object.
with the querystring
?public_key=``public_key``. A JSON object will be returned.
The invitation is valid if the object contains a key named ``valid`` which is
``true``. Otherwise, the invitation MUST be rejected. This request is
idempotent and may be retried by the homeserver.
The third party user will then need to verify their identity, which results in
a call from the Identity Server to the homeserver that bound the third party
identifier to a user. The homeserver then exchanges the ``m.room.third_party_invite``
event in the room for a complete ``m.room.member`` event for ``membership: invite``
for the user that has bound the third party identifier.
If a homeserver is joining a room for the first time because of an
``m.room.third_party_invite``, the server which is already participating in the
@ -99,29 +138,84 @@ validate that the public key used for signing is still valid, by checking
No other homeservers may reject the joining of the room on the basis of
``key_validity_url``, this is so that all homeservers have a consistent view of
the room. They may, however, indicate to their clients that a member's'
the room. They may, however, indicate to their clients that a member's
membership is questionable.
For example:
#. Room R has two participating homeservers, H1, H2
#. User A on H1 invites a third party identifier to room R
#. H1 asks the identity server for a binding to a Matrix user ID, and has none,
so issues an ``m.room.third_party_invite`` event to the room.
#. When the third party user validates their identity, their homeserver H3
is notified and attempts to issue an ``m.room.member`` event to participate
in the room.
#. H3 validates the signature given to it by the identity server.
#. H3 then asks H1 to join it to the room. H1 *must* validate the ``signed``
property *and* check ``key_validity_url``.
#. Having validated these things, H1 writes the invite event to the room, and H3
begins participating in the room. H2 *must* accept this event.
For example, given H1, H2, and H3 as homeservers, UserA as a user of H1, and an
identity server IS, the full sequence for a third party invite would look like
the following. This diagram assumes H1 and H2 are residents of the room while
H3 is attempting to join.
::
+-------+ +-----------------+ +-----+ +-----+ +-----+ +-----+
| UserA | | ThirdPartyUser | | H1 | | H2 | | H3 | | IS |
+-------+ +-----------------+ +-----+ +-----+ +-----+ +-----+
| | | | | |
| POST /invite for ThirdPartyUser | | | |
|----------------------------------->| | | |
| | | | | |
| | | GET /lookup | | |
| | |---------------------------------------------------------------------------------------------->|
| | | | | |
| | | | Lookup results (empty object) |
| | |<----------------------------------------------------------------------------------------------|
| | | | | |
| | | POST /store-invite | | |
| | |---------------------------------------------------------------------------------------------->|
| | | | | |
| | | | Token, keys, etc for third party invite |
| | |<----------------------------------------------------------------------------------------------|
| | | | | |
| | | (Federation) Emit m.room.third_party_invite | | |
| | |----------------------------------------------->| | |
| | | | | |
| Complete /invite request | | | |
|<-----------------------------------| | | |
| | | | | |
| | Verify identity | | | |
| |-------------------------------------------------------------------------------------------------------------------->|
| | | | | |
| | | | | POST /3pid/onbind |
| | | | |<---------------------------|
| | | | | |
| | | PUT /exchange_third_party_invite/:roomId | |
| | |<-----------------------------------------------------------------| |
| | | | | |
| | | Verify the request | | |
| | |------------------- | | |
| | | | | | |
| | |<------------------ | | |
| | | | | |
| | | (Federation) Emit m.room.member for invite | | |
| | |----------------------------------------------->| | |
| | | | | |
| | | | | |
| | | (Federation) Emit the m.room.member event sent to H2 | |
| | |----------------------------------------------------------------->| |
| | | | | |
| | | Complete /exchange_third_party_invite/:roomId request | |
| | |----------------------------------------------------------------->| |
| | | | | |
| | | | | Participate in the room |
| | | | |------------------------ |
| | | | | | |
| | | | |<----------------------- |
| | | | | |
Note that when H1 sends the ``m.room.member`` event to H2 and H3 it does not
have to block on either server's receipt of the event. Likewise, H1 may complete
the ``/exchange_third_party_invite/:roomId`` request at the same time as sending
the ``m.room.member`` event to H2 and H3. Additionally, H3 may complete the
``/3pid/onbind`` request it got from IS at any time - the completion is not shown
in the diagram.
H1 MUST verify the request from H3 to ensure the ``signed`` property is correct
as well as the ``key_validity_url`` as still being valid. This is done by making
a request to the `Identity Server /isvalid`_ endpoint, using the provided URL
rather than constructing a new one. The query string and response for the provided
URL must match the Identity Server specification.
The reason that no other homeserver may reject the event based on checking
``key_validity_url`` is that we must ensure event acceptance is deterministic.
@ -158,3 +252,6 @@ There is some risk of denial of service attacks by flooding homeservers or
identity servers with many requests, or much state to store. Defending against
these is left to the implementer's discretion.
.. _`Identity Server /isvalid`: ../identity_service/unstable.html#get-matrix-identity-api-v1-pubkey-isvalid

@ -16,6 +16,8 @@
Push Gateway API
================
{{unstable_warning_block_PUSH_GATEWAY_RELEASE_LABEL}}
Clients may want to receive push notifications when events are received at
the homeserver. This is managed by a distinct entity called the Push Gateway.

@ -126,7 +126,7 @@ Server implementation
{{version_ss_http_api}}
Retrieving Server Keys
Retrieving server keys
~~~~~~~~~~~~~~~~~~~~~~
.. NOTE::
@ -308,6 +308,8 @@ creating a new event in this room should populate the new event's
|
E4
.. _`auth events selection`:
The ``auth_events`` field of a PDU identifies the set of events which give the
sender permission to send the event. The ``auth_events`` for the
``m.room.create`` event in a room is empty; for other events, it should be the
@ -315,8 +317,16 @@ following subset of the room state:
- The ``m.room.create`` event.
- The current ``m.room.power_levels`` event, if any.
- The current ``m.room.join_rules`` event, if any.
- The sender's current ``m.room.member`` event, if any.
- If type is ``m.room.member``:
- The target's current ``m.room.member`` event, if any.
- If ``membership`` is ``join`` or ``invite``, the current
``m.room.join_rules`` event, if any.
- If membership is ``invite`` and ``content`` contains a
``third_party_invite`` property, the current
``m.room.third_party_invite`` event with ``state_key`` matching
``content.third_party_invite.signed.token``, if any.
{{definition_ss_pdu}}
@ -358,117 +368,170 @@ be inserted. The types of state events that affect authorization are:
- ``m.room.member``
- ``m.room.join_rules``
- ``m.room.power_levels``
- ``m.room.third_party_invite``
The rules are as follows:
Servers should not create new events that reference unauthorized events.
However, any event that does reference an unauthorized event is not itself
automatically considered unauthorized.
1. If type is ``m.room.create``:
Unauthorized events that appear in the event graph do *not* have any effect on
the state of the room.
a. If it has any previous events, reject.
b. If the domain of the ``room_id`` does not match the domain of the
``sender``, reject.
c. If ``content.room_version`` is present and is not a recognised version,
reject.
d. If ``content`` has no ``creator`` field, reject.
e. Otherwise, allow.
.. Note:: This is in contrast to redacted events which can still affect the
state of the room. For example, a redacted ``join`` event will still
result in the user being considered joined.
#. Reject if event has ``auth_events`` that:
The rules are as follows:
a. have duplicate entries for a given ``type`` and ``state_key`` pair
#. have entries whose ``type`` and ``state_key`` don't match those
specified by the `auth events selection`_ algorithm described above.
#. If event does not have a ``m.room.create`` in its ``auth_events``, reject.
#. If type is ``m.room.aliases``:
a. If event has no ``state_key``, reject.
b. If sender's domain doesn't matches ``state_key``, reject.
c. Otherwise, allow.
#. If type is ``m.room.member``:
a. If no ``state_key`` key or ``membership`` key in ``content``, reject.
#. If ``membership`` is ``join``:
i. If the only previous event is an ``m.room.create``
and the ``state_key`` is the creator, allow.
#. If the ``sender`` does not match ``state_key``, reject.
#. If the ``sender`` is banned, reject.
#. If the ``join_rule`` is ``invite`` then allow if membership state
is ``invite`` or ``join``.
#. If the ``join_rule`` is ``public``, allow.
#. Otherwise, reject.
#. If ``membership`` is ``invite``:
i. If ``content`` has ``third_party_invite`` key:
#. If *target user* is banned, reject.
#. If ``content.third_party_invite`` does not have a
``signed`` key, reject.
1. If type is ``m.room.create``, allow if and only if it has no
previous events - *i.e.* it is the first event in the room.
#. If ``signed`` does not have ``mxid`` and ``token`` keys, reject.
2. If type is ``m.room.member``:
#. If ``mxid`` does not match ``state_key``, reject.
a. If ``membership`` is ``join``:
#. If there is no ``m.room.third_party_invite`` event in the
current room state with ``state_key`` matching ``token``, reject.
i. If the only previous event is an ``m.room.create``
and the ``state_key`` is the creator, allow.
#. If ``sender`` does not match ``sender`` of the
``m.room.third_party_invite``, reject.
#. If the ``sender`` does not match ``state_key``, reject.
#. If any signature in ``signed`` matches any public key in the
``m.room.third_party_invite`` event, allow. The public keys are
in ``content`` of ``m.room.third_party_invite`` as:
#. If the user's current membership state is ``invite`` or ``join``,
allow.
#. A single public key in the ``public_key`` field.
#. A list of public keys in the ``public_keys`` field.
#. If the ``join_rule`` is ``public``, allow.
#. Otherwise, reject.
#. Otherwise, reject.
#. If the ``sender``'s current membership state is not ``join``, reject.
b. If ``membership`` is ``invite``:
#. If *target user*'s current membership state is ``join`` or ``ban``,
reject.
i. If the ``sender``'s current membership state is not ``join``, reject.
#. If the ``sender``'s power level is greater than or equal to the *invite
level*, allow.
#. If *target user*'s current membership state is ``join`` or ``ban``,
reject.
#. Otherwise, reject.
#. If the ``sender``'s power level is greater than or equal to the *invite
level*, allow.
#. If ``membership`` is ``leave``:
#. Otherwise, reject.
i. If the ``sender`` matches ``state_key``, allow if and only if that user's
current membership state is ``invite`` or ``join``.
c. If ``membership`` is ``leave``:
#. If the ``sender``'s current membership state is not ``join``, reject.
i. If the ``sender`` matches ``state_key``, allow if and only if that user's
current membership state is ``invite`` or ``join``.
#. If the *target user*'s current membership state is ``ban``, and the
``sender``'s power level is less than the *ban level*, reject.
#. If the ``sender``'s current membership state is not ``join``, reject.
#. If the ``sender``'s power level is greater than or equal to the *kick
level*, and the *target user*'s power level is less than the
``sender``'s power level, allow.
#. If the *target user*'s current membership state is ``ban``, and the
``sender``'s power level is less than the *ban level*, reject.
#. Otherwise, reject.
#. If the ``sender``'s power level is greater than or equal to the *kick
level*, and the *target user*'s power level is less than the
``sender``'s power level, allow.
#. If ``membership`` is ``ban``:
#. Otherwise, reject.
i. If the ``sender``'s current membership state is not ``join``, reject.
d. If ``membership`` is ``ban``:
#. If the ``sender``'s power level is greater than or equal to the *ban
level*, and the *target user*'s power level is less than the
``sender``'s power level, allow.
i. If the ``sender``'s current membership state is not ``join``, reject.
#. Otherwise, reject.
#. If the ``sender``'s power level is greater than or equal to the *ban
level*, and the *target user*'s power level is less than the
``sender``'s power level, allow.
#. Otherwise, the membership is unknown. Reject.
#. Otherwise, reject.
#. If the ``sender``'s current membership state is not ``join``, reject.
e. Otherwise, the membership is unknown. Reject.
#. If type is ``m.room.third_party_invite``:
3. If the ``sender``'s current membership state is not ``join``, reject.
a. Allow if and only if ``sender``'s current power level is greater than
or equal to the *invite level*.
4. If the event type's *required power level* is greater than the ``sender``'s power
#. If the event type's *required power level* is greater than the ``sender``'s power
level, reject.
5. If type is ``m.room.power_levels``:
#. If the event has a ``state_key`` that starts with an ``@`` and does not match
the ``sender``, reject.
a. If there is no previous ``m.room.power_levels`` event in the room, allow.
#. If type is ``m.room.power_levels``:
b. For each of the keys ``users_default``, ``events_default``,
``state_default``, ``ban``, ``redact``, ``kick``, ``invite``, as well as
each entry being changed under the ``events`` or ``users`` keys:
a. If ``users`` key in ``content`` is not a dictionary with keys that are
valid user IDs with values that are integers (or a string that is an
integer), reject.
i. If the current value is higher than the ``sender``'s current power level,
reject.
#. If there is no previous ``m.room.power_levels`` event in the room, allow.
#. If the new value is higher than the ``sender``'s current power level,
reject.
#. For each of the keys ``users_default``, ``events_default``,
``state_default``, ``ban``, ``redact``, ``kick``, ``invite``, as well as
each entry being changed under the ``events`` or ``users`` keys:
c. For each entry being changed under the ``users`` key, other than the
``sender``'s own entry:
i. If the current value is higher than the ``sender``'s current power level,
reject.
i. If the current value is equal to the ``sender``'s current power level,
reject.
#. If the new value is higher than the ``sender``'s current power level,
reject.
d. Otherwise, allow.
#. For each entry being changed under the ``users`` key, other than the
``sender``'s own entry:
6. If type is ``m.room.redaction``:
i. If the current value is equal to the ``sender``'s current power level,
reject.
a. If the ``sender``'s power level is greater than or equal to the *redact
level*, allow.
#. Otherwise, allow.
#. If the ``sender`` of the event being redacted is the same as the
``sender`` of the ``m.room.redaction``, allow.
#. If type is ``m.room.redaction``:
#. Otherwise, reject.
a. If the ``sender``'s power level is greater than or equal to the *redact
level*, allow.
7. Otherwise, allow.
#. If the domain of the ``event_id`` of the event being redacted is the same
as the domain of the ``event_id`` of the ``m.room.redaction``, allow.
#. Otherwise, reject.
#. Otherwise, allow.
.. NOTE::
@ -482,9 +545,30 @@ The rules are as follows:
the kick *and* ban levels, *and* greater than the target user's power
level.
.. TODO-spec
I think there is some magic about 3pid invites too.
Rejection
+++++++++
If an event is rejected it should neither be relayed to clients nor be included
as a prev event in any new events generated by the server. Subsequent events
from other servers that reference rejected events should be allowed if they
still pass the auth rules. The state used in the checks should be calculated as
normal, except not updating with the rejected event where it is a state event.
If an event in an incoming transaction is rejected, this should not cause the
transaction request to be responded to with an error response.
.. NOTE::
This means that events may be included in the room DAG even though they
should be rejected.
.. NOTE::
This is in contrast to redacted events which can still affect the
state of the room. For example, a redacted ``join`` event will still
result in the user being considered joined.
Retrieving event authorization information
++++++++++++++++++++++++++++++++++++++++++
@ -752,6 +836,10 @@ event to other servers in the room.
Third-party invites
-------------------
.. NOTE::
More information about third party invites is available in the `Client-Server API`_
under the Third Party Invites module.
When an user wants to invite another user in a room but doesn't know the Matrix
ID to invite, they can do so using a third-party identifier (e.g. an e-mail or a
phone number).
@ -822,7 +910,7 @@ identifier.
Public Room Directory
---------------------
To compliment the `Client-Server API`_'s room directory, homeservers need a
To complement the `Client-Server API`_'s room directory, homeservers need a
way to query the public rooms for another server. This can be done by making
a request to the ``/publicRooms`` endpoint for the server the room directory
should be retrieved for.
@ -908,6 +996,19 @@ nothing else.
{{openid_ss_http_api}}
End-to-End Encryption
---------------------
This section complements the `End-to-End Encryption module`_ of the Client-Server
API. For detailed information about end-to-end encryption, please see that module.
The APIs defined here are designed to be able to proxy much of the client's request
through to federation, and have the response also be proxied through to the client.
{{user_keys_ss_http_api}}
Send-to-device messaging
------------------------
@ -979,142 +1080,114 @@ Signing Events
Signing events is complicated by the fact that servers can choose to redact
non-essential parts of an event.
Before signing the event, the ``unsigned`` and ``signature`` members are
removed, it is encoded as `Canonical JSON`_, and then hashed using SHA-256. The
resulting hash is then stored in the event JSON in a ``hash`` object under a
``sha256`` key.
.. code:: python
def hash_event(event_json_object):
# Keys under "unsigned" can be modified by other servers.
# They are useful for conveying information like the age of an
# event that will change in transit.
# Since they can be modifed we need to exclude them from the hash.
unsigned = event_json_object.pop("unsigned", None)
# Signatures will depend on the current value of the "hashes" key.
# We cannot add new hashes without invalidating existing signatures.
signatures = event_json_object.pop("signatures", None)
# The "hashes" key might contain multiple algorithms if we decide to
# migrate away from SHA-2. We don't want to include an existing hash
# output in our hash so we exclude the "hashes" dict from the hash.
hashes = event_json_object.pop("hashes", {})
Adding hashes and signatures to outgoing events
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Encode the JSON using a canonical encoding so that we get the same
# bytes on every server for the same JSON object.
event_json_bytes = encode_canonical_json(event_json_bytes)
Before signing the event, the *content hash* of the event is calculated as
described below. The hash is encoded using `Unpadded Base64`_ and stored in the
event object, in a ``hashes`` object, under a ``sha256`` key.
# Add the base64 encoded bytes of the hash to the "hashes" dict.
hashes["sha256"] = encode_base64(sha256(event_json_bytes).digest())
The event object is then *redacted*, following the `redaction
algorithm`_. Finally it is signed as described in `Signing JSON`_, using the
server's signing key (see also `Retrieving server keys`_).
# Add the "hashes" dict back the event JSON under a "hashes" key.
event_json_object["hashes"] = hashes
if unsigned is not None:
event_json_object["unsigned"] = unsigned
return event_json_object
The signature is then copied back to the original event object.
The event is then stripped of all non-essential keys both at the top level and
within the ``content`` object. Any top-level keys not in the following list
MUST be removed:
See `Persistent Data Unit schema`_ for an example of a signed event.
.. code::
auth_events
depth
event_id
hashes
membership
origin
origin_server_ts
prev_events
prev_state
room_id
sender
signatures
state_key
type
A new ``content`` object is constructed for the resulting event that contains
only the essential keys of the original ``content`` object. If the original
event lacked a ``content`` object at all, a new empty JSON object is created
for it.
The keys that are considered essential for the ``content`` object depend on the
the ``type`` of the event. These are:
Validating hashes and signatures on received events
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When a server receives an event over federation from another server, the
receiving server should check the hashes and signatures on that event.
.. code::
First the signature is checked. The event is redacted following the `redaction
algorithm`_, and the resultant object is checked for a signature from the
originating server, following the algorithm described in `Checking for a signature`_.
Note that this step should succeed whether we have been sent the full event or
a redacted copy.
type is "m.room.aliases":
aliases
If the signature is found to be valid, the expected content hash is calculated
as described below. The content hash in the ``hashes`` property of the received
event is base64-decoded, and the two are compared for equality.
type is "m.room.create":
creator
If the hash check fails, then it is assumed that this is because we have only
been given a redacted version of the event. To enforce this, the receiving
server should use the redacted copy it calculated rather than the full copy it
received.
type is "m.room.history_visibility":
history_visibility
Calculating the content hash for an event
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
type is "m.room.join_rules":
join_rule
The *content hash* of an event covers the complete event including the
*unredacted* contents. It is calculated as follows.
type is "m.room.member":
membership
First, any existing ``unsigned``, ``signature``, and ``hashes`` members are
removed. The resulting object is then encoded as `Canonical JSON`_, and the
JSON is hashed using SHA-256.
type is "m.room.power_levels":
ban
events
events_default
kick
redact
state_default
users
users_default
The resulting stripped object with the new ``content`` object and the original
``hashes`` key is then signed using the JSON signing algorithm outlined below:
Example code
~~~~~~~~~~~~
.. code:: python
def sign_event(event_json_object, name, key):
# Make sure the event has a "hashes" key.
if "hashes" not in event_json_object:
event_json_object = hash_event(event_json_object)
def hash_and_sign_event(event_object, signing_key, signing_name):
# First we need to hash the event object.
content_hash = compute_content_hash(event_object)
event_object["hashes"] = {"sha256": encode_unpadded_base64(content_hash)}
# Strip all the keys that would be removed if the event was redacted.
# The hashes are not stripped and cover all the keys in the event.
# This means that we can tell if any of the non-essential keys are
# modified or removed.
stripped_json_object = strip_non_essential_keys(event_json_object)
stripped_object = strip_non_essential_keys(event_object)
# Sign the stripped JSON object. The signature only covers the
# essential keys and the hashes. This means that we can check the
# signature even if the event is redacted.
signed_json_object = sign_json(stripped_json_object)
signed_object = sign_json(stripped_object, signing_key, signing_name)
# Copy the signatures from the stripped event to the original event.
event_json_object["signatures"] = signed_json_oject["signatures"]
return event_json_object
event_object["signatures"] = signed_object["signatures"]
def compute_content_hash(event_object):
# take a copy of the event before we remove any keys.
event_object = dict(event_object)
# Keys under "unsigned" can be modified by other servers.
# They are useful for conveying information like the age of an
# event that will change in transit.
# Since they can be modifed we need to exclude them from the hash.
event_object.pop("unsigned", None)
# Signatures will depend on the current value of the "hashes" key.
# We cannot add new hashes without invalidating existing signatures.
event_object.pop("signatures", None)
Servers can then transmit the entire event or the event with the non-essential
keys removed. If the entire event is present, receiving servers can then check
the event by computing the SHA-256 of the event, excluding the ``hash`` object.
If the keys have been redacted, then the ``hash`` object is included when
calculating the SHA-256 hash instead.
# The "hashes" key might contain multiple algorithms if we decide to
# migrate away from SHA-2. We don't want to include an existing hash
# output in our hash so we exclude the "hashes" dict from the hash.
event_object.pop("hashes", None)
# Encode the JSON using a canonical encoding so that we get the same
# bytes on every server for the same JSON object.
event_json_bytes = encode_canonical_json(event_object)
New hash functions can be introduced by adding additional keys to the ``hash``
object. Since the ``hash`` object cannot be redacted a server shouldn't allow
too many hashes to be listed, otherwise a server might embed illict data within
the ``hash`` object. For similar reasons a server shouldn't allow hash values
that are too long.
return hashlib.sha256(event_json_bytes)
.. TODO
[[TODO(markjh): We might want to specify a maximum number of keys for the
``hash`` and we might want to specify the maximum output size of a hash]]
[[TODO(markjh) We might want to allow the server to omit the output of well
known hash functions like SHA-256 when none of the keys have been redacted]]
[[TODO(markjh): Since the ``hash`` object cannot be redacted a server
shouldn't allow too many hashes to be listed, otherwise a server might embed
illict data within the ``hash`` object.
We might want to specify a maximum number of keys for the
``hash`` and we might want to specify the maximum output size of a hash]]
[[TODO(markjh) We might want to allow the server to omit the output of well
known hash functions like SHA-256 when none of the keys have been redacted]]
.. |/query/directory| replace:: ``/query/directory``
.. _/query/directory: #get-matrix-federation-v1-query-directory
@ -1125,4 +1198,9 @@ that are too long.
.. _`Inviting to a room`: #inviting-to-a-room
.. _`Canonical JSON`: ../appendices.html#canonical-json
.. _`Unpadded Base64`: ../appendices.html#unpadded-base64
.. _`Server ACLs`: ../client_server/unstable.html#module-server-acls
.. _`Server ACLs`: ../client_server/%CLIENT_RELEASE_LABEL%.html#module-server-acls
.. _`redaction algorithm`: ../client_server/%CLIENT_RELEASE_LABEL%.html#redactions
.. _`Signing JSON`: ../appendices.html#signing-json
.. _`Checking for a signature`: ../appendices.html#checking-for-a-signature
.. _`Device Management module`: ../client_server/%CLIENT_RELEASE_LABEL%.html#device-management
.. _`End-to-End Encryption module`: ../client_server/%CLIENT_RELEASE_LABEL%.html#end-to-end-encryption

Loading…
Cancel
Save