Add syntax highlighting

pull/2980/head
Will 3 years ago
parent 6c6bd57ebf
commit ab64bda76d
No known key found for this signature in database
GPG Key ID: 385872BB265E8BF8

@ -87,19 +87,21 @@ Note
Float values are not permitted by this encoding.
import json
def canonical_json(value):
return json.dumps(
value,
# Encode code-points outside of ASCII as UTF-8 rather than \u escapes
ensure_ascii=False,
# Remove unnecessary white space.
separators=(',',':'),
# Sort the keys of dictionaries.
sort_keys=True,
# Encode the resulting Unicode as UTF-8 bytes.
).encode("UTF-8")
```py
import json
def canonical_json(value):
return json.dumps(
value,
# Encode code-points outside of ASCII as UTF-8 rather than \u escapes
ensure_ascii=False,
# Remove unnecessary white space.
separators=(',',':'),
# Sort the keys of dictionaries.
sort_keys=True,
# Encode the resulting Unicode as UTF-8 bytes.
).encode("UTF-8")
```
#### Grammar
@ -138,108 +140,144 @@ transformation code.
Given the following JSON object:
{}
```json
{}
```
The following canonical JSON should be produced:
{}
```json
{}
```
Given the following JSON object:
{
"one": 1,
"two": "Two"
}
```json
{
"one": 1,
"two": "Two"
}
```
The following canonical JSON should be produced:
{"one":1,"two":"Two"}
```json
{"one":1,"two":"Two"}
```
Given the following JSON object:
{
"b": "2",
"a": "1"
}
```json
{
"b": "2",
"a": "1"
}
```
The following canonical JSON should be produced:
{"a":"1","b":"2"}
```json
{"a":"1","b":"2"}
```
Given the following JSON object:
{"b":"2","a":"1"}
```json
{"b":"2","a":"1"}
```
The following canonical JSON should be produced:
{"a":"1","b":"2"}
```json
{"a":"1","b":"2"}
```
Given the following JSON object:
{
"auth": {
"success": true,
"mxid": "@john.doe:example.com",
"profile": {
"display_name": "John Doe",
"three_pids": [
{
"medium": "email",
"address": "john.doe@example.org"
},
{
"medium": "msisdn",
"address": "123456789"
}
]
}
```json
{
"auth": {
"success": true,
"mxid": "@john.doe:example.com",
"profile": {
"display_name": "John Doe",
"three_pids": [
{
"medium": "email",
"address": "john.doe@example.org"
},
{
"medium": "msisdn",
"address": "123456789"
}
]
}
}
}
```
The following canonical JSON should be produced:
{"auth":{"mxid":"@john.doe:example.com","profile":{"display_name":"John Doe","three_pids":[{"address":"john.doe@example.org","medium":"email"},{"address":"123456789","medium":"msisdn"}]},"success":true}}
```json
{"auth":{"mxid":"@john.doe:example.com","profile":{"display_name":"John Doe","three_pids":[{"address":"john.doe@example.org","medium":"email"},{"address":"123456789","medium":"msisdn"}]},"success":true}}
```
Given the following JSON object:
{
"a": "日本語"
}
```json
{
"a": "日本語"
}
```
The following canonical JSON should be produced:
{"a":"日本語"}
```json
{"a":"日本語"}
```
Given the following JSON object:
{
"本": 2,
"日": 1
}
```json
{
"本": 2,
"日": 1
}
```
The following canonical JSON should be produced:
{"日":1,"本":2}
```json
{"日":1,"本":2}
```
Given the following JSON object:
{
"a": "\u65E5"
}
```json
{
"a": "\u65E5"
}
```
The following canonical JSON should be produced:
{"a":"日"}
```json
{"a":"日"}
```
Given the following JSON object:
{
"a": null
}
```json
{
"a": null
}
```
The following canonical JSON should be produced:
{"a":null}
```json
{"a":null}
```
### Signing Details
@ -263,36 +301,40 @@ The `unsigned` object and the `signatures` object are not covered by the
signature. Therefore intermediate entities can add unsigned data such as
timestamps and additional signatures.
{
"name": "example.org",
"signing_keys": {
"ed25519:1": "XSl0kuyvrXNj6A+7/tkrB9sxSbRi08Of5uRhxOqZtEQ"
},
"unsigned": {
"age_ts": 922834800000
},
"signatures": {
"example.org": {
"ed25519:1": "s76RUgajp8w172am0zQb/iPTHsRnb4SkrzGoeCOSFfcBY2V/1c8QfrmdXHpvnc2jK5BD1WiJIxiMW95fMjK7Bw"
}
}
}
def sign_json(json_object, signing_key, signing_name):
signatures = json_object.pop("signatures", {})
unsigned = json_object.pop("unsigned", None)
signed = signing_key.sign(encode_canonical_json(json_object))
signature_base64 = encode_base64(signed.signature)
key_id = "%s:%s" % (signing_key.alg, signing_key.version)
signatures.setdefault(signing_name, {})[key_id] = signature_base64
json_object["signatures"] = signatures
if unsigned is not None:
json_object["unsigned"] = unsigned
return json_object
```json
{
"name": "example.org",
"signing_keys": {
"ed25519:1": "XSl0kuyvrXNj6A+7/tkrB9sxSbRi08Of5uRhxOqZtEQ"
},
"unsigned": {
"age_ts": 922834800000
},
"signatures": {
"example.org": {
"ed25519:1": "s76RUgajp8w172am0zQb/iPTHsRnb4SkrzGoeCOSFfcBY2V/1c8QfrmdXHpvnc2jK5BD1WiJIxiMW95fMjK7Bw"
}
}
}
```
```py
def sign_json(json_object, signing_key, signing_name):
signatures = json_object.pop("signatures", {})
unsigned = json_object.pop("unsigned", None)
signed = signing_key.sign(encode_canonical_json(json_object))
signature_base64 = encode_base64(signed.signature)
key_id = "%s:%s" % (signing_key.alg, signing_key.version)
signatures.setdefault(signing_name, {})[key_id] = signature_base64
json_object["signatures"] = signatures
if unsigned is not None:
json_object["unsigned"] = unsigned
return json_object
```
### Checking for a Signature
@ -872,122 +914,138 @@ In each case, the server name and key ID are as follows:
Given an empty JSON object:
{}
```json
{}
```
The JSON signing algorithm should emit the following signed data:
{
"signatures": {
"domain": {
"ed25519:1": "K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ"
}
```json
{
"signatures": {
"domain": {
"ed25519:1": "K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ"
}
}
}
```
Given the following JSON object with data values in it:
{
"one": 1,
"two": "Two"
}
```json
{
"one": 1,
"two": "Two"
}
```
The JSON signing algorithm should emit the following signed JSON:
{
"one": 1,
"signatures": {
"domain": {
"ed25519:1": "KqmLSbO39/Bzb0QIYE82zqLwsA+PDzYIpIRA2sRQ4sL53+sN6/fpNSoqE7BP7vBZhG6kYdD13EIMJpvhJI+6Bw"
}
},
"two": "Two"
}
```json
{
"one": 1,
"signatures": {
"domain": {
"ed25519:1": "KqmLSbO39/Bzb0QIYE82zqLwsA+PDzYIpIRA2sRQ4sL53+sN6/fpNSoqE7BP7vBZhG6kYdD13EIMJpvhJI+6Bw"
}
},
"two": "Two"
}
```
### Event Signing
Given the following minimally-sized event:
{
"room_id": "!x:domain",
"sender": "@a:domain",
"origin": "domain",
"origin_server_ts": 1000000,
"signatures": {},
"hashes": {},
"type": "X",
"content": {},
"prev_events": [],
"auth_events": [],
"depth": 3,
"unsigned": {
"age_ts": 1000000
}
```json
{
"room_id": "!x:domain",
"sender": "@a:domain",
"origin": "domain",
"origin_server_ts": 1000000,
"signatures": {},
"hashes": {},
"type": "X",
"content": {},
"prev_events": [],
"auth_events": [],
"depth": 3,
"unsigned": {
"age_ts": 1000000
}
}
```
The event signing algorithm should emit the following signed event:
{
"auth_events": [],
"content": {},
"depth": 3,
"hashes": {
"sha256": "5jM4wQpv6lnBo7CLIghJuHdW+s2CMBJPUOGOC89ncos"
},
"origin": "domain",
"origin_server_ts": 1000000,
"prev_events": [],
"room_id": "!x:domain",
"sender": "@a:domain",
"signatures": {
"domain": {
"ed25519:1": "KxwGjPSDEtvnFgU00fwFz+l6d2pJM6XBIaMEn81SXPTRl16AqLAYqfIReFGZlHi5KLjAWbOoMszkwsQma+lYAg"
}
},
"type": "X",
"unsigned": {
"age_ts": 1000000
```json
{
"auth_events": [],
"content": {},
"depth": 3,
"hashes": {
"sha256": "5jM4wQpv6lnBo7CLIghJuHdW+s2CMBJPUOGOC89ncos"
},
"origin": "domain",
"origin_server_ts": 1000000,
"prev_events": [],
"room_id": "!x:domain",
"sender": "@a:domain",
"signatures": {
"domain": {
"ed25519:1": "KxwGjPSDEtvnFgU00fwFz+l6d2pJM6XBIaMEn81SXPTRl16AqLAYqfIReFGZlHi5KLjAWbOoMszkwsQma+lYAg"
}
},
"type": "X",
"unsigned": {
"age_ts": 1000000
}
}
```
Given the following event containing redactable content:
{
"content": {
"body": "Here is the message content"
},
"event_id": "$0:domain",
"origin": "domain",
"origin_server_ts": 1000000,
"type": "m.room.message",
"room_id": "!r:domain",
"sender": "@u:domain",
"signatures": {},
"unsigned": {
"age_ts": 1000000
}
```json
{
"content": {
"body": "Here is the message content"
},
"event_id": "$0:domain",
"origin": "domain",
"origin_server_ts": 1000000,
"type": "m.room.message",
"room_id": "!r:domain",
"sender": "@u:domain",
"signatures": {},
"unsigned": {
"age_ts": 1000000
}
}
```
The event signing algorithm should emit the following signed event:
{
"content": {
"body": "Here is the message content"
},
"event_id": "$0:domain",
"hashes": {
"sha256": "onLKD1bGljeBWQhWZ1kaP9SorVmRQNdN5aM2JYU2n/g"
},
"origin": "domain",
"origin_server_ts": 1000000,
"type": "m.room.message",
"room_id": "!r:domain",
"sender": "@u:domain",
"signatures": {
"domain": {
"ed25519:1": "Wm+VzmOUOz08Ds+0NTWb1d4CZrVsJSikkeRxh6aCcUwu6pNC78FunoD7KNWzqFn241eYHYMGCA5McEiVPdhzBA"
}
},
"unsigned": {
"age_ts": 1000000
```json
{
"content": {
"body": "Here is the message content"
},
"event_id": "$0:domain",
"hashes": {
"sha256": "onLKD1bGljeBWQhWZ1kaP9SorVmRQNdN5aM2JYU2n/g"
},
"origin": "domain",
"origin_server_ts": 1000000,
"type": "m.room.message",
"room_id": "!r:domain",
"sender": "@u:domain",
"signatures": {
"domain": {
"ed25519:1": "Wm+VzmOUOz08Ds+0NTWb1d4CZrVsJSikkeRxh6aCcUwu6pNC78FunoD7KNWzqFn241eYHYMGCA5McEiVPdhzBA"
}
},
"unsigned": {
"age_ts": 1000000
}
}
```

@ -71,10 +71,12 @@ inconsistency.
Any errors which occur at the Matrix API level MUST return a "standard
error response". This is a JSON object which looks like:
{
"errcode": "<error code>",
"error": "<error message>"
}
```json
{
"errcode": "<error code>",
"error": "<error message>"
}
```
The `error` string will be a human-readable error message, usually a
sentence explaining what went wrong. The `errcode` string will be a
@ -439,22 +441,24 @@ homeserver returns an HTTP 401 response, with a JSON body, as follows:
HTTP/1.1 401 Unauthorized
Content-Type: application/json
```json
{
"flows": [
{
"flows": [
{
"stages": [ "example.type.foo", "example.type.bar" ]
},
{
"stages": [ "example.type.foo", "example.type.baz" ]
}
],
"params": {
"example.type.baz": {
"example_key": "foobar"
}
},
"session": "xxxxxx"
"stages": [ "example.type.foo", "example.type.bar" ]
},
{
"stages": [ "example.type.foo", "example.type.baz" ]
}
],
"params": {
"example.type.baz": {
"example_key": "foobar"
}
},
"session": "xxxxxx"
}
```
In addition to the `flows`, this object contains some extra information:
@ -482,15 +486,17 @@ type `example.type.foo`, it might submit something like this:
POST /_matrix/client/r0/endpoint HTTP/1.1
Content-Type: application/json
{
"a_request_parameter": "something",
"another_request_parameter": "something else",
"auth": {
"type": "example.type.foo",
"session": "xxxxxx",
"example_credential": "verypoorsharedsecret"
}
}
```json
{
"a_request_parameter": "something",
"another_request_parameter": "something else",
"auth": {
"type": "example.type.foo",
"session": "xxxxxx",
"example_credential": "verypoorsharedsecret"
}
}
```
If the homeserver deems the authentication attempt to be successful but
still requires more stages to be completed, it returns HTTP status 401
@ -501,23 +507,25 @@ client has completed successfully:
HTTP/1.1 401 Unauthorized
Content-Type: application/json
```json
{
"completed": [ "example.type.foo" ],
"flows": [
{
"completed": [ "example.type.foo" ],
"flows": [
{
"stages": [ "example.type.foo", "example.type.bar" ]
},
{
"stages": [ "example.type.foo", "example.type.baz" ]
}
],
"params": {
"example.type.baz": {
"example_key": "foobar"
}
},
"session": "xxxxxx"
"stages": [ "example.type.foo", "example.type.bar" ]
},
{
"stages": [ "example.type.foo", "example.type.baz" ]
}
],
"params": {
"example.type.baz": {
"example_key": "foobar"
}
},
"session": "xxxxxx"
}
```
Individual stages may require more than one request to complete, in
which case the response will be as if the request was unauthenticated
@ -531,25 +539,27 @@ status 401 response as above, with the addition of the standard
HTTP/1.1 401 Unauthorized
Content-Type: application/json
```json
{
"errcode": "M_FORBIDDEN",
"error": "Invalid password",
"completed": [ "example.type.foo" ],
"flows": [
{
"errcode": "M_FORBIDDEN",
"error": "Invalid password",
"completed": [ "example.type.foo" ],
"flows": [
{
"stages": [ "example.type.foo", "example.type.bar" ]
},
{
"stages": [ "example.type.foo", "example.type.baz" ]
}
],
"params": {
"example.type.baz": {
"example_key": "foobar"
}
},
"session": "xxxxxx"
"stages": [ "example.type.foo", "example.type.bar" ]
},
{
"stages": [ "example.type.foo", "example.type.baz" ]
}
],
"params": {
"example.type.baz": {
"example_key": "foobar"
}
},
"session": "xxxxxx"
}
```
If the request fails for a reason other than authentication, the server
returns an error message in the standard format. For example:
@ -557,10 +567,12 @@ returns an error message in the standard format. For example:
HTTP/1.1 400 Bad request
Content-Type: application/json
{
"errcode": "M_EXAMPLE_ERROR",
"error": "Something was wrong"
}
```json
{
"errcode": "M_EXAMPLE_ERROR",
"error": "Something was wrong"
}
```
If the client has completed all stages of a flow, the homeserver
performs the API call and returns the result as normal. Completed stages
@ -641,14 +653,16 @@ plain-text.
To use this authentication type, clients should submit an auth dict as
follows:
{
"type": "m.login.password",
"identifier": {
...
},
"password": "<password>",
"session": "<session ID>"
}
```
{
"type": "m.login.password",
"identifier": {
...
},
"password": "<password>",
"session": "<session ID>"
}
```
where the `identifier` property is a user identifier object, as
described in [Identifier types](#identifier-types).
@ -656,30 +670,34 @@ described in [Identifier types](#identifier-types).
For example, to authenticate using the user's Matrix ID, clients would
submit:
{
"type": "m.login.password",
"identifier": {
"type": "m.id.user",
"user": "<user_id or user localpart>"
},
"password": "<password>",
"session": "<session ID>"
}
```json
{
"type": "m.login.password",
"identifier": {
"type": "m.id.user",
"user": "<user_id or user localpart>"
},
"password": "<password>",
"session": "<session ID>"
}
```
Alternatively reply using a 3PID bound to the user's account on the
homeserver using the `/account/3pid`\_ API rather than giving the `user`
explicitly as follows:
{
"type": "m.login.password",
"identifier": {
"type": "m.id.thirdparty",
"medium": "<The medium of the third party identifier.>",
"address": "<The third party address of the user>"
},
"password": "<password>",
"session": "<session ID>"
}
```json
{
"type": "m.login.password",
"identifier": {
"type": "m.id.thirdparty",
"medium": "<The medium of the third party identifier.>",
"address": "<The third party address of the user>"
},
"password": "<password>",
"session": "<session ID>"
}
```
In the case that the homeserver does not know about the supplied 3PID,
the homeserver must respond with 403 Forbidden.
@ -695,11 +713,13 @@ The user completes a Google ReCaptcha 2.0 challenge
To use this authentication type, clients should submit an auth dict as
follows:
{
"type": "m.login.recaptcha",
"response": "<captcha response>",
"session": "<session ID>"
}
```json
{
"type": "m.login.recaptcha",
"response": "<captcha response>",
"session": "<session ID>"
}
```
#### Single Sign-On
@ -730,18 +750,20 @@ information should be submitted to the homeserver.
To use this authentication type, clients should submit an auth dict as
follows:
```json
{
"type": "m.login.email.identity",
"threepidCreds": [
{
"type": "m.login.email.identity",
"threepidCreds": [
{
"sid": "<identity server session id>",
"client_secret": "<identity server client secret>",
"id_server": "<url of identity server authed with, e.g. 'matrix.org:8090'>",
"id_access_token": "<access token previously registered with the identity server>"
}
],
"session": "<session ID>"
"sid": "<identity server session id>",
"client_secret": "<identity server client secret>",
"id_server": "<url of identity server authed with, e.g. 'matrix.org:8090'>",
"id_access_token": "<access token previously registered with the identity server>"
}
],
"session": "<session ID>"
}
```
Note that `id_server` (and therefore `id_access_token`) is optional if
the `/requestToken` request did not include them.
@ -762,18 +784,20 @@ information should be submitted to the homeserver.
To use this authentication type, clients should submit an auth dict as
follows:
```json
{
"type": "m.login.msisdn",
"threepidCreds": [
{
"type": "m.login.msisdn",
"threepidCreds": [
{
"sid": "<identity server session id>",
"client_secret": "<identity server client secret>",
"id_server": "<url of identity server authed with, e.g. 'matrix.org:8090'>",
"id_access_token": "<access token previously registered with the identity server>"
}
],
"session": "<session ID>"
"sid": "<identity server session id>",
"client_secret": "<identity server client secret>",
"id_server": "<url of identity server authed with, e.g. 'matrix.org:8090'>",
"id_access_token": "<access token previously registered with the identity server>"
}
],
"session": "<session ID>"
}
```
Note that `id_server` (and therefore `id_access_token`) is optional if
the `/requestToken` request did not include them.
@ -798,10 +822,12 @@ server can instead send flows `m.login.recaptcha, m.login.dummy` and
To use this authentication type, clients should submit an auth dict with
just the type and session, if provided:
{
"type": "m.login.dummy",
"session": "<session ID>"
}
```json
{
"type": "m.login.dummy",
"session": "<session ID>"
}
```
##### Fallback
@ -820,11 +846,13 @@ This MUST return an HTML page which can perform this authentication
stage. This page must use the following JavaScript when the
authentication has been completed:
if (window.onAuthDone) {
window.onAuthDone();
} else if (window.opener && window.opener.postMessage) {
window.opener.postMessage("authDone", "*");
}
```js
if (window.onAuthDone) {
window.onAuthDone();
} else if (window.opener && window.opener.postMessage) {
window.opener.postMessage("authDone", "*");
}
```
This allows the client to either arrange for the global function
`onAuthDone` to be defined in an embedded browser, or to use the HTML5
@ -836,64 +864,67 @@ Once a client receives the notification that the authentication stage
has been completed, it should resubmit the request with an auth dict
with just the session ID:
{
"session": "<session ID>"
}
```json
{
"session": "<session ID>"
}
```
#### Example
A client webapp might use the following JavaScript to open a popup
window which will handle unknown login types:
/**
* Arguments:
* homeserverUrl: the base url of the homeserver (e.g. "https://matrix.org")
*
* apiEndpoint: the API endpoint being used (e.g.
* "/_matrix/client/%CLIENT_MAJOR_VERSION%/account/password")
*
* loginType: the loginType being attempted (e.g. "m.login.recaptcha")
*
* sessionID: the session ID given by the homeserver in earlier requests
*
* onComplete: a callback which will be called with the results of the request
*/
function unknownLoginType(homeserverUrl, apiEndpoint, loginType, sessionID, onComplete) {
var popupWindow;
var eventListener = function(ev) {
// check it's the right message from the right place.
if (ev.data !== "authDone" || ev.origin !== homeserverUrl) {
return;
}
// close the popup
popupWindow.close();
window.removeEventListener("message", eventListener);
// repeat the request
var requestBody = {
auth: {
session: sessionID,
},
};
request({
method:'POST', url:apiEndpoint, json:requestBody,
}, onComplete);
```js
/**
* Arguments:
* homeserverUrl: the base url of the homeserver (e.g. "https://matrix.org")
*
* apiEndpoint: the API endpoint being used (e.g.
* "/_matrix/client/%CLIENT_MAJOR_VERSION%/account/password")
*
* loginType: the loginType being attempted (e.g. "m.login.recaptcha")
*
* sessionID: the session ID given by the homeserver in earlier requests
*
* onComplete: a callback which will be called with the results of the request
*/
function unknownLoginType(homeserverUrl, apiEndpoint, loginType, sessionID, onComplete) {
var popupWindow;
var eventListener = function(ev) {
// check it's the right message from the right place.
if (ev.data !== "authDone" || ev.origin !== homeserverUrl) {
return;
}
// close the popup
popupWindow.close();
window.removeEventListener("message", eventListener);
// repeat the request
var requestBody = {
auth: {
session: sessionID,
},
};
window.addEventListener("message", eventListener);
request({
method:'POST', url:apiEndpoint, json:requestBody,
}, onComplete);
};
var url = homeserverUrl +
"/_matrix/client/%CLIENT_MAJOR_VERSION%/auth/" +
encodeURIComponent(loginType) +
"/fallback/web?session=" +
encodeURIComponent(sessionID);
window.addEventListener("message", eventListener);
var url = homeserverUrl +
"/_matrix/client/%CLIENT_MAJOR_VERSION%/auth/" +
encodeURIComponent(loginType) +
"/fallback/web?session=" +
encodeURIComponent(sessionID);
popupWindow = window.open(url);
}
popupWindow = window.open(url);
}
```
##### Identifier types
@ -920,10 +951,12 @@ A client can identify a user using their Matrix ID. This can either be
the fully qualified Matrix user ID, or just the localpart of the user
ID.
"identifier": {
"type": "m.id.user",
"user": "<user_id or user localpart>"
}
```json
"identifier": {
"type": "m.id.user",
"user": "<user_id or user localpart>"
}
```
#### Third-party ID
@ -940,11 +973,13 @@ using the `/account/3pid`\_ API. See the [3PID
Types](../appendices.html#pid-types) Appendix for a list of Third-party
ID media.
"identifier": {
"type": "m.id.thirdparty",
"medium": "<The medium of the third party identifier>",
"address": "<The canonicalised third party address of the user>"
}
```json
"identifier": {
"type": "m.id.thirdparty",
"medium": "<The medium of the third party identifier>",
"address": "<The canonicalised third party address of the user>"
}
```
#### Phone number
@ -962,11 +997,13 @@ If the client wishes to canonicalise the phone number, then it can use
the `m.id.thirdparty` identifier type with a `medium` of `msisdn`
instead.
"identifier": {
"type": "m.id.phone",
"country": "<The country that the phone number is from>",
"phone": "<The phone number>"
}
```json
"identifier": {
"type": "m.id.phone",
"country": "<The country that the phone number is from>",
"phone": "<The phone number>"
}
```
The `country` is the two-letter uppercase ISO-3166-1 alpha-2 country
code that the number in `phone` should be parsed as if it were dialled
@ -983,27 +1020,31 @@ API](#user-interactive-authentication-api).
For a simple username/password login, clients should submit a `/login`
request as follows:
{
"type": "m.login.password",
"identifier": {
"type": "m.id.user",
"user": "<user_id or user localpart>"
},
"password": "<password>"
}
```json
{
"type": "m.login.password",
"identifier": {
"type": "m.id.user",
"user": "<user_id or user localpart>"
},
"password": "<password>"
}
```
Alternatively, a client can use a 3PID bound to the user's account on
the homeserver using the `/account/3pid`\_ API rather than giving the
`user` explicitly, as follows:
{
"type": "m.login.password",
"identifier": {
"medium": "<The medium of the third party identifier>",
"address": "<The canonicalised third party address of the user>"
},
"password": "<password>"
}
```json
{
"type": "m.login.password",
"identifier": {
"medium": "<The medium of the third party identifier>",
"address": "<The canonicalised third party address of the user>"
},
"password": "<password>"
}
```
In the case that the homeserver does not know about the supplied 3PID,
the homeserver must respond with `403 Forbidden`.
@ -1011,10 +1052,12 @@ the homeserver must respond with `403 Forbidden`.
To log in using a login token, clients should submit a `/login` request
as follows:
{
"type": "m.login.token",
"token": "<login token>"
}
```json
{
"type": "m.login.token",
"token": "<login token>"
}
```
As with [token-based]() interactive login, the `token` must encode the
user ID. In the case that the token is not valid, the homeserver must
@ -1166,13 +1209,15 @@ to change their password.
An example of the capability API's response for this capability is:
{
"capabilities": {
"m.change_password": {
"enabled": false
}
}
```json
{
"capabilities": {
"m.change_password": {
"enabled": false
}
}
}
```
### `m.room_versions` capability
@ -1183,19 +1228,21 @@ upgrade their rooms.
An example of the capability API's response for this capability is:
{
"capabilities": {
"m.room_versions": {
"default": "1",
"available": {
"1": "stable",
"2": "stable",
"3": "unstable",
"custom-version": "unstable"
}
}
```json
{
"capabilities": {
"m.room_versions": {
"default": "1",
"available": {
"1": "stable",
"2": "stable",
"3": "unstable",
"custom-version": "unstable"
}
}
}
}
```
This capability mirrors the same restrictions of [room
versions](../index.html#room-versions) to describe which versions are
@ -1626,36 +1673,48 @@ There are several APIs provided to `GET` events for a room:
Valid requests look like:
PUT /rooms/!roomid:domain/state/m.example.event
{ "key" : "without a state key" }
PUT /rooms/!roomid:domain/state/m.another.example.event/foo
{ "key" : "with 'foo' as the state key" }
```
PUT /rooms/!roomid:domain/state/m.example.event
{ "key" : "without a state key" }
```
```
PUT /rooms/!roomid:domain/state/m.another.example.event/foo
{ "key" : "with 'foo' as the state key" }
```
In contrast, these requests are invalid:
POST /rooms/!roomid:domain/state/m.example.event/
{ "key" : "cannot use POST here" }
PUT /rooms/!roomid:domain/state/m.another.example.event/foo/11
{ "key" : "txnIds are not supported" }
```
POST /rooms/!roomid:domain/state/m.example.event/
{ "key" : "cannot use POST here" }
```
```
PUT /rooms/!roomid:domain/state/m.another.example.event/foo/11
{ "key" : "txnIds are not supported" }
```
Care should be taken to avoid setting the wrong `state key`:
PUT /rooms/!roomid:domain/state/m.another.example.event/11
{ "key" : "with '11' as the state key, but was probably intended to be a txnId" }
```
PUT /rooms/!roomid:domain/state/m.another.example.event/11
{ "key" : "with '11' as the state key, but was probably intended to be a txnId" }
```
The `state_key` is often used to store state about individual users, by
using the user ID as the `state_key` value. For example:
PUT /rooms/!roomid:domain/state/m.favorite.animal.event/%40my_user%3Aexample.org
{ "animal" : "cat", "reason": "fluffy" }
```
PUT /rooms/!roomid:domain/state/m.favorite.animal.event/%40my_user%3Aexample.org
{ "animal" : "cat", "reason": "fluffy" }
```
In some cases, there may be no need for a `state_key`, so it can be
omitted:
PUT /rooms/!roomid:domain/state/m.room.bgd.color
{ "color": "red", "hex": "#ff0000" }
```
PUT /rooms/!roomid:domain/state/m.room.bgd.color
{ "color": "red", "hex": "#ff0000" }
```
{{room\_send\_cs\_http\_api}}
@ -1872,19 +1931,23 @@ someone, the user performing the ban MUST have the required power level.
To ban a user, a request should be made to `/rooms/<room_id>/ban`\_
with:
{
"user_id": "<user id to ban>"
"reason": "string: <reason for the ban>"
}
```json
{
"user_id": "<user id to ban>",
"reason": "string: <reason for the ban>"
}
````
Banning a user adjusts the banned member's membership state to `ban`.
Like with other membership changes, a user can directly adjust the
target member's state, by making a request to
`/rooms/<room id>/state/m.room.member/<user id>`:
{
"membership": "ban"
}
```json
{
"membership": "ban"
}
```
A user must be explicitly unbanned with a request to
`/rooms/<room_id>/unban`\_ before they can re-join the room or be
@ -1938,11 +2001,13 @@ Homeservers SHOULD implement rate limiting to reduce the risk of being
overloaded. If a request is refused due to rate limiting, it should
return a standard error response of the form:
{
"errcode": "M_LIMIT_EXCEEDED",
"error": "string",
"retry_after_ms": integer (optional)
}
```json
{
"errcode": "M_LIMIT_EXCEEDED",
"error": "string",
"retry_after_ms": integer (optional)
}
```
The `retry_after_ms` key SHOULD be included to tell the client how long
they have to wait in milliseconds before they can try again.

@ -99,14 +99,16 @@ with the following properties:
Example:
{
"key":"06UzBknVHFMwgi7AVloY7ylC+xhOhEX4PkNge14Grl8",
"signatures": {
"@user:example.com": {
"ed25519:EGURVBUNJP": "YbJva03ihSj5mPk+CHMJKUKlCXCPFXjXOK6VqBnN9nA2evksQcTGn6hwQfrgRHIDDXO2le49x7jnWJHMJrJoBQ"
}
}
```json
{
"key":"06UzBknVHFMwgi7AVloY7ylC+xhOhEX4PkNge14Grl8",
"signatures": {
"@user:example.com": {
"ed25519:EGURVBUNJP": "YbJva03ihSj5mPk+CHMJKUKlCXCPFXjXOK6VqBnN9nA2evksQcTGn6hwQfrgRHIDDXO2le49x7jnWJHMJrJoBQ"
}
}
}
```
##### Device keys
@ -1239,22 +1241,24 @@ keys in [Server-side key backups](#server-side-key-backups) but adds the
Example:
[
{
"algorithm": "m.megolm.v1.aes-sha2",
"forwarding_curve25519_key_chain": [
"hPQNcabIABgGnx3/ACv/jmMmiQHoeFfuLB17tzWp6Hw"
],
"room_id": "!Cuyf34gef24t:localhost",
"sender_key": "RF3s+E7RkTQTGF2d8Deol0FkQvgII2aJDf3/Jp5mxVU",
"sender_claimed_keys": {
"ed25519": "<device ed25519 identity key>",
},
"session_id": "X3lUlvLELLYxeTx4yOVu6UDpasGEVO0Jbu+QFnm0cKQ",
"session_key": "AgAAAADxKHa9uFxcXzwYoNueL5Xqi69IkD4sni8Llf..."
```
[
{
"algorithm": "m.megolm.v1.aes-sha2",
"forwarding_curve25519_key_chain": [
"hPQNcabIABgGnx3/ACv/jmMmiQHoeFfuLB17tzWp6Hw"
],
"room_id": "!Cuyf34gef24t:localhost",
"sender_key": "RF3s+E7RkTQTGF2d8Deol0FkQvgII2aJDf3/Jp5mxVU",
"sender_claimed_keys": {
"ed25519": "<device ed25519 identity key>",
},
...
]
"session_id": "X3lUlvLELLYxeTx4yOVu6UDpasGEVO0Jbu+QFnm0cKQ",
"session_key": "AgAAAADxKHa9uFxcXzwYoNueL5Xqi69IkD4sni8Llf..."
},
...
]
```
#### Messaging Algorithms
@ -1297,19 +1301,21 @@ device key, and must publish Curve25519 one-time keys.
An event encrypted using Olm has the following format:
{
"type": "m.room.encrypted",
"content": {
"algorithm": "m.olm.v1.curve25519-aes-sha2",
"sender_key": "<sender_curve25519_key>",
"ciphertext": {
"<device_curve25519_key>": {
"type": 0,
"body": "<encrypted_payload_base_64>"
}
}
```json
{
"type": "m.room.encrypted",
"content": {
"algorithm": "m.olm.v1.curve25519-aes-sha2",
"sender_key": "<sender_curve25519_key>",
"ciphertext": {
"<device_curve25519_key>": {
"type": 0,
"body": "<encrypted_payload_base_64>"
}
}
}
}
```
`ciphertext` is a mapping from device Curve25519 key to an encrypted
payload for that device. `body` is a Base64-encoded Olm message body.
@ -1334,18 +1340,20 @@ message.
The plaintext payload is of the form:
{
"type": "<type of the plaintext event>",
"content": "<content for the plaintext event>",
"sender": "<sender_user_id>",
"recipient": "<recipient_user_id>",
"recipient_keys": {
"ed25519": "<our_ed25519_key>"
},
"keys": {
"ed25519": "<sender_ed25519_key>"
}
}
```json
{
"type": "<type of the plaintext event>",
"content": "<content for the plaintext event>",
"sender": "<sender_user_id>",
"recipient": "<recipient_user_id>",
"recipient_keys": {
"ed25519": "<our_ed25519_key>"
},
"keys": {
"ed25519": "<sender_ed25519_key>"
}
}
```
The type and content of the plaintext message event are given in the
payload.
@ -1418,25 +1426,29 @@ Devices that support Megolm must support Olm, and include
An event encrypted using Megolm has the following format:
{
"type": "m.room.encrypted",
"content": {
"algorithm": "m.megolm.v1.aes-sha2",
"sender_key": "<sender_curve25519_key>",
"device_id": "<sender_device_id>",
"session_id": "<outbound_group_session_id>",
"ciphertext": "<encrypted_payload_base_64>"
}
}
```json
{
"type": "m.room.encrypted",
"content": {
"algorithm": "m.megolm.v1.aes-sha2",
"sender_key": "<sender_curve25519_key>",
"device_id": "<sender_device_id>",
"session_id": "<outbound_group_session_id>",
"ciphertext": "<encrypted_payload_base_64>"
}
}
```
The encrypted payload can contain any message event. The plaintext is of
the form:
{
"type": "<event_type>",
"content": "<event_content>",
"room_id": "<the room_id>"
}
```json
{
"type": "<event_type>",
"content": "<event_content>",
"room_id": "<the room_id>"
}
```
We include the room ID in the payload, because otherwise the homeserver
would be able to change the room a message was sent in.
@ -1554,22 +1566,24 @@ already shared a room.
Example response:
{
"next_batch": "s72595_4483_1934",
"rooms": {"leave": {}, "join": {}, "invite": {}},
"device_lists": {
"changed": [
"@alice:example.com",
],
"left": [
"@bob:example.com",
],
},
"device_one_time_keys_count": {
"curve25519": 10,
"signed_curve25519": 20
}
}
```json
{
"next_batch": "s72595_4483_1934",
"rooms": {"leave": {}, "join": {}, "invite": {}},
"device_lists": {
"changed": [
"@alice:example.com",
],
"left": [
"@bob:example.com",
],
},
"device_one_time_keys_count": {
"curve25519": 10,
"signed_curve25519": 20
}
}
```
#### Reporting that decryption keys are withheld

@ -332,21 +332,23 @@ a rich reply, infinitely.
An `m.in_reply_to` relationship looks like the following:
{
...
"type": "m.room.message",
"content": {
"msgtype": "m.text",
"body": "<body including fallback>",
"format": "org.matrix.custom.html",
"formatted_body": "<HTML including fallback>",
"m.relates_to": {
"m.in_reply_to": {
"event_id": "$another:event.com"
}
}
```
{
...
"type": "m.room.message",
"content": {
"msgtype": "m.text",
"body": "<body including fallback>",
"format": "org.matrix.custom.html",
"formatted_body": "<HTML including fallback>",
"m.relates_to": {
"m.in_reply_to": {
"event_id": "$another:event.com"
}
}
}
}
```
####### Fallbacks and event representation

@ -18,12 +18,14 @@ Mentions apply only to [m.room.message]() events where the `msgtype` is
To make a mention, reference the entity being mentioned in the
`formatted_body` using an anchor, like so:
{
"body": "Hello Alice!",
"msgtype": "m.text",
"format": "org.matrix.custom.html",
"formatted_body": "Hello <a href='https://matrix.to/#/@alice:example.org'>Alice</a>!"
}
```json
{
"body": "Hello Alice!",
"msgtype": "m.text",
"format": "org.matrix.custom.html",
"formatted_body": "Hello <a href='https://matrix.to/#/@alice:example.org'>Alice</a>!"
}
```
#### Client behaviour

@ -282,15 +282,17 @@ user. By default this rule is disabled.
Definition
{
"rule_id": ".m.rule.master",
"default": true,
"enabled": false,
"conditions": [],
"actions": [
"dont_notify"
]
}
```json
{
"rule_id": ".m.rule.master",
"default": true,
"enabled": false,
"conditions": [],
"actions": [
"dont_notify"
]
}
```
######## `.m.rule.suppress_notices`
@ -298,21 +300,23 @@ Matches messages with a `msgtype` of `notice`.
Definition:
{
"rule_id": ".m.rule.suppress_notices",
"default": true,
"enabled": true,
"conditions": [
{
"kind": "event_match",
"key": "content.msgtype",
"pattern": "m.notice",
}
],
"actions": [
"dont_notify",
]
}
```json
{
"rule_id": ".m.rule.suppress_notices",
"default": true,
"enabled": true,
"conditions": [
{
"kind": "event_match",
"key": "content.msgtype",
"pattern": "m.notice",
}
],
"actions": [
"dont_notify",
]
}
```
######## `.m.rule.invite_for_me`
@ -320,35 +324,37 @@ Matches any invites to a new room for this user.
Definition:
{
"rule_id": ".m.rule.invite_for_me",
"default": true,
"enabled": true,
"conditions": [
{
"key": "type",
"kind": "event_match",
"pattern": "m.room.member"
},
{
"key": "content.membership",
"kind": "event_match",
"pattern": "invite"
},
{
"key": "state_key",
"kind": "event_match",
"pattern": "[the user's Matrix ID]"
}
],
"actions": [
"notify",
{
"set_tweak": "sound",
"value": "default"
}
]
}
```json
{
"rule_id": ".m.rule.invite_for_me",
"default": true,
"enabled": true,
"conditions": [
{
"key": "type",
"kind": "event_match",
"pattern": "m.room.member"
},
{
"key": "content.membership",
"kind": "event_match",
"pattern": "invite"
},
{
"key": "state_key",
"kind": "event_match",
"pattern": "[the user's Matrix ID]"
}
],
"actions": [
"notify",
{
"set_tweak": "sound",
"value": "default"
}
]
}
```
######## `.m.rule.member_event`
@ -356,21 +362,23 @@ Matches any `m.room.member_event`.
Definition:
{
"rule_id": ".m.rule.member_event",
"default": true,
"enabled": true,
"conditions": [
{
"key": "type",
"kind": "event_match",
"pattern": "m.room.member"
}
],
"actions": [
"dont_notify"
]
}
```json
{
"rule_id": ".m.rule.member_event",
"default": true,
"enabled": true,
"conditions": [
{
"key": "type",
"kind": "event_match",
"pattern": "m.room.member"
}
],
"actions": [
"dont_notify"
]
}
```
######## `.m.rule.contains_display_name`
@ -379,26 +387,28 @@ current display name in the room in which it was sent.
Definition:
{
"rule_id": ".m.rule.contains_display_name",
"default": true,
"enabled": true,
"conditions": [
{
"kind": "contains_display_name"
}
],
"actions": [
"notify",
{
"set_tweak": "sound",
"value": "default"
},
{
"set_tweak": "highlight"
}
]
}
```json
{
"rule_id": ".m.rule.contains_display_name",
"default": true,
"enabled": true,
"conditions": [
{
"kind": "contains_display_name"
}
],
"actions": [
"notify",
{
"set_tweak": "sound",
"value": "default"
},
{
"set_tweak": "highlight"
}
]
}
```
######## `.m.rule.tombstone`
@ -408,29 +418,31 @@ an `@room` notification would accomplish.
Definition:
{
"rule_id": ".m.rule.tombstone",
"default": true,
"enabled": true,
"conditions": [
{
"kind": "event_match",
"key": "type",
"pattern": "m.room.tombstone"
},
{
"kind": "event_match",
"key": "state_key",
"pattern": ""
}
],
"actions": [
"notify",
{
"set_tweak": "highlight"
}
]
}
```json
{
"rule_id": ".m.rule.tombstone",
"default": true,
"enabled": true,
"conditions": [
{
"kind": "event_match",
"key": "type",
"pattern": "m.room.tombstone"
},
{
"kind": "event_match",
"key": "state_key",
"pattern": ""
}
],
"actions": [
"notify",
{
"set_tweak": "highlight"
}
]
}
```
######## `.m.rule.roomnotif`
@ -439,28 +451,30 @@ Matches any message whose content is unencrypted and contains the text
Definition:
{
"rule_id": ".m.rule.roomnotif",
"default": true,
"enabled": true,
"conditions": [
{
"kind": "event_match",
"key": "content.body",
"pattern": "@room"
},
{
"kind": "sender_notification_permission",
"key": "room"
}
],
"actions": [
"notify",
{
"set_tweak": "highlight"
}
]
}
```json
{
"rule_id": ".m.rule.roomnotif",
"default": true,
"enabled": true,
"conditions": [
{
"kind": "event_match",
"key": "content.body",
"pattern": "@room"
},
{
"kind": "sender_notification_permission",
"key": "room"
}
],
"actions": [
"notify",
{
"set_tweak": "highlight"
}
]
}
```
####### Default Content Rules
@ -471,22 +485,24 @@ part of the user's Matrix ID, separated by word boundaries.
Definition (as a `content` rule):
{
"rule_id": ".m.rule.contains_user_name",
"default": true,
"enabled": true,
"pattern": "[the local part of the user's Matrix ID]",
"actions": [
"notify",
{
"set_tweak": "sound",
"value": "default"
},
{
"set_tweak": "highlight"
}
]
}
```json
{
"rule_id": ".m.rule.contains_user_name",
"default": true,
"enabled": true,
"pattern": "[the local part of the user's Matrix ID]",
"actions": [
"notify",
{
"set_tweak": "sound",
"value": "default"
},
{
"set_tweak": "highlight"
}
]
}
```
####### Default Underride Rules
@ -496,25 +512,27 @@ Matches any incoming VOIP call.
Definition:
{
"rule_id": ".m.rule.call",
"default": true,
"enabled": true,
"conditions": [
{
"key": "type",
"kind": "event_match",
"pattern": "m.call.invite"
}
],
"actions": [
"notify",
{
"set_tweak": "sound",
"value": "ring"
}
]
}
```json
{
"rule_id": ".m.rule.call",
"default": true,
"enabled": true,
"conditions": [
{
"key": "type",
"kind": "event_match",
"pattern": "m.call.invite"
}
],
"actions": [
"notify",
{
"set_tweak": "sound",
"value": "ring"
}
]
}
```
######## `.m.rule.encrypted_room_one_to_one`
@ -526,29 +544,31 @@ encrypted (in 1:1 rooms) or none.
Definition:
{
"rule_id": ".m.rule.encrypted_room_one_to_one",
"default": true,
"enabled": true,
"conditions": [
{
"kind": "room_member_count",
"is": "2"
},
{
"kind": "event_match",
"key": "type",
"pattern": "m.room.encrypted"
}
],
"actions": [
"notify",
{
"set_tweak": "sound",
"value": "default"
}
]
}
```json
{
"rule_id": ".m.rule.encrypted_room_one_to_one",
"default": true,
"enabled": true,
"conditions": [
{
"kind": "room_member_count",
"is": "2"
},
{
"kind": "event_match",
"key": "type",
"pattern": "m.room.encrypted"
}
],
"actions": [
"notify",
{
"set_tweak": "sound",
"value": "default"
}
]
}
```
######## `.m.rule.room_one_to_one`
@ -556,29 +576,31 @@ Matches any message sent in a room with exactly two members.
Definition:
{
"rule_id": ".m.rule.room_one_to_one",
"default": true,
"enabled": true,
"conditions": [
{
"kind": "room_member_count",
"is": "2"
},
{
"kind": "event_match",
"key": "type",
"pattern": "m.room.message"
}
],
"actions": [
"notify",
{
"set_tweak": "sound",
"value": "default"
}
]
}
```json
{
"rule_id": ".m.rule.room_one_to_one",
"default": true,
"enabled": true,
"conditions": [
{
"kind": "room_member_count",
"is": "2"
},
{
"kind": "event_match",
"key": "type",
"pattern": "m.room.message"
}
],
"actions": [
"notify",
{
"set_tweak": "sound",
"value": "default"
}
]
}
```
######## `.m.rule.message`
@ -586,21 +608,23 @@ Matches all chat messages.
Definition:
{
"rule_id": ".m.rule.message",
"default": true,
"enabled": true,
"conditions": [
{
"kind": "event_match",
"key": "type",
"pattern": "m.room.message"
}
],
"actions": [
"notify"
]
}
```json
{
"rule_id": ".m.rule.message",
"default": true,
"enabled": true,
"conditions": [
{
"kind": "event_match",
"key": "type",
"pattern": "m.room.message"
}
],
"actions": [
"notify"
]
}
```
######## `.m.rule.encrypted`
@ -611,21 +635,23 @@ either matches *all* events that are encrypted (in group rooms) or none.
Definition:
{
"rule_id": ".m.rule.encrypted",
"default": true,
"enabled": true,
"conditions": [
{
"kind": "event_match",
"key": "type",
"pattern": "m.room.encrypted"
}
],
"actions": [
"notify"
]
}
```json
{
"rule_id": ".m.rule.encrypted",
"default": true,
"enabled": true,
"conditions": [
{
"kind": "event_match",
"key": "type",
"pattern": "m.room.encrypted"
}
],
"actions": [
"notify"
]
}
```
##### Push Rules: API

@ -68,15 +68,17 @@ before delivering them to clients.
Receipts are sent across federation as EDUs with type `m.receipt`. The
format of the EDUs are:
{
<room_id>: {
<receipt_type>: {
<user_id>: { <content> }
},
...
```
{
<room_id>: {
<receipt_type>: {
<user_id>: { <content> }
},
...
}
},
...
}
```
These are always sent as deltas to previously sent receipts. Currently
only a single `<receipt_type>` should be used: `m.read`.

@ -114,37 +114,43 @@ Some secret is encrypted using keys with ID `key_id_1` and `key_id_2`:
`org.example.some.secret`:
{
"encrypted": {
"key_id_1": {
"ciphertext": "base64+encoded+encrypted+data",
"mac": "base64+encoded+mac",
// ... other properties according to algorithm property in
// m.secret_storage.key.key_id_1
},
"key_id_2": {
// ...
}
}
```
{
"encrypted": {
"key_id_1": {
"ciphertext": "base64+encoded+encrypted+data",
"mac": "base64+encoded+mac",
// ... other properties according to algorithm property in
// m.secret_storage.key.key_id_1
},
"key_id_2": {
// ...
}
}
}
```
and the key descriptions for the keys would be:
`m.secret_storage.key.key_id_1`:
{
"name": "Some key",
"algorithm": "m.secret_storage.v1.aes-hmac-sha2",
// ... other properties according to algorithm
}
```
{
"name": "Some key",
"algorithm": "m.secret_storage.v1.aes-hmac-sha2",
// ... other properties according to algorithm
}
```
`m.secret_storage.key.key_id_2`:
{
"name": "Some other key",
"algorithm": "m.secret_storage.v1.aes-hmac-sha2",
// ... other properties according to algorithm
}
```
{
"name": "Some other key",
"algorithm": "m.secret_storage.v1.aes-hmac-sha2",
// ... other properties according to algorithm
}
```
###### `m.secret_storage.v1.aes-hmac-sha2`
@ -247,24 +253,28 @@ correctly entered the key, clients should:
For example, the `m.secret_storage.key.key_id` for a key using this
algorithm could look like:
{
"name": "m.default",
"algorithm": "m.secret_storage.v1.aes-hmac-sha2",
"iv": "random+data",
"mac": "mac+of+encrypted+zeros"
}
```json
{
"name": "m.default",
"algorithm": "m.secret_storage.v1.aes-hmac-sha2",
"iv": "random+data",
"mac": "mac+of+encrypted+zeros"
}
```
and data encrypted using this algorithm could look like this:
{
"encrypted": {
"key_id": {
"iv": "16+bytes+base64",
"ciphertext": "base64+encoded+encrypted+data",
"mac": "base64+encoded+mac"
}
```json
{
"encrypted": {
"key_id": {
"iv": "16+bytes+base64",
"ciphertext": "base64+encoded+encrypted+data",
"mac": "base64+encoded+mac"
}
}
}
}
```
###### Key representation
@ -339,15 +349,17 @@ in the `iterations` parameter.
Example:
{
"passphrase": {
"algorithm": "m.pbkdf2",
"salt": "MmMsAlty",
"iterations": 100000,
"bits": 256
},
...
}
```
{
"passphrase": {
"algorithm": "m.pbkdf2",
"salt": "MmMsAlty",
"iterations": 100000,
"bits": 256
},
...
}
```
#### Sharing
@ -407,12 +419,14 @@ previous request. It is sent as an unencrypted to-device event.
Example:
{
"name": "org.example.some.secret",
"action": "request",
"requesting_device_id": "ABCDEFG",
"request_id": "randomly_generated_id_9573"
}
```json
{
"name": "org.example.some.secret",
"action": "request",
"requesting_device_id": "ABCDEFG",
"request_id": "randomly_generated_id_9573"
}
```
###### `m.secret.send`
@ -444,7 +458,9 @@ an `m.secret.request` event. It must be encrypted as an
Example:
{
"request_id": "randomly_generated_id_9573",
"secret": "ThisIsASecretDon'tTellAnyone"
}
```json
{
"request_id": "randomly_generated_id_9573",
"secret": "ThisIsASecretDon'tTellAnyone"
}
```

@ -125,19 +125,21 @@ This module adds the following properties to the \_ response:
Example response:
{
"next_batch": "s72595_4483_1934",
"rooms": {"leave": {}, "join": {}, "invite": {}},
"to_device": {
"events": [
{
"sender": "@alice:example.com",
"type": "m.new_device",
"content": {
"device_id": "XYZABCDE",
"rooms": ["!726s6s6q:example.com"]
}
}
]
```json
{
"next_batch": "s72595_4483_1934",
"rooms": {"leave": {}, "join": {}, "invite": {}},
"to_device": {
"events": [
{
"sender": "@alice:example.com",
"type": "m.new_device",
"content": {
"device_id": "XYZABCDE",
"rooms": ["!726s6s6q:example.com"]
}
}
}
]
}
}
```

@ -69,10 +69,12 @@ communication, and all API calls use a Content-Type of
Any errors which occur at the Matrix API level MUST return a "standard
error response". This is a JSON object which looks like:
{
"errcode": "<error code>",
"error": "<error message>"
}
```json
{
"errcode": "<error code>",
"error": "<error message>"
}
```
The `error` string will be a human-readable error message, usually a
sentence explaining what went wrong. The `errcode` string will be a

@ -248,18 +248,20 @@ and any query parameters if present, but should not include the leading
Step 1 sign JSON:
{
"method": "GET",
"uri": "/target",
"origin": "origin.hs.example.com",
"destination": "destination.hs.example.com",
"content": <request body>,
"signatures": {
"origin.hs.example.com": {
"ed25519:key1": "ABCDEF..."
}
```
{
"method": "GET",
"uri": "/target",
"origin": "origin.hs.example.com",
"destination": "destination.hs.example.com",
"content": <request body>,
"signatures": {
"origin.hs.example.com": {
"ed25519:key1": "ABCDEF..."
}
}
}
```
The server names in the JSON above are the server names for each
homeserver involved. Delegation from the [server name resolution
@ -277,31 +279,33 @@ Step 2 add Authorization header:
Example python code:
def authorization_headers(origin_name, origin_signing_key,
destination_name, request_method, request_target,
content=None):
request_json = {
"method": request_method,
"uri": request_target,
"origin": origin_name,
"destination": destination_name,
}
```py
def authorization_headers(origin_name, origin_signing_key,
destination_name, request_method, request_target,
content=None):
request_json = {
"method": request_method,
"uri": request_target,
"origin": origin_name,
"destination": destination_name,
}
if content is not None:
request_json["content"] = content
if content is not None:
request_json["content"] = content
signed_json = sign_json(request_json, origin_name, origin_signing_key)
signed_json = sign_json(request_json, origin_name, origin_signing_key)
authorization_headers = []
authorization_headers = []
for key, sig in signed_json["signatures"][origin_name].items():
authorization_headers.append(bytes(
"X-Matrix origin=%s,key=\"%s\",sig=\"%s\"" % (
origin_name, key, sig,
)
))
for key, sig in signed_json["signatures"][origin_name].items():
authorization_headers.append(bytes(
"X-Matrix origin=%s,key=\"%s\",sig=\"%s\"" % (
origin_name, key, sig,
)
))
return ("Authorization", authorization_headers)
return ("Authorization", authorization_headers)
```
### Response Authentication
@ -1121,49 +1125,51 @@ SHA-256.
### Example code
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_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_object = sign_json(stripped_object, signing_key, signing_name)
# Copy the signatures from the stripped event to the original event.
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 modified 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)
# 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)
return hashlib.sha256(event_json_bytes)
```py
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_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_object = sign_json(stripped_object, signing_key, signing_name)
# Copy the signatures from the stripped event to the original event.
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 modified 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)
# 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)
return hashlib.sha256(event_json_bytes)
```
## Security considerations

Loading…
Cancel
Save