MatrixSynapse/synapse/rest/client/v2_alpha/room_keys.py

333 lines
11 KiB
Python
Raw Normal View History

2017-12-05 02:29:25 +01:00
# -*- coding: utf-8 -*-
# Copyright 2017 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.
import logging
from twisted.internet import defer
2017-12-06 10:02:49 +01:00
from synapse.api.errors import SynapseError
2017-12-05 02:29:25 +01:00
from synapse.http.servlet import (
2017-12-05 22:44:25 +01:00
RestServlet, parse_json_object_from_request
2017-12-05 02:29:25 +01:00
)
from ._base import client_v2_patterns
logger = logging.getLogger(__name__)
2017-12-05 18:54:48 +01:00
class RoomKeysServlet(RestServlet):
2017-12-05 22:44:25 +01:00
PATTERNS = client_v2_patterns(
"/room_keys/keys(/(?P<room_id>[^/]+))?(/(?P<session_id>[^/]+))?$"
)
2017-12-05 02:29:25 +01:00
def __init__(self, hs):
"""
Args:
hs (synapse.server.HomeServer): server
"""
2017-12-05 22:44:25 +01:00
super(RoomKeysServlet, self).__init__()
2017-12-05 02:29:25 +01:00
self.auth = hs.get_auth()
self.e2e_room_keys_handler = hs.get_e2e_room_keys_handler()
@defer.inlineCallbacks
2017-12-05 18:54:48 +01:00
def on_PUT(self, request, room_id, session_id):
2017-12-06 00:06:43 +01:00
"""
Uploads one or more encrypted E2E room keys for backup purposes.
room_id: the ID of the room the keys are for (optional)
session_id: the ID for the E2E room keys for the room (optional)
version: the version of the user's backup which this data is for.
the version must already have been created via the /change_secret API.
Each session has:
* first_message_index: a numeric index indicating the oldest message
encrypted by this session.
* forwarded_count: how many times the uploading client claims this key
has been shared (forwarded)
* is_verified: whether the client that uploaded the keys claims they
were sent by a device which they've verified
* session_data: base64-encrypted data describing the session.
Returns 200 OK on success with body {}
The API is designed to be otherwise agnostic to the room_key encryption
algorithm being used. Sessions are merged with existing ones in the
backup using the heuristics:
* is_verified sessions always win over unverified sessions
* older first_message_index always win over newer sessions
* lower forwarded_count always wins over higher forwarded_count
We trust the clients not to lie and corrupt their own backups.
It also means that if your access_token is stolen, the attacker could
delete your backup.
2017-12-06 00:06:43 +01:00
POST /room_keys/keys/!abc:matrix.org/c0ff33?version=1 HTTP/1.1
Content-Type: application/json
{
"first_message_index": 1,
"forwarded_count": 1,
"is_verified": false,
"session_data": "SSBBTSBBIEZJU0gK"
}
Or...
POST /room_keys/keys/!abc:matrix.org?version=1 HTTP/1.1
Content-Type: application/json
{
"sessions": {
"c0ff33": {
"first_message_index": 1,
"forwarded_count": 1,
"is_verified": false,
"session_data": "SSBBTSBBIEZJU0gK"
}
}
}
Or...
POST /room_keys/keys?version=1 HTTP/1.1
Content-Type: application/json
{
"rooms": {
"!abc:matrix.org": {
"sessions": {
"c0ff33": {
"first_message_index": 1,
"forwarded_count": 1,
"is_verified": false,
"session_data": "SSBBTSBBIEZJU0gK"
}
}
}
}
}
"""
2017-12-05 18:54:48 +01:00
requester = yield self.auth.get_user_by_req(request, allow_guest=False)
2017-12-05 02:29:25 +01:00
user_id = requester.user.to_string()
body = parse_json_object_from_request(request)
2017-12-05 22:44:25 +01:00
version = request.args.get("version")[0]
2017-12-05 18:54:48 +01:00
if session_id:
2017-12-05 22:44:25 +01:00
body = {
"sessions": {
session_id: body
}
}
2017-12-05 18:54:48 +01:00
if room_id:
2017-12-05 22:44:25 +01:00
body = {
"rooms": {
room_id: body
}
}
2017-12-05 02:29:25 +01:00
2017-12-05 22:44:25 +01:00
yield self.e2e_room_keys_handler.upload_room_keys(
2017-12-05 02:29:25 +01:00
user_id, version, body
)
2017-12-05 22:44:25 +01:00
defer.returnValue((200, {}))
2017-12-05 02:29:25 +01:00
2017-12-05 18:54:48 +01:00
@defer.inlineCallbacks
def on_GET(self, request, room_id, session_id):
2017-12-06 00:06:43 +01:00
"""
Retrieves one or more encrypted E2E room keys for backup purposes.
Symmetric with the PUT version of the API.
room_id: the ID of the room to retrieve the keys for (optional)
session_id: the ID for the E2E room keys to retrieve the keys for (optional)
version: the version of the user's backup which this data is for.
the version must already have been created via the /change_secret API.
Returns as follows:
GET /room_keys/keys/!abc:matrix.org/c0ff33?version=1 HTTP/1.1
{
"first_message_index": 1,
"forwarded_count": 1,
"is_verified": false,
"session_data": "SSBBTSBBIEZJU0gK"
}
Or...
GET /room_keys/keys/!abc:matrix.org?version=1 HTTP/1.1
{
"sessions": {
"c0ff33": {
"first_message_index": 1,
"forwarded_count": 1,
"is_verified": false,
"session_data": "SSBBTSBBIEZJU0gK"
}
}
}
Or...
GET /room_keys/keys?version=1 HTTP/1.1
{
"rooms": {
"!abc:matrix.org": {
"sessions": {
"c0ff33": {
"first_message_index": 1,
"forwarded_count": 1,
"is_verified": false,
"session_data": "SSBBTSBBIEZJU0gK"
}
}
}
}
}
"""
2017-12-05 18:54:48 +01:00
requester = yield self.auth.get_user_by_req(request, allow_guest=False)
user_id = requester.user.to_string()
2017-12-05 22:44:25 +01:00
version = request.args.get("version")[0]
2017-12-05 18:54:48 +01:00
room_keys = yield self.e2e_room_keys_handler.get_room_keys(
user_id, version, room_id, session_id
)
defer.returnValue((200, room_keys))
@defer.inlineCallbacks
def on_DELETE(self, request, room_id, session_id):
2017-12-06 00:06:43 +01:00
"""
Deletes one or more encrypted E2E room keys for a user for backup purposes.
DELETE /room_keys/keys/!abc:matrix.org/c0ff33?version=1
HTTP/1.1 200 OK
{}
2017-12-06 00:06:43 +01:00
room_id: the ID of the room whose keys to delete (optional)
session_id: the ID for the E2E session to delete (optional)
version: the version of the user's backup which this data is for.
the version must already have been created via the /change_secret API.
"""
2017-12-05 18:54:48 +01:00
requester = yield self.auth.get_user_by_req(request, allow_guest=False)
user_id = requester.user.to_string()
2017-12-05 22:44:25 +01:00
version = request.args.get("version")[0]
2017-12-05 18:54:48 +01:00
yield self.e2e_room_keys_handler.delete_room_keys(
user_id, version, room_id, session_id
)
defer.returnValue((200, {}))
2017-12-05 02:29:25 +01:00
class RoomKeysVersionServlet(RestServlet):
PATTERNS = client_v2_patterns(
"/room_keys/version(/(?P<version>[^/]+))?$"
)
def __init__(self, hs):
"""
Args:
hs (synapse.server.HomeServer): server
"""
super(RoomKeysVersionServlet, self).__init__()
self.auth = hs.get_auth()
self.e2e_room_keys_handler = hs.get_e2e_room_keys_handler()
@defer.inlineCallbacks
def on_POST(self, request, version):
"""
Create a new backup version for this user's room_keys with the given
info. The version is allocated by the server and returned to the user
in the response. This API is intended to be used whenever the user
changes the encryption key for their backups, ensuring that backups
encrypted with different keys don't collide.
The algorithm passed in the version info is a reverse-DNS namespaced
identifier to describe the format of the encrypted backupped keys.
The auth_data is { user_id: "user_id", nonce: <random string> }
encrypted using the algorithm and current encryption key described above.
POST /room_keys/version
Content-Type: application/json
{
"algorithm": "m.megolm_backup.v1",
"auth_data": "dGhpcyBzaG91bGQgYWN0dWFsbHkgYmUgZW5jcnlwdGVkIGpzb24K"
}
HTTP/1.1 200 OK
Content-Type: application/json
{
"version": 12345
}
"""
2017-12-06 10:02:49 +01:00
if version:
raise SynapseError(405, "Cannot POST to a specific version")
requester = yield self.auth.get_user_by_req(request, allow_guest=False)
user_id = requester.user.to_string()
info = parse_json_object_from_request(request)
new_version = yield self.e2e_room_keys_handler.create_version(
2017-12-06 10:02:49 +01:00
user_id, info
)
defer.returnValue((200, {"version": new_version}))
2017-12-06 10:02:49 +01:00
# we deliberately don't have a PUT /version, as these things really should
# be immutable to avoid people footgunning
@defer.inlineCallbacks
def on_GET(self, request, version):
"""
Retrieve the version information about a given version of the user's
room_keys backup.
GET /room_keys/version/12345 HTTP/1.1
{
"algorithm": "m.megolm_backup.v1",
"auth_data": "dGhpcyBzaG91bGQgYWN0dWFsbHkgYmUgZW5jcnlwdGVkIGpzb24K"
}
"""
requester = yield self.auth.get_user_by_req(request, allow_guest=False)
user_id = requester.user.to_string()
info = yield self.e2e_room_keys_handler.get_version_info(
user_id, version
)
defer.returnValue((200, info))
@defer.inlineCallbacks
def on_DELETE(self, request, version):
"""
Delete the information about a given version of the user's
room_keys backup. Doesn't delete the actual room data.
DELETE /room_keys/version/12345 HTTP/1.1
HTTP/1.1 200 OK
{}
"""
requester = yield self.auth.get_user_by_req(request, allow_guest=False)
user_id = requester.user.to_string()
yield self.e2e_room_keys_handler.delete_version(
user_id, version
)
defer.returnValue((200, {}))
2017-12-05 02:29:25 +01:00
def register_servlets(hs, http_server):
2017-12-05 18:54:48 +01:00
RoomKeysServlet(hs).register(http_server)
RoomKeysVersionServlet(hs).register(http_server)