test_e2e_keys.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2016 OpenMarket Ltd
  3. # Copyright 2019 New Vector Ltd
  4. # Copyright 2019 The Matrix.org Foundation C.I.C.
  5. #
  6. # Licensed under the Apache License, Version 2.0 (the "License");
  7. # you may not use this file except in compliance with the License.
  8. # You may obtain a copy of the License at
  9. #
  10. # http://www.apache.org/licenses/LICENSE-2.0
  11. #
  12. # Unless required by applicable law or agreed to in writing, software
  13. # distributed under the License is distributed on an "AS IS" BASIS,
  14. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. # See the License for the specific language governing permissions and
  16. # limitations under the License.
  17. import mock
  18. import signedjson.key as key
  19. import signedjson.sign as sign
  20. from twisted.internet import defer
  21. import synapse.handlers.e2e_keys
  22. import synapse.storage
  23. from synapse.api import errors
  24. from tests import unittest, utils
  25. class E2eKeysHandlerTestCase(unittest.TestCase):
  26. def __init__(self, *args, **kwargs):
  27. super(E2eKeysHandlerTestCase, self).__init__(*args, **kwargs)
  28. self.hs = None # type: synapse.server.HomeServer
  29. self.handler = None # type: synapse.handlers.e2e_keys.E2eKeysHandler
  30. @defer.inlineCallbacks
  31. def setUp(self):
  32. self.hs = yield utils.setup_test_homeserver(
  33. self.addCleanup, handlers=None, federation_client=mock.Mock()
  34. )
  35. self.handler = synapse.handlers.e2e_keys.E2eKeysHandler(self.hs)
  36. @defer.inlineCallbacks
  37. def test_query_local_devices_no_devices(self):
  38. """If the user has no devices, we expect an empty list.
  39. """
  40. local_user = "@boris:" + self.hs.hostname
  41. res = yield self.handler.query_local_devices({local_user: None})
  42. self.assertDictEqual(res, {local_user: {}})
  43. @defer.inlineCallbacks
  44. def test_reupload_one_time_keys(self):
  45. """we should be able to re-upload the same keys"""
  46. local_user = "@boris:" + self.hs.hostname
  47. device_id = "xyz"
  48. keys = {
  49. "alg1:k1": "key1",
  50. "alg2:k2": {"key": "key2", "signatures": {"k1": "sig1"}},
  51. "alg2:k3": {"key": "key3"},
  52. }
  53. res = yield self.handler.upload_keys_for_user(
  54. local_user, device_id, {"one_time_keys": keys}
  55. )
  56. self.assertDictEqual(res, {"one_time_key_counts": {"alg1": 1, "alg2": 2}})
  57. # we should be able to change the signature without a problem
  58. keys["alg2:k2"]["signatures"]["k1"] = "sig2"
  59. res = yield self.handler.upload_keys_for_user(
  60. local_user, device_id, {"one_time_keys": keys}
  61. )
  62. self.assertDictEqual(res, {"one_time_key_counts": {"alg1": 1, "alg2": 2}})
  63. @defer.inlineCallbacks
  64. def test_change_one_time_keys(self):
  65. """attempts to change one-time-keys should be rejected"""
  66. local_user = "@boris:" + self.hs.hostname
  67. device_id = "xyz"
  68. keys = {
  69. "alg1:k1": "key1",
  70. "alg2:k2": {"key": "key2", "signatures": {"k1": "sig1"}},
  71. "alg2:k3": {"key": "key3"},
  72. }
  73. res = yield self.handler.upload_keys_for_user(
  74. local_user, device_id, {"one_time_keys": keys}
  75. )
  76. self.assertDictEqual(res, {"one_time_key_counts": {"alg1": 1, "alg2": 2}})
  77. try:
  78. yield self.handler.upload_keys_for_user(
  79. local_user, device_id, {"one_time_keys": {"alg1:k1": "key2"}}
  80. )
  81. self.fail("No error when changing string key")
  82. except errors.SynapseError:
  83. pass
  84. try:
  85. yield self.handler.upload_keys_for_user(
  86. local_user, device_id, {"one_time_keys": {"alg2:k3": "key2"}}
  87. )
  88. self.fail("No error when replacing dict key with string")
  89. except errors.SynapseError:
  90. pass
  91. try:
  92. yield self.handler.upload_keys_for_user(
  93. local_user, device_id, {"one_time_keys": {"alg1:k1": {"key": "key"}}}
  94. )
  95. self.fail("No error when replacing string key with dict")
  96. except errors.SynapseError:
  97. pass
  98. try:
  99. yield self.handler.upload_keys_for_user(
  100. local_user,
  101. device_id,
  102. {
  103. "one_time_keys": {
  104. "alg2:k2": {"key": "key3", "signatures": {"k1": "sig1"}}
  105. }
  106. },
  107. )
  108. self.fail("No error when replacing dict key")
  109. except errors.SynapseError:
  110. pass
  111. @defer.inlineCallbacks
  112. def test_claim_one_time_key(self):
  113. local_user = "@boris:" + self.hs.hostname
  114. device_id = "xyz"
  115. keys = {"alg1:k1": "key1"}
  116. res = yield self.handler.upload_keys_for_user(
  117. local_user, device_id, {"one_time_keys": keys}
  118. )
  119. self.assertDictEqual(res, {"one_time_key_counts": {"alg1": 1}})
  120. res2 = yield self.handler.claim_one_time_keys(
  121. {"one_time_keys": {local_user: {device_id: "alg1"}}}, timeout=None
  122. )
  123. self.assertEqual(
  124. res2,
  125. {
  126. "failures": {},
  127. "one_time_keys": {local_user: {device_id: {"alg1:k1": "key1"}}},
  128. },
  129. )
  130. @defer.inlineCallbacks
  131. def test_replace_master_key(self):
  132. """uploading a new signing key should make the old signing key unavailable"""
  133. local_user = "@boris:" + self.hs.hostname
  134. keys1 = {
  135. "master_key": {
  136. # private key: 2lonYOM6xYKdEsO+6KrC766xBcHnYnim1x/4LFGF8B0
  137. "user_id": local_user,
  138. "usage": ["master"],
  139. "keys": {
  140. "ed25519:nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk": "nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk"
  141. },
  142. }
  143. }
  144. yield self.handler.upload_signing_keys_for_user(local_user, keys1)
  145. keys2 = {
  146. "master_key": {
  147. # private key: 4TL4AjRYwDVwD3pqQzcor+ez/euOB1/q78aTJ+czDNs
  148. "user_id": local_user,
  149. "usage": ["master"],
  150. "keys": {
  151. "ed25519:Hq6gL+utB4ET+UvD5ci0kgAwsX6qP/zvf8v6OInU5iw": "Hq6gL+utB4ET+UvD5ci0kgAwsX6qP/zvf8v6OInU5iw"
  152. },
  153. }
  154. }
  155. yield self.handler.upload_signing_keys_for_user(local_user, keys2)
  156. devices = yield self.handler.query_devices(
  157. {"device_keys": {local_user: []}}, 0, local_user
  158. )
  159. self.assertDictEqual(devices["master_keys"], {local_user: keys2["master_key"]})
  160. @defer.inlineCallbacks
  161. def test_reupload_signatures(self):
  162. """re-uploading a signature should not fail"""
  163. local_user = "@boris:" + self.hs.hostname
  164. keys1 = {
  165. "master_key": {
  166. # private key: HvQBbU+hc2Zr+JP1sE0XwBe1pfZZEYtJNPJLZJtS+F8
  167. "user_id": local_user,
  168. "usage": ["master"],
  169. "keys": {
  170. "ed25519:EmkqvokUn8p+vQAGZitOk4PWjp7Ukp3txV2TbMPEiBQ": "EmkqvokUn8p+vQAGZitOk4PWjp7Ukp3txV2TbMPEiBQ"
  171. },
  172. },
  173. "self_signing_key": {
  174. # private key: 2lonYOM6xYKdEsO+6KrC766xBcHnYnim1x/4LFGF8B0
  175. "user_id": local_user,
  176. "usage": ["self_signing"],
  177. "keys": {
  178. "ed25519:nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk": "nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk"
  179. },
  180. },
  181. }
  182. master_signing_key = key.decode_signing_key_base64(
  183. "ed25519",
  184. "EmkqvokUn8p+vQAGZitOk4PWjp7Ukp3txV2TbMPEiBQ",
  185. "HvQBbU+hc2Zr+JP1sE0XwBe1pfZZEYtJNPJLZJtS+F8",
  186. )
  187. sign.sign_json(keys1["self_signing_key"], local_user, master_signing_key)
  188. signing_key = key.decode_signing_key_base64(
  189. "ed25519",
  190. "nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk",
  191. "2lonYOM6xYKdEsO+6KrC766xBcHnYnim1x/4LFGF8B0",
  192. )
  193. yield self.handler.upload_signing_keys_for_user(local_user, keys1)
  194. # upload two device keys, which will be signed later by the self-signing key
  195. device_key_1 = {
  196. "user_id": local_user,
  197. "device_id": "abc",
  198. "algorithms": ["m.olm.curve25519-aes-sha256", "m.megolm.v1.aes-sha"],
  199. "keys": {
  200. "ed25519:abc": "base64+ed25519+key",
  201. "curve25519:abc": "base64+curve25519+key",
  202. },
  203. "signatures": {local_user: {"ed25519:abc": "base64+signature"}},
  204. }
  205. device_key_2 = {
  206. "user_id": local_user,
  207. "device_id": "def",
  208. "algorithms": ["m.olm.curve25519-aes-sha256", "m.megolm.v1.aes-sha"],
  209. "keys": {
  210. "ed25519:def": "base64+ed25519+key",
  211. "curve25519:def": "base64+curve25519+key",
  212. },
  213. "signatures": {local_user: {"ed25519:def": "base64+signature"}},
  214. }
  215. yield self.handler.upload_keys_for_user(
  216. local_user, "abc", {"device_keys": device_key_1}
  217. )
  218. yield self.handler.upload_keys_for_user(
  219. local_user, "def", {"device_keys": device_key_2}
  220. )
  221. # sign the first device key and upload it
  222. del device_key_1["signatures"]
  223. sign.sign_json(device_key_1, local_user, signing_key)
  224. yield self.handler.upload_signatures_for_device_keys(
  225. local_user, {local_user: {"abc": device_key_1}}
  226. )
  227. # sign the second device key and upload both device keys. The server
  228. # should ignore the first device key since it already has a valid
  229. # signature for it
  230. del device_key_2["signatures"]
  231. sign.sign_json(device_key_2, local_user, signing_key)
  232. yield self.handler.upload_signatures_for_device_keys(
  233. local_user, {local_user: {"abc": device_key_1, "def": device_key_2}}
  234. )
  235. device_key_1["signatures"][local_user]["ed25519:abc"] = "base64+signature"
  236. device_key_2["signatures"][local_user]["ed25519:def"] = "base64+signature"
  237. devices = yield self.handler.query_devices(
  238. {"device_keys": {local_user: []}}, 0, local_user
  239. )
  240. del devices["device_keys"][local_user]["abc"]["unsigned"]
  241. del devices["device_keys"][local_user]["def"]["unsigned"]
  242. self.assertDictEqual(devices["device_keys"][local_user]["abc"], device_key_1)
  243. self.assertDictEqual(devices["device_keys"][local_user]["def"], device_key_2)
  244. @defer.inlineCallbacks
  245. def test_self_signing_key_doesnt_show_up_as_device(self):
  246. """signing keys should be hidden when fetching a user's devices"""
  247. local_user = "@boris:" + self.hs.hostname
  248. keys1 = {
  249. "master_key": {
  250. # private key: 2lonYOM6xYKdEsO+6KrC766xBcHnYnim1x/4LFGF8B0
  251. "user_id": local_user,
  252. "usage": ["master"],
  253. "keys": {
  254. "ed25519:nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk": "nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk"
  255. },
  256. }
  257. }
  258. yield self.handler.upload_signing_keys_for_user(local_user, keys1)
  259. res = None
  260. try:
  261. yield self.hs.get_device_handler().check_device_registered(
  262. user_id=local_user,
  263. device_id="nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk",
  264. initial_device_display_name="new display name",
  265. )
  266. except errors.SynapseError as e:
  267. res = e.code
  268. self.assertEqual(res, 400)
  269. res = yield self.handler.query_local_devices({local_user: None})
  270. self.assertDictEqual(res, {local_user: {}})
  271. @defer.inlineCallbacks
  272. def test_upload_signatures(self):
  273. """should check signatures that are uploaded"""
  274. # set up a user with cross-signing keys and a device. This user will
  275. # try uploading signatures
  276. local_user = "@boris:" + self.hs.hostname
  277. device_id = "xyz"
  278. # private key: OMkooTr76ega06xNvXIGPbgvvxAOzmQncN8VObS7aBA
  279. device_pubkey = "NnHhnqiMFQkq969szYkooLaBAXW244ZOxgukCvm2ZeY"
  280. device_key = {
  281. "user_id": local_user,
  282. "device_id": device_id,
  283. "algorithms": ["m.olm.curve25519-aes-sha256", "m.megolm.v1.aes-sha"],
  284. "keys": {"curve25519:xyz": "curve25519+key", "ed25519:xyz": device_pubkey},
  285. "signatures": {local_user: {"ed25519:xyz": "something"}},
  286. }
  287. device_signing_key = key.decode_signing_key_base64(
  288. "ed25519", "xyz", "OMkooTr76ega06xNvXIGPbgvvxAOzmQncN8VObS7aBA"
  289. )
  290. yield self.handler.upload_keys_for_user(
  291. local_user, device_id, {"device_keys": device_key}
  292. )
  293. # private key: 2lonYOM6xYKdEsO+6KrC766xBcHnYnim1x/4LFGF8B0
  294. master_pubkey = "nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk"
  295. master_key = {
  296. "user_id": local_user,
  297. "usage": ["master"],
  298. "keys": {"ed25519:" + master_pubkey: master_pubkey},
  299. }
  300. master_signing_key = key.decode_signing_key_base64(
  301. "ed25519", master_pubkey, "2lonYOM6xYKdEsO+6KrC766xBcHnYnim1x/4LFGF8B0"
  302. )
  303. usersigning_pubkey = "Hq6gL+utB4ET+UvD5ci0kgAwsX6qP/zvf8v6OInU5iw"
  304. usersigning_key = {
  305. # private key: 4TL4AjRYwDVwD3pqQzcor+ez/euOB1/q78aTJ+czDNs
  306. "user_id": local_user,
  307. "usage": ["user_signing"],
  308. "keys": {"ed25519:" + usersigning_pubkey: usersigning_pubkey},
  309. }
  310. usersigning_signing_key = key.decode_signing_key_base64(
  311. "ed25519", usersigning_pubkey, "4TL4AjRYwDVwD3pqQzcor+ez/euOB1/q78aTJ+czDNs"
  312. )
  313. sign.sign_json(usersigning_key, local_user, master_signing_key)
  314. # private key: HvQBbU+hc2Zr+JP1sE0XwBe1pfZZEYtJNPJLZJtS+F8
  315. selfsigning_pubkey = "EmkqvokUn8p+vQAGZitOk4PWjp7Ukp3txV2TbMPEiBQ"
  316. selfsigning_key = {
  317. "user_id": local_user,
  318. "usage": ["self_signing"],
  319. "keys": {"ed25519:" + selfsigning_pubkey: selfsigning_pubkey},
  320. }
  321. selfsigning_signing_key = key.decode_signing_key_base64(
  322. "ed25519", selfsigning_pubkey, "HvQBbU+hc2Zr+JP1sE0XwBe1pfZZEYtJNPJLZJtS+F8"
  323. )
  324. sign.sign_json(selfsigning_key, local_user, master_signing_key)
  325. cross_signing_keys = {
  326. "master_key": master_key,
  327. "user_signing_key": usersigning_key,
  328. "self_signing_key": selfsigning_key,
  329. }
  330. yield self.handler.upload_signing_keys_for_user(local_user, cross_signing_keys)
  331. # set up another user with a master key. This user will be signed by
  332. # the first user
  333. other_user = "@otherboris:" + self.hs.hostname
  334. other_master_pubkey = "fHZ3NPiKxoLQm5OoZbKa99SYxprOjNs4TwJUKP+twCM"
  335. other_master_key = {
  336. # private key: oyw2ZUx0O4GifbfFYM0nQvj9CL0b8B7cyN4FprtK8OI
  337. "user_id": other_user,
  338. "usage": ["master"],
  339. "keys": {"ed25519:" + other_master_pubkey: other_master_pubkey},
  340. }
  341. yield self.handler.upload_signing_keys_for_user(
  342. other_user, {"master_key": other_master_key}
  343. )
  344. # test various signature failures (see below)
  345. ret = yield self.handler.upload_signatures_for_device_keys(
  346. local_user,
  347. {
  348. local_user: {
  349. # fails because the signature is invalid
  350. # should fail with INVALID_SIGNATURE
  351. device_id: {
  352. "user_id": local_user,
  353. "device_id": device_id,
  354. "algorithms": [
  355. "m.olm.curve25519-aes-sha256",
  356. "m.megolm.v1.aes-sha",
  357. ],
  358. "keys": {
  359. "curve25519:xyz": "curve25519+key",
  360. # private key: OMkooTr76ega06xNvXIGPbgvvxAOzmQncN8VObS7aBA
  361. "ed25519:xyz": device_pubkey,
  362. },
  363. "signatures": {
  364. local_user: {"ed25519:" + selfsigning_pubkey: "something"}
  365. },
  366. },
  367. # fails because device is unknown
  368. # should fail with NOT_FOUND
  369. "unknown": {
  370. "user_id": local_user,
  371. "device_id": "unknown",
  372. "signatures": {
  373. local_user: {"ed25519:" + selfsigning_pubkey: "something"}
  374. },
  375. },
  376. # fails because the signature is invalid
  377. # should fail with INVALID_SIGNATURE
  378. master_pubkey: {
  379. "user_id": local_user,
  380. "usage": ["master"],
  381. "keys": {"ed25519:" + master_pubkey: master_pubkey},
  382. "signatures": {
  383. local_user: {"ed25519:" + device_pubkey: "something"}
  384. },
  385. },
  386. },
  387. other_user: {
  388. # fails because the device is not the user's master-signing key
  389. # should fail with NOT_FOUND
  390. "unknown": {
  391. "user_id": other_user,
  392. "device_id": "unknown",
  393. "signatures": {
  394. local_user: {"ed25519:" + usersigning_pubkey: "something"}
  395. },
  396. },
  397. other_master_pubkey: {
  398. # fails because the key doesn't match what the server has
  399. # should fail with UNKNOWN
  400. "user_id": other_user,
  401. "usage": ["master"],
  402. "keys": {"ed25519:" + other_master_pubkey: other_master_pubkey},
  403. "something": "random",
  404. "signatures": {
  405. local_user: {"ed25519:" + usersigning_pubkey: "something"}
  406. },
  407. },
  408. },
  409. },
  410. )
  411. user_failures = ret["failures"][local_user]
  412. self.assertEqual(
  413. user_failures[device_id]["errcode"], errors.Codes.INVALID_SIGNATURE
  414. )
  415. self.assertEqual(
  416. user_failures[master_pubkey]["errcode"], errors.Codes.INVALID_SIGNATURE
  417. )
  418. self.assertEqual(user_failures["unknown"]["errcode"], errors.Codes.NOT_FOUND)
  419. other_user_failures = ret["failures"][other_user]
  420. self.assertEqual(
  421. other_user_failures["unknown"]["errcode"], errors.Codes.NOT_FOUND
  422. )
  423. self.assertEqual(
  424. other_user_failures[other_master_pubkey]["errcode"], errors.Codes.UNKNOWN
  425. )
  426. # test successful signatures
  427. del device_key["signatures"]
  428. sign.sign_json(device_key, local_user, selfsigning_signing_key)
  429. sign.sign_json(master_key, local_user, device_signing_key)
  430. sign.sign_json(other_master_key, local_user, usersigning_signing_key)
  431. ret = yield self.handler.upload_signatures_for_device_keys(
  432. local_user,
  433. {
  434. local_user: {device_id: device_key, master_pubkey: master_key},
  435. other_user: {other_master_pubkey: other_master_key},
  436. },
  437. )
  438. self.assertEqual(ret["failures"], {})
  439. # fetch the signed keys/devices and make sure that the signatures are there
  440. ret = yield self.handler.query_devices(
  441. {"device_keys": {local_user: [], other_user: []}}, 0, local_user
  442. )
  443. self.assertEqual(
  444. ret["device_keys"][local_user]["xyz"]["signatures"][local_user][
  445. "ed25519:" + selfsigning_pubkey
  446. ],
  447. device_key["signatures"][local_user]["ed25519:" + selfsigning_pubkey],
  448. )
  449. self.assertEqual(
  450. ret["master_keys"][local_user]["signatures"][local_user][
  451. "ed25519:" + device_id
  452. ],
  453. master_key["signatures"][local_user]["ed25519:" + device_id],
  454. )
  455. self.assertEqual(
  456. ret["master_keys"][other_user]["signatures"][local_user][
  457. "ed25519:" + usersigning_pubkey
  458. ],
  459. other_master_key["signatures"][local_user]["ed25519:" + usersigning_pubkey],
  460. )