1
0

test_keys.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. # Copyright 2021 The Matrix.org Foundation C.I.C.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License
  14. from http import HTTPStatus
  15. from signedjson.key import (
  16. encode_verify_key_base64,
  17. generate_signing_key,
  18. get_verify_key,
  19. )
  20. from signedjson.sign import sign_json
  21. from synapse.api.errors import Codes
  22. from synapse.rest import admin
  23. from synapse.rest.client import keys, login
  24. from synapse.types import JsonDict
  25. from tests import unittest
  26. from tests.http.server._base import make_request_with_cancellation_test
  27. from tests.unittest import override_config
  28. class KeyQueryTestCase(unittest.HomeserverTestCase):
  29. servlets = [
  30. keys.register_servlets,
  31. admin.register_servlets_for_client_rest_resource,
  32. login.register_servlets,
  33. ]
  34. def test_rejects_device_id_ice_key_outside_of_list(self) -> None:
  35. self.register_user("alice", "wonderland")
  36. alice_token = self.login("alice", "wonderland")
  37. bob = self.register_user("bob", "uncle")
  38. channel = self.make_request(
  39. "POST",
  40. "/_matrix/client/r0/keys/query",
  41. {
  42. "device_keys": {
  43. bob: "device_id1",
  44. },
  45. },
  46. alice_token,
  47. )
  48. self.assertEqual(channel.code, HTTPStatus.BAD_REQUEST, channel.result)
  49. self.assertEqual(
  50. channel.json_body["errcode"],
  51. Codes.BAD_JSON,
  52. channel.result,
  53. )
  54. def test_rejects_device_key_given_as_map_to_bool(self) -> None:
  55. self.register_user("alice", "wonderland")
  56. alice_token = self.login("alice", "wonderland")
  57. bob = self.register_user("bob", "uncle")
  58. channel = self.make_request(
  59. "POST",
  60. "/_matrix/client/r0/keys/query",
  61. {
  62. "device_keys": {
  63. bob: {
  64. "device_id1": True,
  65. },
  66. },
  67. },
  68. alice_token,
  69. )
  70. self.assertEqual(channel.code, HTTPStatus.BAD_REQUEST, channel.result)
  71. self.assertEqual(
  72. channel.json_body["errcode"],
  73. Codes.BAD_JSON,
  74. channel.result,
  75. )
  76. def test_requires_device_key(self) -> None:
  77. """`device_keys` is required. We should complain if it's missing."""
  78. self.register_user("alice", "wonderland")
  79. alice_token = self.login("alice", "wonderland")
  80. channel = self.make_request(
  81. "POST",
  82. "/_matrix/client/r0/keys/query",
  83. {},
  84. alice_token,
  85. )
  86. self.assertEqual(channel.code, HTTPStatus.BAD_REQUEST, channel.result)
  87. self.assertEqual(
  88. channel.json_body["errcode"],
  89. Codes.BAD_JSON,
  90. channel.result,
  91. )
  92. def test_key_query_cancellation(self) -> None:
  93. """
  94. Tests that /keys/query is cancellable and does not swallow the
  95. CancelledError.
  96. """
  97. self.register_user("alice", "wonderland")
  98. alice_token = self.login("alice", "wonderland")
  99. bob = self.register_user("bob", "uncle")
  100. channel = make_request_with_cancellation_test(
  101. "test_key_query_cancellation",
  102. self.reactor,
  103. self.site,
  104. "POST",
  105. "/_matrix/client/r0/keys/query",
  106. {
  107. "device_keys": {
  108. # Empty list means we request keys for all bob's devices
  109. bob: [],
  110. },
  111. },
  112. token=alice_token,
  113. )
  114. self.assertEqual(200, channel.code, msg=channel.result["body"])
  115. self.assertIn(bob, channel.json_body["device_keys"])
  116. def make_device_keys(self, user_id: str, device_id: str) -> JsonDict:
  117. # We only generate a master key to simplify the test.
  118. master_signing_key = generate_signing_key(device_id)
  119. master_verify_key = encode_verify_key_base64(get_verify_key(master_signing_key))
  120. return {
  121. "master_key": sign_json(
  122. {
  123. "user_id": user_id,
  124. "usage": ["master"],
  125. "keys": {"ed25519:" + master_verify_key: master_verify_key},
  126. },
  127. user_id,
  128. master_signing_key,
  129. ),
  130. }
  131. def test_device_signing_with_uia(self) -> None:
  132. """Device signing key upload requires UIA."""
  133. password = "wonderland"
  134. device_id = "ABCDEFGHI"
  135. alice_id = self.register_user("alice", password)
  136. alice_token = self.login("alice", password, device_id=device_id)
  137. content = self.make_device_keys(alice_id, device_id)
  138. channel = self.make_request(
  139. "POST",
  140. "/_matrix/client/v3/keys/device_signing/upload",
  141. content,
  142. alice_token,
  143. )
  144. self.assertEqual(channel.code, HTTPStatus.UNAUTHORIZED, channel.result)
  145. # Grab the session
  146. session = channel.json_body["session"]
  147. # Ensure that flows are what is expected.
  148. self.assertIn({"stages": ["m.login.password"]}, channel.json_body["flows"])
  149. # add UI auth
  150. content["auth"] = {
  151. "type": "m.login.password",
  152. "identifier": {"type": "m.id.user", "user": alice_id},
  153. "password": password,
  154. "session": session,
  155. }
  156. channel = self.make_request(
  157. "POST",
  158. "/_matrix/client/v3/keys/device_signing/upload",
  159. content,
  160. alice_token,
  161. )
  162. self.assertEqual(channel.code, HTTPStatus.OK, channel.result)
  163. @override_config({"ui_auth": {"session_timeout": "15m"}})
  164. def test_device_signing_with_uia_session_timeout(self) -> None:
  165. """Device signing key upload requires UIA buy passes with grace period."""
  166. password = "wonderland"
  167. device_id = "ABCDEFGHI"
  168. alice_id = self.register_user("alice", password)
  169. alice_token = self.login("alice", password, device_id=device_id)
  170. content = self.make_device_keys(alice_id, device_id)
  171. channel = self.make_request(
  172. "POST",
  173. "/_matrix/client/v3/keys/device_signing/upload",
  174. content,
  175. alice_token,
  176. )
  177. self.assertEqual(channel.code, HTTPStatus.OK, channel.result)
  178. @override_config(
  179. {
  180. "experimental_features": {"msc3967_enabled": True},
  181. "ui_auth": {"session_timeout": "15s"},
  182. }
  183. )
  184. def test_device_signing_with_msc3967(self) -> None:
  185. """Device signing key follows MSC3967 behaviour when enabled."""
  186. password = "wonderland"
  187. device_id = "ABCDEFGHI"
  188. alice_id = self.register_user("alice", password)
  189. alice_token = self.login("alice", password, device_id=device_id)
  190. keys1 = self.make_device_keys(alice_id, device_id)
  191. # Initial request should succeed as no existing keys are present.
  192. channel = self.make_request(
  193. "POST",
  194. "/_matrix/client/v3/keys/device_signing/upload",
  195. keys1,
  196. alice_token,
  197. )
  198. self.assertEqual(channel.code, HTTPStatus.OK, channel.result)
  199. keys2 = self.make_device_keys(alice_id, device_id)
  200. # Subsequent request should require UIA as keys already exist even though session_timeout is set.
  201. channel = self.make_request(
  202. "POST",
  203. "/_matrix/client/v3/keys/device_signing/upload",
  204. keys2,
  205. alice_token,
  206. )
  207. self.assertEqual(channel.code, HTTPStatus.UNAUTHORIZED, channel.result)
  208. # Grab the session
  209. session = channel.json_body["session"]
  210. # Ensure that flows are what is expected.
  211. self.assertIn({"stages": ["m.login.password"]}, channel.json_body["flows"])
  212. # add UI auth
  213. keys2["auth"] = {
  214. "type": "m.login.password",
  215. "identifier": {"type": "m.id.user", "user": alice_id},
  216. "password": password,
  217. "session": session,
  218. }
  219. # Request should complete
  220. channel = self.make_request(
  221. "POST",
  222. "/_matrix/client/v3/keys/device_signing/upload",
  223. keys2,
  224. alice_token,
  225. )
  226. self.assertEqual(channel.code, HTTPStatus.OK, channel.result)