keys.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. # Copyright 2015, 2016 OpenMarket Ltd
  2. # Copyright 2019 New Vector Ltd
  3. # Copyright 2020 The Matrix.org Foundation C.I.C.
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. import logging
  17. from typing import TYPE_CHECKING, Any, Optional, Tuple
  18. from synapse.api.errors import InvalidAPICallError, SynapseError
  19. from synapse.http.server import HttpServer
  20. from synapse.http.servlet import (
  21. RestServlet,
  22. parse_integer,
  23. parse_json_object_from_request,
  24. parse_string,
  25. )
  26. from synapse.http.site import SynapseRequest
  27. from synapse.logging.opentracing import log_kv, set_tag, trace
  28. from synapse.types import JsonDict, StreamToken
  29. from ._base import client_patterns, interactive_auth_handler
  30. if TYPE_CHECKING:
  31. from synapse.server import HomeServer
  32. logger = logging.getLogger(__name__)
  33. class KeyUploadServlet(RestServlet):
  34. """
  35. POST /keys/upload HTTP/1.1
  36. Content-Type: application/json
  37. {
  38. "device_keys": {
  39. "user_id": "<user_id>",
  40. "device_id": "<device_id>",
  41. "valid_until_ts": <millisecond_timestamp>,
  42. "algorithms": [
  43. "m.olm.curve25519-aes-sha2",
  44. ]
  45. "keys": {
  46. "<algorithm>:<device_id>": "<key_base64>",
  47. },
  48. "signatures:" {
  49. "<user_id>" {
  50. "<algorithm>:<device_id>": "<signature_base64>"
  51. } } },
  52. "one_time_keys": {
  53. "<algorithm>:<key_id>": "<key_base64>"
  54. },
  55. }
  56. """
  57. PATTERNS = client_patterns("/keys/upload(/(?P<device_id>[^/]+))?$")
  58. def __init__(self, hs: "HomeServer"):
  59. super().__init__()
  60. self.auth = hs.get_auth()
  61. self.e2e_keys_handler = hs.get_e2e_keys_handler()
  62. self.device_handler = hs.get_device_handler()
  63. @trace(opname="upload_keys")
  64. async def on_POST(
  65. self, request: SynapseRequest, device_id: Optional[str]
  66. ) -> Tuple[int, JsonDict]:
  67. requester = await self.auth.get_user_by_req(request, allow_guest=True)
  68. user_id = requester.user.to_string()
  69. body = parse_json_object_from_request(request)
  70. if device_id is not None:
  71. # Providing the device_id should only be done for setting keys
  72. # for dehydrated devices; however, we allow it for any device for
  73. # compatibility with older clients.
  74. if requester.device_id is not None and device_id != requester.device_id:
  75. dehydrated_device = await self.device_handler.get_dehydrated_device(
  76. user_id
  77. )
  78. if dehydrated_device is not None and device_id != dehydrated_device[0]:
  79. set_tag("error", True)
  80. log_kv(
  81. {
  82. "message": "Client uploading keys for a different device",
  83. "logged_in_id": requester.device_id,
  84. "key_being_uploaded": device_id,
  85. }
  86. )
  87. logger.warning(
  88. "Client uploading keys for a different device "
  89. "(logged in as %s, uploading for %s)",
  90. requester.device_id,
  91. device_id,
  92. )
  93. else:
  94. device_id = requester.device_id
  95. if device_id is None:
  96. raise SynapseError(
  97. 400, "To upload keys, you must pass device_id when authenticating"
  98. )
  99. result = await self.e2e_keys_handler.upload_keys_for_user(
  100. user_id, device_id, body
  101. )
  102. return 200, result
  103. class KeyQueryServlet(RestServlet):
  104. """
  105. POST /keys/query HTTP/1.1
  106. Content-Type: application/json
  107. {
  108. "device_keys": {
  109. "<user_id>": ["<device_id>"]
  110. } }
  111. HTTP/1.1 200 OK
  112. {
  113. "device_keys": {
  114. "<user_id>": {
  115. "<device_id>": {
  116. "user_id": "<user_id>", // Duplicated to be signed
  117. "device_id": "<device_id>", // Duplicated to be signed
  118. "valid_until_ts": <millisecond_timestamp>,
  119. "algorithms": [ // List of supported algorithms
  120. "m.olm.curve25519-aes-sha2",
  121. ],
  122. "keys": { // Must include a ed25519 signing key
  123. "<algorithm>:<key_id>": "<key_base64>",
  124. },
  125. "signatures:" {
  126. // Must be signed with device's ed25519 key
  127. "<user_id>/<device_id>": {
  128. "<algorithm>:<key_id>": "<signature_base64>"
  129. }
  130. // Must be signed by this server.
  131. "<server_name>": {
  132. "<algorithm>:<key_id>": "<signature_base64>"
  133. } } } } } }
  134. """
  135. PATTERNS = client_patterns("/keys/query$")
  136. def __init__(self, hs: "HomeServer"):
  137. super().__init__()
  138. self.auth = hs.get_auth()
  139. self.e2e_keys_handler = hs.get_e2e_keys_handler()
  140. async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
  141. requester = await self.auth.get_user_by_req(request, allow_guest=True)
  142. user_id = requester.user.to_string()
  143. device_id = requester.device_id
  144. timeout = parse_integer(request, "timeout", 10 * 1000)
  145. body = parse_json_object_from_request(request)
  146. device_keys = body.get("device_keys")
  147. if not isinstance(device_keys, dict):
  148. raise InvalidAPICallError("'device_keys' must be a JSON object")
  149. def is_list_of_strings(values: Any) -> bool:
  150. return isinstance(values, list) and all(isinstance(v, str) for v in values)
  151. if any(not is_list_of_strings(keys) for keys in device_keys.values()):
  152. raise InvalidAPICallError(
  153. "'device_keys' values must be a list of strings",
  154. )
  155. result = await self.e2e_keys_handler.query_devices(
  156. body, timeout, user_id, device_id
  157. )
  158. return 200, result
  159. class KeyChangesServlet(RestServlet):
  160. """Returns the list of changes of keys between two stream tokens (may return
  161. spurious extra results, since we currently ignore the `to` param).
  162. GET /keys/changes?from=...&to=...
  163. 200 OK
  164. { "changed": ["@foo:example.com"] }
  165. """
  166. PATTERNS = client_patterns("/keys/changes$")
  167. def __init__(self, hs: "HomeServer"):
  168. super().__init__()
  169. self.auth = hs.get_auth()
  170. self.device_handler = hs.get_device_handler()
  171. self.store = hs.get_datastores().main
  172. async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
  173. requester = await self.auth.get_user_by_req(request, allow_guest=True)
  174. from_token_string = parse_string(request, "from", required=True)
  175. set_tag("from", from_token_string)
  176. # We want to enforce they do pass us one, but we ignore it and return
  177. # changes after the "to" as well as before.
  178. set_tag("to", parse_string(request, "to"))
  179. from_token = await StreamToken.from_string(self.store, from_token_string)
  180. user_id = requester.user.to_string()
  181. results = await self.device_handler.get_user_ids_changed(user_id, from_token)
  182. return 200, results
  183. class OneTimeKeyServlet(RestServlet):
  184. """
  185. POST /keys/claim HTTP/1.1
  186. {
  187. "one_time_keys": {
  188. "<user_id>": {
  189. "<device_id>": "<algorithm>"
  190. } } }
  191. HTTP/1.1 200 OK
  192. {
  193. "one_time_keys": {
  194. "<user_id>": {
  195. "<device_id>": {
  196. "<algorithm>:<key_id>": "<key_base64>"
  197. } } } }
  198. """
  199. PATTERNS = client_patterns("/keys/claim$")
  200. def __init__(self, hs: "HomeServer"):
  201. super().__init__()
  202. self.auth = hs.get_auth()
  203. self.e2e_keys_handler = hs.get_e2e_keys_handler()
  204. async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
  205. await self.auth.get_user_by_req(request, allow_guest=True)
  206. timeout = parse_integer(request, "timeout", 10 * 1000)
  207. body = parse_json_object_from_request(request)
  208. result = await self.e2e_keys_handler.claim_one_time_keys(body, timeout)
  209. return 200, result
  210. class SigningKeyUploadServlet(RestServlet):
  211. """
  212. POST /keys/device_signing/upload HTTP/1.1
  213. Content-Type: application/json
  214. {
  215. }
  216. """
  217. PATTERNS = client_patterns("/keys/device_signing/upload$", releases=("v3",))
  218. def __init__(self, hs: "HomeServer"):
  219. super().__init__()
  220. self.hs = hs
  221. self.auth = hs.get_auth()
  222. self.e2e_keys_handler = hs.get_e2e_keys_handler()
  223. self.auth_handler = hs.get_auth_handler()
  224. @interactive_auth_handler
  225. async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
  226. requester = await self.auth.get_user_by_req(request)
  227. user_id = requester.user.to_string()
  228. body = parse_json_object_from_request(request)
  229. await self.auth_handler.validate_user_via_ui_auth(
  230. requester,
  231. request,
  232. body,
  233. "add a device signing key to your account",
  234. # Allow skipping of UI auth since this is frequently called directly
  235. # after login and it is silly to ask users to re-auth immediately.
  236. can_skip_ui_auth=True,
  237. )
  238. result = await self.e2e_keys_handler.upload_signing_keys_for_user(user_id, body)
  239. return 200, result
  240. class SignaturesUploadServlet(RestServlet):
  241. """
  242. POST /keys/signatures/upload HTTP/1.1
  243. Content-Type: application/json
  244. {
  245. "@alice:example.com": {
  246. "<device_id>": {
  247. "user_id": "<user_id>",
  248. "device_id": "<device_id>",
  249. "algorithms": [
  250. "m.olm.curve25519-aes-sha2",
  251. "m.megolm.v1.aes-sha2"
  252. ],
  253. "keys": {
  254. "<algorithm>:<device_id>": "<key_base64>",
  255. },
  256. "signatures": {
  257. "<signing_user_id>": {
  258. "<algorithm>:<signing_key_base64>": "<signature_base64>>"
  259. }
  260. }
  261. }
  262. }
  263. }
  264. """
  265. PATTERNS = client_patterns("/keys/signatures/upload$")
  266. def __init__(self, hs: "HomeServer"):
  267. super().__init__()
  268. self.auth = hs.get_auth()
  269. self.e2e_keys_handler = hs.get_e2e_keys_handler()
  270. async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
  271. requester = await self.auth.get_user_by_req(request, allow_guest=True)
  272. user_id = requester.user.to_string()
  273. body = parse_json_object_from_request(request)
  274. result = await self.e2e_keys_handler.upload_signatures_for_device_keys(
  275. user_id, body
  276. )
  277. return 200, result
  278. def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None:
  279. KeyUploadServlet(hs).register(http_server)
  280. KeyQueryServlet(hs).register(http_server)
  281. KeyChangesServlet(hs).register(http_server)
  282. OneTimeKeyServlet(hs).register(http_server)
  283. SigningKeyUploadServlet(hs).register(http_server)
  284. SignaturesUploadServlet(hs).register(http_server)