You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

440 lines
16 KiB

  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. import urllib.parse
  15. from http import HTTPStatus
  16. from unittest.mock import patch
  17. from signedjson.key import (
  18. encode_verify_key_base64,
  19. generate_signing_key,
  20. get_verify_key,
  21. )
  22. from signedjson.sign import sign_json
  23. from synapse.api.errors import Codes
  24. from synapse.rest import admin
  25. from synapse.rest.client import keys, login
  26. from synapse.types import JsonDict, Requester, create_requester
  27. from tests import unittest
  28. from tests.http.server._base import make_request_with_cancellation_test
  29. from tests.unittest import override_config
  30. from tests.utils import HAS_AUTHLIB
  31. class KeyQueryTestCase(unittest.HomeserverTestCase):
  32. servlets = [
  33. keys.register_servlets,
  34. admin.register_servlets_for_client_rest_resource,
  35. login.register_servlets,
  36. ]
  37. def test_rejects_device_id_ice_key_outside_of_list(self) -> None:
  38. self.register_user("alice", "wonderland")
  39. alice_token = self.login("alice", "wonderland")
  40. bob = self.register_user("bob", "uncle")
  41. channel = self.make_request(
  42. "POST",
  43. "/_matrix/client/r0/keys/query",
  44. {
  45. "device_keys": {
  46. bob: "device_id1",
  47. },
  48. },
  49. alice_token,
  50. )
  51. self.assertEqual(channel.code, HTTPStatus.BAD_REQUEST, channel.result)
  52. self.assertEqual(
  53. channel.json_body["errcode"],
  54. Codes.BAD_JSON,
  55. channel.result,
  56. )
  57. def test_rejects_device_key_given_as_map_to_bool(self) -> None:
  58. self.register_user("alice", "wonderland")
  59. alice_token = self.login("alice", "wonderland")
  60. bob = self.register_user("bob", "uncle")
  61. channel = self.make_request(
  62. "POST",
  63. "/_matrix/client/r0/keys/query",
  64. {
  65. "device_keys": {
  66. bob: {
  67. "device_id1": True,
  68. },
  69. },
  70. },
  71. alice_token,
  72. )
  73. self.assertEqual(channel.code, HTTPStatus.BAD_REQUEST, channel.result)
  74. self.assertEqual(
  75. channel.json_body["errcode"],
  76. Codes.BAD_JSON,
  77. channel.result,
  78. )
  79. def test_requires_device_key(self) -> None:
  80. """`device_keys` is required. We should complain if it's missing."""
  81. self.register_user("alice", "wonderland")
  82. alice_token = self.login("alice", "wonderland")
  83. channel = self.make_request(
  84. "POST",
  85. "/_matrix/client/r0/keys/query",
  86. {},
  87. alice_token,
  88. )
  89. self.assertEqual(channel.code, HTTPStatus.BAD_REQUEST, channel.result)
  90. self.assertEqual(
  91. channel.json_body["errcode"],
  92. Codes.BAD_JSON,
  93. channel.result,
  94. )
  95. def test_key_query_cancellation(self) -> None:
  96. """
  97. Tests that /keys/query is cancellable and does not swallow the
  98. CancelledError.
  99. """
  100. self.register_user("alice", "wonderland")
  101. alice_token = self.login("alice", "wonderland")
  102. bob = self.register_user("bob", "uncle")
  103. channel = make_request_with_cancellation_test(
  104. "test_key_query_cancellation",
  105. self.reactor,
  106. self.site,
  107. "POST",
  108. "/_matrix/client/r0/keys/query",
  109. {
  110. "device_keys": {
  111. # Empty list means we request keys for all bob's devices
  112. bob: [],
  113. },
  114. },
  115. token=alice_token,
  116. )
  117. self.assertEqual(200, channel.code, msg=channel.result["body"])
  118. self.assertIn(bob, channel.json_body["device_keys"])
  119. def make_device_keys(self, user_id: str, device_id: str) -> JsonDict:
  120. # We only generate a master key to simplify the test.
  121. master_signing_key = generate_signing_key(device_id)
  122. master_verify_key = encode_verify_key_base64(get_verify_key(master_signing_key))
  123. return {
  124. "master_key": sign_json(
  125. {
  126. "user_id": user_id,
  127. "usage": ["master"],
  128. "keys": {"ed25519:" + master_verify_key: master_verify_key},
  129. },
  130. user_id,
  131. master_signing_key,
  132. ),
  133. }
  134. def test_device_signing_with_uia(self) -> None:
  135. """Device signing key upload requires UIA."""
  136. password = "wonderland"
  137. device_id = "ABCDEFGHI"
  138. alice_id = self.register_user("alice", password)
  139. alice_token = self.login("alice", password, device_id=device_id)
  140. content = self.make_device_keys(alice_id, device_id)
  141. channel = self.make_request(
  142. "POST",
  143. "/_matrix/client/v3/keys/device_signing/upload",
  144. content,
  145. alice_token,
  146. )
  147. self.assertEqual(channel.code, HTTPStatus.UNAUTHORIZED, channel.result)
  148. # Grab the session
  149. session = channel.json_body["session"]
  150. # Ensure that flows are what is expected.
  151. self.assertIn({"stages": ["m.login.password"]}, channel.json_body["flows"])
  152. # add UI auth
  153. content["auth"] = {
  154. "type": "m.login.password",
  155. "identifier": {"type": "m.id.user", "user": alice_id},
  156. "password": password,
  157. "session": session,
  158. }
  159. channel = self.make_request(
  160. "POST",
  161. "/_matrix/client/v3/keys/device_signing/upload",
  162. content,
  163. alice_token,
  164. )
  165. self.assertEqual(channel.code, HTTPStatus.OK, channel.result)
  166. @override_config({"ui_auth": {"session_timeout": "15m"}})
  167. def test_device_signing_with_uia_session_timeout(self) -> None:
  168. """Device signing key upload requires UIA buy passes with grace period."""
  169. password = "wonderland"
  170. device_id = "ABCDEFGHI"
  171. alice_id = self.register_user("alice", password)
  172. alice_token = self.login("alice", password, device_id=device_id)
  173. content = self.make_device_keys(alice_id, device_id)
  174. channel = self.make_request(
  175. "POST",
  176. "/_matrix/client/v3/keys/device_signing/upload",
  177. content,
  178. alice_token,
  179. )
  180. self.assertEqual(channel.code, HTTPStatus.OK, channel.result)
  181. @override_config(
  182. {
  183. "experimental_features": {"msc3967_enabled": True},
  184. "ui_auth": {"session_timeout": "15s"},
  185. }
  186. )
  187. def test_device_signing_with_msc3967(self) -> None:
  188. """Device signing key follows MSC3967 behaviour when enabled."""
  189. password = "wonderland"
  190. device_id = "ABCDEFGHI"
  191. alice_id = self.register_user("alice", password)
  192. alice_token = self.login("alice", password, device_id=device_id)
  193. keys1 = self.make_device_keys(alice_id, device_id)
  194. # Initial request should succeed as no existing keys are present.
  195. channel = self.make_request(
  196. "POST",
  197. "/_matrix/client/v3/keys/device_signing/upload",
  198. keys1,
  199. alice_token,
  200. )
  201. self.assertEqual(channel.code, HTTPStatus.OK, channel.result)
  202. keys2 = self.make_device_keys(alice_id, device_id)
  203. # Subsequent request should require UIA as keys already exist even though session_timeout is set.
  204. channel = self.make_request(
  205. "POST",
  206. "/_matrix/client/v3/keys/device_signing/upload",
  207. keys2,
  208. alice_token,
  209. )
  210. self.assertEqual(channel.code, HTTPStatus.UNAUTHORIZED, channel.result)
  211. # Grab the session
  212. session = channel.json_body["session"]
  213. # Ensure that flows are what is expected.
  214. self.assertIn({"stages": ["m.login.password"]}, channel.json_body["flows"])
  215. # add UI auth
  216. keys2["auth"] = {
  217. "type": "m.login.password",
  218. "identifier": {"type": "m.id.user", "user": alice_id},
  219. "password": password,
  220. "session": session,
  221. }
  222. # Request should complete
  223. channel = self.make_request(
  224. "POST",
  225. "/_matrix/client/v3/keys/device_signing/upload",
  226. keys2,
  227. alice_token,
  228. )
  229. self.assertEqual(channel.code, HTTPStatus.OK, channel.result)
  230. class SigningKeyUploadServletTestCase(unittest.HomeserverTestCase):
  231. servlets = [
  232. admin.register_servlets,
  233. keys.register_servlets,
  234. ]
  235. OIDC_ADMIN_TOKEN = "_oidc_admin_token"
  236. @unittest.skip_unless(HAS_AUTHLIB, "requires authlib")
  237. @override_config(
  238. {
  239. "enable_registration": False,
  240. "experimental_features": {
  241. "msc3861": {
  242. "enabled": True,
  243. "issuer": "https://issuer",
  244. "account_management_url": "https://my-account.issuer",
  245. "client_id": "id",
  246. "client_auth_method": "client_secret_post",
  247. "client_secret": "secret",
  248. "admin_token": OIDC_ADMIN_TOKEN,
  249. },
  250. },
  251. }
  252. )
  253. def test_master_cross_signing_key_replacement_msc3861(self) -> None:
  254. # Provision a user like MAS would, cribbing from
  255. # https://github.com/matrix-org/matrix-authentication-service/blob/08d46a79a4adb22819ac9d55e15f8375dfe2c5c7/crates/matrix-synapse/src/lib.rs#L224-L229
  256. alice = "@alice:test"
  257. channel = self.make_request(
  258. "PUT",
  259. f"/_synapse/admin/v2/users/{urllib.parse.quote(alice)}",
  260. access_token=self.OIDC_ADMIN_TOKEN,
  261. content={},
  262. )
  263. self.assertEqual(channel.code, HTTPStatus.CREATED, channel.json_body)
  264. # Provision a device like MAS would, cribbing from
  265. # https://github.com/matrix-org/matrix-authentication-service/blob/08d46a79a4adb22819ac9d55e15f8375dfe2c5c7/crates/matrix-synapse/src/lib.rs#L260-L262
  266. alice_device = "alice_device"
  267. channel = self.make_request(
  268. "POST",
  269. f"/_synapse/admin/v2/users/{urllib.parse.quote(alice)}/devices",
  270. access_token=self.OIDC_ADMIN_TOKEN,
  271. content={"device_id": alice_device},
  272. )
  273. self.assertEqual(channel.code, HTTPStatus.CREATED, channel.json_body)
  274. # Prepare a mock MAS access token.
  275. alice_token = "alice_token_1234_oidcwhatyoudidthere"
  276. async def mocked_get_user_by_access_token(
  277. token: str, allow_expired: bool = False
  278. ) -> Requester:
  279. self.assertEqual(token, alice_token)
  280. return create_requester(
  281. user_id=alice,
  282. device_id=alice_device,
  283. scope=[],
  284. is_guest=False,
  285. )
  286. patch_get_user_by_access_token = patch.object(
  287. self.hs.get_auth(),
  288. "get_user_by_access_token",
  289. wraps=mocked_get_user_by_access_token,
  290. )
  291. # Copied from E2eKeysHandlerTestCase
  292. master_pubkey = "nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk"
  293. master_pubkey2 = "fHZ3NPiKxoLQm5OoZbKa99SYxprOjNs4TwJUKP+twCM"
  294. master_pubkey3 = "85T7JXPFBAySB/jwby4S3lBPTqY3+Zg53nYuGmu1ggY"
  295. master_key: JsonDict = {
  296. "user_id": alice,
  297. "usage": ["master"],
  298. "keys": {"ed25519:" + master_pubkey: master_pubkey},
  299. }
  300. master_key2: JsonDict = {
  301. "user_id": alice,
  302. "usage": ["master"],
  303. "keys": {"ed25519:" + master_pubkey2: master_pubkey2},
  304. }
  305. master_key3: JsonDict = {
  306. "user_id": alice,
  307. "usage": ["master"],
  308. "keys": {"ed25519:" + master_pubkey3: master_pubkey3},
  309. }
  310. with patch_get_user_by_access_token:
  311. # Upload an initial cross-signing key.
  312. channel = self.make_request(
  313. "POST",
  314. "/_matrix/client/v3/keys/device_signing/upload",
  315. access_token=alice_token,
  316. content={
  317. "master_key": master_key,
  318. },
  319. )
  320. self.assertEqual(channel.code, HTTPStatus.OK, channel.json_body)
  321. # Should not be able to upload another master key.
  322. channel = self.make_request(
  323. "POST",
  324. "/_matrix/client/v3/keys/device_signing/upload",
  325. access_token=alice_token,
  326. content={
  327. "master_key": master_key2,
  328. },
  329. )
  330. self.assertEqual(
  331. channel.code, HTTPStatus.NOT_IMPLEMENTED, channel.json_body
  332. )
  333. # Pretend that MAS did UIA and allowed us to replace the master key.
  334. channel = self.make_request(
  335. "POST",
  336. f"/_synapse/admin/v1/users/{urllib.parse.quote(alice)}/_allow_cross_signing_replacement_without_uia",
  337. access_token=self.OIDC_ADMIN_TOKEN,
  338. )
  339. self.assertEqual(HTTPStatus.OK, channel.code, msg=channel.json_body)
  340. with patch_get_user_by_access_token:
  341. # Should now be able to upload master key2.
  342. channel = self.make_request(
  343. "POST",
  344. "/_matrix/client/v3/keys/device_signing/upload",
  345. access_token=alice_token,
  346. content={
  347. "master_key": master_key2,
  348. },
  349. )
  350. self.assertEqual(channel.code, HTTPStatus.OK, channel.json_body)
  351. # Even though we're still in the grace period, we shouldn't be able to
  352. # upload master key 3 immediately after uploading key 2.
  353. channel = self.make_request(
  354. "POST",
  355. "/_matrix/client/v3/keys/device_signing/upload",
  356. access_token=alice_token,
  357. content={
  358. "master_key": master_key3,
  359. },
  360. )
  361. self.assertEqual(
  362. channel.code, HTTPStatus.NOT_IMPLEMENTED, channel.json_body
  363. )
  364. # Pretend that MAS did UIA and allowed us to replace the master key.
  365. channel = self.make_request(
  366. "POST",
  367. f"/_synapse/admin/v1/users/{urllib.parse.quote(alice)}/_allow_cross_signing_replacement_without_uia",
  368. access_token=self.OIDC_ADMIN_TOKEN,
  369. )
  370. self.assertEqual(HTTPStatus.OK, channel.code, msg=channel.json_body)
  371. timestamp_ms = channel.json_body["updatable_without_uia_before_ms"]
  372. # Advance to 1 second after the replacement period ends.
  373. self.reactor.advance(timestamp_ms - self.clock.time_msec() + 1000)
  374. with patch_get_user_by_access_token:
  375. # We should not be able to upload master key3 because the replacement has
  376. # expired.
  377. channel = self.make_request(
  378. "POST",
  379. "/_matrix/client/v3/keys/device_signing/upload",
  380. access_token=alice_token,
  381. content={
  382. "master_key": master_key3,
  383. },
  384. )
  385. self.assertEqual(
  386. channel.code, HTTPStatus.NOT_IMPLEMENTED, channel.json_body
  387. )