test_e2e_keys.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  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. from signedjson import key as key, sign as sign
  19. from twisted.internet import defer
  20. import synapse.handlers.e2e_keys
  21. import synapse.storage
  22. from synapse.api import errors
  23. from synapse.api.constants import RoomEncryptionAlgorithms
  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": [
  199. "m.olm.curve25519-aes-sha2",
  200. RoomEncryptionAlgorithms.MEGOLM_V1_AES_SHA2,
  201. ],
  202. "keys": {
  203. "ed25519:abc": "base64+ed25519+key",
  204. "curve25519:abc": "base64+curve25519+key",
  205. },
  206. "signatures": {local_user: {"ed25519:abc": "base64+signature"}},
  207. }
  208. device_key_2 = {
  209. "user_id": local_user,
  210. "device_id": "def",
  211. "algorithms": [
  212. "m.olm.curve25519-aes-sha2",
  213. RoomEncryptionAlgorithms.MEGOLM_V1_AES_SHA2,
  214. ],
  215. "keys": {
  216. "ed25519:def": "base64+ed25519+key",
  217. "curve25519:def": "base64+curve25519+key",
  218. },
  219. "signatures": {local_user: {"ed25519:def": "base64+signature"}},
  220. }
  221. yield self.handler.upload_keys_for_user(
  222. local_user, "abc", {"device_keys": device_key_1}
  223. )
  224. yield self.handler.upload_keys_for_user(
  225. local_user, "def", {"device_keys": device_key_2}
  226. )
  227. # sign the first device key and upload it
  228. del device_key_1["signatures"]
  229. sign.sign_json(device_key_1, local_user, signing_key)
  230. yield self.handler.upload_signatures_for_device_keys(
  231. local_user, {local_user: {"abc": device_key_1}}
  232. )
  233. # sign the second device key and upload both device keys. The server
  234. # should ignore the first device key since it already has a valid
  235. # signature for it
  236. del device_key_2["signatures"]
  237. sign.sign_json(device_key_2, local_user, signing_key)
  238. yield self.handler.upload_signatures_for_device_keys(
  239. local_user, {local_user: {"abc": device_key_1, "def": device_key_2}}
  240. )
  241. device_key_1["signatures"][local_user]["ed25519:abc"] = "base64+signature"
  242. device_key_2["signatures"][local_user]["ed25519:def"] = "base64+signature"
  243. devices = yield self.handler.query_devices(
  244. {"device_keys": {local_user: []}}, 0, local_user
  245. )
  246. del devices["device_keys"][local_user]["abc"]["unsigned"]
  247. del devices["device_keys"][local_user]["def"]["unsigned"]
  248. self.assertDictEqual(devices["device_keys"][local_user]["abc"], device_key_1)
  249. self.assertDictEqual(devices["device_keys"][local_user]["def"], device_key_2)
  250. @defer.inlineCallbacks
  251. def test_self_signing_key_doesnt_show_up_as_device(self):
  252. """signing keys should be hidden when fetching a user's devices"""
  253. local_user = "@boris:" + self.hs.hostname
  254. keys1 = {
  255. "master_key": {
  256. # private key: 2lonYOM6xYKdEsO+6KrC766xBcHnYnim1x/4LFGF8B0
  257. "user_id": local_user,
  258. "usage": ["master"],
  259. "keys": {
  260. "ed25519:nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk": "nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk"
  261. },
  262. }
  263. }
  264. yield self.handler.upload_signing_keys_for_user(local_user, keys1)
  265. res = None
  266. try:
  267. yield self.hs.get_device_handler().check_device_registered(
  268. user_id=local_user,
  269. device_id="nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk",
  270. initial_device_display_name="new display name",
  271. )
  272. except errors.SynapseError as e:
  273. res = e.code
  274. self.assertEqual(res, 400)
  275. res = yield self.handler.query_local_devices({local_user: None})
  276. self.assertDictEqual(res, {local_user: {}})
  277. @defer.inlineCallbacks
  278. def test_upload_signatures(self):
  279. """should check signatures that are uploaded"""
  280. # set up a user with cross-signing keys and a device. This user will
  281. # try uploading signatures
  282. local_user = "@boris:" + self.hs.hostname
  283. device_id = "xyz"
  284. # private key: OMkooTr76ega06xNvXIGPbgvvxAOzmQncN8VObS7aBA
  285. device_pubkey = "NnHhnqiMFQkq969szYkooLaBAXW244ZOxgukCvm2ZeY"
  286. device_key = {
  287. "user_id": local_user,
  288. "device_id": device_id,
  289. "algorithms": [
  290. "m.olm.curve25519-aes-sha2",
  291. RoomEncryptionAlgorithms.MEGOLM_V1_AES_SHA2,
  292. ],
  293. "keys": {"curve25519:xyz": "curve25519+key", "ed25519:xyz": device_pubkey},
  294. "signatures": {local_user: {"ed25519:xyz": "something"}},
  295. }
  296. device_signing_key = key.decode_signing_key_base64(
  297. "ed25519", "xyz", "OMkooTr76ega06xNvXIGPbgvvxAOzmQncN8VObS7aBA"
  298. )
  299. yield self.handler.upload_keys_for_user(
  300. local_user, device_id, {"device_keys": device_key}
  301. )
  302. # private key: 2lonYOM6xYKdEsO+6KrC766xBcHnYnim1x/4LFGF8B0
  303. master_pubkey = "nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk"
  304. master_key = {
  305. "user_id": local_user,
  306. "usage": ["master"],
  307. "keys": {"ed25519:" + master_pubkey: master_pubkey},
  308. }
  309. master_signing_key = key.decode_signing_key_base64(
  310. "ed25519", master_pubkey, "2lonYOM6xYKdEsO+6KrC766xBcHnYnim1x/4LFGF8B0"
  311. )
  312. usersigning_pubkey = "Hq6gL+utB4ET+UvD5ci0kgAwsX6qP/zvf8v6OInU5iw"
  313. usersigning_key = {
  314. # private key: 4TL4AjRYwDVwD3pqQzcor+ez/euOB1/q78aTJ+czDNs
  315. "user_id": local_user,
  316. "usage": ["user_signing"],
  317. "keys": {"ed25519:" + usersigning_pubkey: usersigning_pubkey},
  318. }
  319. usersigning_signing_key = key.decode_signing_key_base64(
  320. "ed25519", usersigning_pubkey, "4TL4AjRYwDVwD3pqQzcor+ez/euOB1/q78aTJ+czDNs"
  321. )
  322. sign.sign_json(usersigning_key, local_user, master_signing_key)
  323. # private key: HvQBbU+hc2Zr+JP1sE0XwBe1pfZZEYtJNPJLZJtS+F8
  324. selfsigning_pubkey = "EmkqvokUn8p+vQAGZitOk4PWjp7Ukp3txV2TbMPEiBQ"
  325. selfsigning_key = {
  326. "user_id": local_user,
  327. "usage": ["self_signing"],
  328. "keys": {"ed25519:" + selfsigning_pubkey: selfsigning_pubkey},
  329. }
  330. selfsigning_signing_key = key.decode_signing_key_base64(
  331. "ed25519", selfsigning_pubkey, "HvQBbU+hc2Zr+JP1sE0XwBe1pfZZEYtJNPJLZJtS+F8"
  332. )
  333. sign.sign_json(selfsigning_key, local_user, master_signing_key)
  334. cross_signing_keys = {
  335. "master_key": master_key,
  336. "user_signing_key": usersigning_key,
  337. "self_signing_key": selfsigning_key,
  338. }
  339. yield self.handler.upload_signing_keys_for_user(local_user, cross_signing_keys)
  340. # set up another user with a master key. This user will be signed by
  341. # the first user
  342. other_user = "@otherboris:" + self.hs.hostname
  343. other_master_pubkey = "fHZ3NPiKxoLQm5OoZbKa99SYxprOjNs4TwJUKP+twCM"
  344. other_master_key = {
  345. # private key: oyw2ZUx0O4GifbfFYM0nQvj9CL0b8B7cyN4FprtK8OI
  346. "user_id": other_user,
  347. "usage": ["master"],
  348. "keys": {"ed25519:" + other_master_pubkey: other_master_pubkey},
  349. }
  350. yield self.handler.upload_signing_keys_for_user(
  351. other_user, {"master_key": other_master_key}
  352. )
  353. # test various signature failures (see below)
  354. ret = yield self.handler.upload_signatures_for_device_keys(
  355. local_user,
  356. {
  357. local_user: {
  358. # fails because the signature is invalid
  359. # should fail with INVALID_SIGNATURE
  360. device_id: {
  361. "user_id": local_user,
  362. "device_id": device_id,
  363. "algorithms": [
  364. "m.olm.curve25519-aes-sha2",
  365. RoomEncryptionAlgorithms.MEGOLM_V1_AES_SHA2,
  366. ],
  367. "keys": {
  368. "curve25519:xyz": "curve25519+key",
  369. # private key: OMkooTr76ega06xNvXIGPbgvvxAOzmQncN8VObS7aBA
  370. "ed25519:xyz": device_pubkey,
  371. },
  372. "signatures": {
  373. local_user: {"ed25519:" + selfsigning_pubkey: "something"}
  374. },
  375. },
  376. # fails because device is unknown
  377. # should fail with NOT_FOUND
  378. "unknown": {
  379. "user_id": local_user,
  380. "device_id": "unknown",
  381. "signatures": {
  382. local_user: {"ed25519:" + selfsigning_pubkey: "something"}
  383. },
  384. },
  385. # fails because the signature is invalid
  386. # should fail with INVALID_SIGNATURE
  387. master_pubkey: {
  388. "user_id": local_user,
  389. "usage": ["master"],
  390. "keys": {"ed25519:" + master_pubkey: master_pubkey},
  391. "signatures": {
  392. local_user: {"ed25519:" + device_pubkey: "something"}
  393. },
  394. },
  395. },
  396. other_user: {
  397. # fails because the device is not the user's master-signing key
  398. # should fail with NOT_FOUND
  399. "unknown": {
  400. "user_id": other_user,
  401. "device_id": "unknown",
  402. "signatures": {
  403. local_user: {"ed25519:" + usersigning_pubkey: "something"}
  404. },
  405. },
  406. other_master_pubkey: {
  407. # fails because the key doesn't match what the server has
  408. # should fail with UNKNOWN
  409. "user_id": other_user,
  410. "usage": ["master"],
  411. "keys": {"ed25519:" + other_master_pubkey: other_master_pubkey},
  412. "something": "random",
  413. "signatures": {
  414. local_user: {"ed25519:" + usersigning_pubkey: "something"}
  415. },
  416. },
  417. },
  418. },
  419. )
  420. user_failures = ret["failures"][local_user]
  421. self.assertEqual(
  422. user_failures[device_id]["errcode"], errors.Codes.INVALID_SIGNATURE
  423. )
  424. self.assertEqual(
  425. user_failures[master_pubkey]["errcode"], errors.Codes.INVALID_SIGNATURE
  426. )
  427. self.assertEqual(user_failures["unknown"]["errcode"], errors.Codes.NOT_FOUND)
  428. other_user_failures = ret["failures"][other_user]
  429. self.assertEqual(
  430. other_user_failures["unknown"]["errcode"], errors.Codes.NOT_FOUND
  431. )
  432. self.assertEqual(
  433. other_user_failures[other_master_pubkey]["errcode"], errors.Codes.UNKNOWN
  434. )
  435. # test successful signatures
  436. del device_key["signatures"]
  437. sign.sign_json(device_key, local_user, selfsigning_signing_key)
  438. sign.sign_json(master_key, local_user, device_signing_key)
  439. sign.sign_json(other_master_key, local_user, usersigning_signing_key)
  440. ret = yield self.handler.upload_signatures_for_device_keys(
  441. local_user,
  442. {
  443. local_user: {device_id: device_key, master_pubkey: master_key},
  444. other_user: {other_master_pubkey: other_master_key},
  445. },
  446. )
  447. self.assertEqual(ret["failures"], {})
  448. # fetch the signed keys/devices and make sure that the signatures are there
  449. ret = yield self.handler.query_devices(
  450. {"device_keys": {local_user: [], other_user: []}}, 0, local_user
  451. )
  452. self.assertEqual(
  453. ret["device_keys"][local_user]["xyz"]["signatures"][local_user][
  454. "ed25519:" + selfsigning_pubkey
  455. ],
  456. device_key["signatures"][local_user]["ed25519:" + selfsigning_pubkey],
  457. )
  458. self.assertEqual(
  459. ret["master_keys"][local_user]["signatures"][local_user][
  460. "ed25519:" + device_id
  461. ],
  462. master_key["signatures"][local_user]["ed25519:" + device_id],
  463. )
  464. self.assertEqual(
  465. ret["master_keys"][other_user]["signatures"][local_user][
  466. "ed25519:" + usersigning_pubkey
  467. ],
  468. other_master_key["signatures"][local_user]["ed25519:" + usersigning_pubkey],
  469. )