test_e2e_keys.py 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833
  1. # Copyright 2016 OpenMarket Ltd
  2. # Copyright 2019 New Vector Ltd
  3. # Copyright 2019 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. from unittest import mock
  17. from signedjson import key as key, sign as sign
  18. from twisted.internet import defer
  19. from synapse.api.constants import RoomEncryptionAlgorithms
  20. from synapse.api.errors import Codes, SynapseError
  21. from tests import unittest
  22. class E2eKeysHandlerTestCase(unittest.HomeserverTestCase):
  23. def make_homeserver(self, reactor, clock):
  24. return self.setup_test_homeserver(federation_client=mock.Mock())
  25. def prepare(self, reactor, clock, hs):
  26. self.handler = hs.get_e2e_keys_handler()
  27. self.store = self.hs.get_datastore()
  28. def test_query_local_devices_no_devices(self):
  29. """If the user has no devices, we expect an empty list."""
  30. local_user = "@boris:" + self.hs.hostname
  31. res = self.get_success(self.handler.query_local_devices({local_user: None}))
  32. self.assertDictEqual(res, {local_user: {}})
  33. def test_reupload_one_time_keys(self):
  34. """we should be able to re-upload the same keys"""
  35. local_user = "@boris:" + self.hs.hostname
  36. device_id = "xyz"
  37. keys = {
  38. "alg1:k1": "key1",
  39. "alg2:k2": {"key": "key2", "signatures": {"k1": "sig1"}},
  40. "alg2:k3": {"key": "key3"},
  41. }
  42. # Note that "signed_curve25519" is always returned in key count responses. This is necessary until
  43. # https://github.com/matrix-org/matrix-doc/issues/3298 is fixed.
  44. res = self.get_success(
  45. self.handler.upload_keys_for_user(
  46. local_user, device_id, {"one_time_keys": keys}
  47. )
  48. )
  49. self.assertDictEqual(
  50. res, {"one_time_key_counts": {"alg1": 1, "alg2": 2, "signed_curve25519": 0}}
  51. )
  52. # we should be able to change the signature without a problem
  53. keys["alg2:k2"]["signatures"]["k1"] = "sig2"
  54. res = self.get_success(
  55. self.handler.upload_keys_for_user(
  56. local_user, device_id, {"one_time_keys": keys}
  57. )
  58. )
  59. self.assertDictEqual(
  60. res, {"one_time_key_counts": {"alg1": 1, "alg2": 2, "signed_curve25519": 0}}
  61. )
  62. def test_change_one_time_keys(self):
  63. """attempts to change one-time-keys should be rejected"""
  64. local_user = "@boris:" + self.hs.hostname
  65. device_id = "xyz"
  66. keys = {
  67. "alg1:k1": "key1",
  68. "alg2:k2": {"key": "key2", "signatures": {"k1": "sig1"}},
  69. "alg2:k3": {"key": "key3"},
  70. }
  71. res = self.get_success(
  72. self.handler.upload_keys_for_user(
  73. local_user, device_id, {"one_time_keys": keys}
  74. )
  75. )
  76. self.assertDictEqual(
  77. res, {"one_time_key_counts": {"alg1": 1, "alg2": 2, "signed_curve25519": 0}}
  78. )
  79. # Error when changing string key
  80. self.get_failure(
  81. self.handler.upload_keys_for_user(
  82. local_user, device_id, {"one_time_keys": {"alg1:k1": "key2"}}
  83. ),
  84. SynapseError,
  85. )
  86. # Error when replacing dict key with string
  87. self.get_failure(
  88. self.handler.upload_keys_for_user(
  89. local_user, device_id, {"one_time_keys": {"alg2:k3": "key2"}}
  90. ),
  91. SynapseError,
  92. )
  93. # Error when replacing string key with dict
  94. self.get_failure(
  95. self.handler.upload_keys_for_user(
  96. local_user,
  97. device_id,
  98. {"one_time_keys": {"alg1:k1": {"key": "key"}}},
  99. ),
  100. SynapseError,
  101. )
  102. # Error when replacing dict key
  103. self.get_failure(
  104. self.handler.upload_keys_for_user(
  105. local_user,
  106. device_id,
  107. {
  108. "one_time_keys": {
  109. "alg2:k2": {"key": "key3", "signatures": {"k1": "sig1"}}
  110. }
  111. },
  112. ),
  113. SynapseError,
  114. )
  115. def test_claim_one_time_key(self):
  116. local_user = "@boris:" + self.hs.hostname
  117. device_id = "xyz"
  118. keys = {"alg1:k1": "key1"}
  119. res = self.get_success(
  120. self.handler.upload_keys_for_user(
  121. local_user, device_id, {"one_time_keys": keys}
  122. )
  123. )
  124. self.assertDictEqual(
  125. res, {"one_time_key_counts": {"alg1": 1, "signed_curve25519": 0}}
  126. )
  127. res2 = self.get_success(
  128. self.handler.claim_one_time_keys(
  129. {"one_time_keys": {local_user: {device_id: "alg1"}}}, timeout=None
  130. )
  131. )
  132. self.assertEqual(
  133. res2,
  134. {
  135. "failures": {},
  136. "one_time_keys": {local_user: {device_id: {"alg1:k1": "key1"}}},
  137. },
  138. )
  139. def test_fallback_key(self):
  140. local_user = "@boris:" + self.hs.hostname
  141. device_id = "xyz"
  142. fallback_key = {"alg1:k1": "fallback_key1"}
  143. fallback_key2 = {"alg1:k2": "fallback_key2"}
  144. fallback_key3 = {"alg1:k2": "fallback_key3"}
  145. otk = {"alg1:k2": "key2"}
  146. # we shouldn't have any unused fallback keys yet
  147. res = self.get_success(
  148. self.store.get_e2e_unused_fallback_key_types(local_user, device_id)
  149. )
  150. self.assertEqual(res, [])
  151. self.get_success(
  152. self.handler.upload_keys_for_user(
  153. local_user,
  154. device_id,
  155. {"fallback_keys": fallback_key},
  156. )
  157. )
  158. # we should now have an unused alg1 key
  159. res = self.get_success(
  160. self.store.get_e2e_unused_fallback_key_types(local_user, device_id)
  161. )
  162. self.assertEqual(res, ["alg1"])
  163. # claiming an OTK when no OTKs are available should return the fallback
  164. # key
  165. res = self.get_success(
  166. self.handler.claim_one_time_keys(
  167. {"one_time_keys": {local_user: {device_id: "alg1"}}}, timeout=None
  168. )
  169. )
  170. self.assertEqual(
  171. res,
  172. {"failures": {}, "one_time_keys": {local_user: {device_id: fallback_key}}},
  173. )
  174. # we shouldn't have any unused fallback keys again
  175. res = self.get_success(
  176. self.store.get_e2e_unused_fallback_key_types(local_user, device_id)
  177. )
  178. self.assertEqual(res, [])
  179. # claiming an OTK again should return the same fallback key
  180. res = self.get_success(
  181. self.handler.claim_one_time_keys(
  182. {"one_time_keys": {local_user: {device_id: "alg1"}}}, timeout=None
  183. )
  184. )
  185. self.assertEqual(
  186. res,
  187. {"failures": {}, "one_time_keys": {local_user: {device_id: fallback_key}}},
  188. )
  189. # re-uploading the same fallback key should still result in no unused fallback
  190. # keys
  191. self.get_success(
  192. self.handler.upload_keys_for_user(
  193. local_user,
  194. device_id,
  195. {"fallback_keys": fallback_key},
  196. )
  197. )
  198. res = self.get_success(
  199. self.store.get_e2e_unused_fallback_key_types(local_user, device_id)
  200. )
  201. self.assertEqual(res, [])
  202. # uploading a new fallback key should result in an unused fallback key
  203. self.get_success(
  204. self.handler.upload_keys_for_user(
  205. local_user,
  206. device_id,
  207. {"fallback_keys": fallback_key2},
  208. )
  209. )
  210. res = self.get_success(
  211. self.store.get_e2e_unused_fallback_key_types(local_user, device_id)
  212. )
  213. self.assertEqual(res, ["alg1"])
  214. # if the user uploads a one-time key, the next claim should fetch the
  215. # one-time key, and then go back to the fallback
  216. self.get_success(
  217. self.handler.upload_keys_for_user(
  218. local_user, device_id, {"one_time_keys": otk}
  219. )
  220. )
  221. res = self.get_success(
  222. self.handler.claim_one_time_keys(
  223. {"one_time_keys": {local_user: {device_id: "alg1"}}}, timeout=None
  224. )
  225. )
  226. self.assertEqual(
  227. res,
  228. {"failures": {}, "one_time_keys": {local_user: {device_id: otk}}},
  229. )
  230. res = self.get_success(
  231. self.handler.claim_one_time_keys(
  232. {"one_time_keys": {local_user: {device_id: "alg1"}}}, timeout=None
  233. )
  234. )
  235. self.assertEqual(
  236. res,
  237. {"failures": {}, "one_time_keys": {local_user: {device_id: fallback_key2}}},
  238. )
  239. # using the unstable prefix should also set the fallback key
  240. self.get_success(
  241. self.handler.upload_keys_for_user(
  242. local_user,
  243. device_id,
  244. {"org.matrix.msc2732.fallback_keys": fallback_key3},
  245. )
  246. )
  247. res = self.get_success(
  248. self.handler.claim_one_time_keys(
  249. {"one_time_keys": {local_user: {device_id: "alg1"}}}, timeout=None
  250. )
  251. )
  252. self.assertEqual(
  253. res,
  254. {"failures": {}, "one_time_keys": {local_user: {device_id: fallback_key3}}},
  255. )
  256. def test_replace_master_key(self):
  257. """uploading a new signing key should make the old signing key unavailable"""
  258. local_user = "@boris:" + self.hs.hostname
  259. keys1 = {
  260. "master_key": {
  261. # private key: 2lonYOM6xYKdEsO+6KrC766xBcHnYnim1x/4LFGF8B0
  262. "user_id": local_user,
  263. "usage": ["master"],
  264. "keys": {
  265. "ed25519:nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk": "nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk"
  266. },
  267. }
  268. }
  269. self.get_success(self.handler.upload_signing_keys_for_user(local_user, keys1))
  270. keys2 = {
  271. "master_key": {
  272. # private key: 4TL4AjRYwDVwD3pqQzcor+ez/euOB1/q78aTJ+czDNs
  273. "user_id": local_user,
  274. "usage": ["master"],
  275. "keys": {
  276. "ed25519:Hq6gL+utB4ET+UvD5ci0kgAwsX6qP/zvf8v6OInU5iw": "Hq6gL+utB4ET+UvD5ci0kgAwsX6qP/zvf8v6OInU5iw"
  277. },
  278. }
  279. }
  280. self.get_success(self.handler.upload_signing_keys_for_user(local_user, keys2))
  281. devices = self.get_success(
  282. self.handler.query_devices(
  283. {"device_keys": {local_user: []}}, 0, local_user, "device123"
  284. )
  285. )
  286. self.assertDictEqual(devices["master_keys"], {local_user: keys2["master_key"]})
  287. def test_reupload_signatures(self):
  288. """re-uploading a signature should not fail"""
  289. local_user = "@boris:" + self.hs.hostname
  290. keys1 = {
  291. "master_key": {
  292. # private key: HvQBbU+hc2Zr+JP1sE0XwBe1pfZZEYtJNPJLZJtS+F8
  293. "user_id": local_user,
  294. "usage": ["master"],
  295. "keys": {
  296. "ed25519:EmkqvokUn8p+vQAGZitOk4PWjp7Ukp3txV2TbMPEiBQ": "EmkqvokUn8p+vQAGZitOk4PWjp7Ukp3txV2TbMPEiBQ"
  297. },
  298. },
  299. "self_signing_key": {
  300. # private key: 2lonYOM6xYKdEsO+6KrC766xBcHnYnim1x/4LFGF8B0
  301. "user_id": local_user,
  302. "usage": ["self_signing"],
  303. "keys": {
  304. "ed25519:nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk": "nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk"
  305. },
  306. },
  307. }
  308. master_signing_key = key.decode_signing_key_base64(
  309. "ed25519",
  310. "EmkqvokUn8p+vQAGZitOk4PWjp7Ukp3txV2TbMPEiBQ",
  311. "HvQBbU+hc2Zr+JP1sE0XwBe1pfZZEYtJNPJLZJtS+F8",
  312. )
  313. sign.sign_json(keys1["self_signing_key"], local_user, master_signing_key)
  314. signing_key = key.decode_signing_key_base64(
  315. "ed25519",
  316. "nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk",
  317. "2lonYOM6xYKdEsO+6KrC766xBcHnYnim1x/4LFGF8B0",
  318. )
  319. self.get_success(self.handler.upload_signing_keys_for_user(local_user, keys1))
  320. # upload two device keys, which will be signed later by the self-signing key
  321. device_key_1 = {
  322. "user_id": local_user,
  323. "device_id": "abc",
  324. "algorithms": [
  325. "m.olm.curve25519-aes-sha2",
  326. RoomEncryptionAlgorithms.MEGOLM_V1_AES_SHA2,
  327. ],
  328. "keys": {
  329. "ed25519:abc": "base64+ed25519+key",
  330. "curve25519:abc": "base64+curve25519+key",
  331. },
  332. "signatures": {local_user: {"ed25519:abc": "base64+signature"}},
  333. }
  334. device_key_2 = {
  335. "user_id": local_user,
  336. "device_id": "def",
  337. "algorithms": [
  338. "m.olm.curve25519-aes-sha2",
  339. RoomEncryptionAlgorithms.MEGOLM_V1_AES_SHA2,
  340. ],
  341. "keys": {
  342. "ed25519:def": "base64+ed25519+key",
  343. "curve25519:def": "base64+curve25519+key",
  344. },
  345. "signatures": {local_user: {"ed25519:def": "base64+signature"}},
  346. }
  347. self.get_success(
  348. self.handler.upload_keys_for_user(
  349. local_user, "abc", {"device_keys": device_key_1}
  350. )
  351. )
  352. self.get_success(
  353. self.handler.upload_keys_for_user(
  354. local_user, "def", {"device_keys": device_key_2}
  355. )
  356. )
  357. # sign the first device key and upload it
  358. del device_key_1["signatures"]
  359. sign.sign_json(device_key_1, local_user, signing_key)
  360. self.get_success(
  361. self.handler.upload_signatures_for_device_keys(
  362. local_user, {local_user: {"abc": device_key_1}}
  363. )
  364. )
  365. # sign the second device key and upload both device keys. The server
  366. # should ignore the first device key since it already has a valid
  367. # signature for it
  368. del device_key_2["signatures"]
  369. sign.sign_json(device_key_2, local_user, signing_key)
  370. self.get_success(
  371. self.handler.upload_signatures_for_device_keys(
  372. local_user, {local_user: {"abc": device_key_1, "def": device_key_2}}
  373. )
  374. )
  375. device_key_1["signatures"][local_user]["ed25519:abc"] = "base64+signature"
  376. device_key_2["signatures"][local_user]["ed25519:def"] = "base64+signature"
  377. devices = self.get_success(
  378. self.handler.query_devices(
  379. {"device_keys": {local_user: []}}, 0, local_user, "device123"
  380. )
  381. )
  382. del devices["device_keys"][local_user]["abc"]["unsigned"]
  383. del devices["device_keys"][local_user]["def"]["unsigned"]
  384. self.assertDictEqual(devices["device_keys"][local_user]["abc"], device_key_1)
  385. self.assertDictEqual(devices["device_keys"][local_user]["def"], device_key_2)
  386. def test_self_signing_key_doesnt_show_up_as_device(self):
  387. """signing keys should be hidden when fetching a user's devices"""
  388. local_user = "@boris:" + self.hs.hostname
  389. keys1 = {
  390. "master_key": {
  391. # private key: 2lonYOM6xYKdEsO+6KrC766xBcHnYnim1x/4LFGF8B0
  392. "user_id": local_user,
  393. "usage": ["master"],
  394. "keys": {
  395. "ed25519:nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk": "nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk"
  396. },
  397. }
  398. }
  399. self.get_success(self.handler.upload_signing_keys_for_user(local_user, keys1))
  400. e = self.get_failure(
  401. self.hs.get_device_handler().check_device_registered(
  402. user_id=local_user,
  403. device_id="nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk",
  404. initial_device_display_name="new display name",
  405. ),
  406. SynapseError,
  407. )
  408. res = e.value.code
  409. self.assertEqual(res, 400)
  410. res = self.get_success(self.handler.query_local_devices({local_user: None}))
  411. self.assertDictEqual(res, {local_user: {}})
  412. def test_upload_signatures(self):
  413. """should check signatures that are uploaded"""
  414. # set up a user with cross-signing keys and a device. This user will
  415. # try uploading signatures
  416. local_user = "@boris:" + self.hs.hostname
  417. device_id = "xyz"
  418. # private key: OMkooTr76ega06xNvXIGPbgvvxAOzmQncN8VObS7aBA
  419. device_pubkey = "NnHhnqiMFQkq969szYkooLaBAXW244ZOxgukCvm2ZeY"
  420. device_key = {
  421. "user_id": local_user,
  422. "device_id": device_id,
  423. "algorithms": [
  424. "m.olm.curve25519-aes-sha2",
  425. RoomEncryptionAlgorithms.MEGOLM_V1_AES_SHA2,
  426. ],
  427. "keys": {"curve25519:xyz": "curve25519+key", "ed25519:xyz": device_pubkey},
  428. "signatures": {local_user: {"ed25519:xyz": "something"}},
  429. }
  430. device_signing_key = key.decode_signing_key_base64(
  431. "ed25519", "xyz", "OMkooTr76ega06xNvXIGPbgvvxAOzmQncN8VObS7aBA"
  432. )
  433. self.get_success(
  434. self.handler.upload_keys_for_user(
  435. local_user, device_id, {"device_keys": device_key}
  436. )
  437. )
  438. # private key: 2lonYOM6xYKdEsO+6KrC766xBcHnYnim1x/4LFGF8B0
  439. master_pubkey = "nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk"
  440. master_key = {
  441. "user_id": local_user,
  442. "usage": ["master"],
  443. "keys": {"ed25519:" + master_pubkey: master_pubkey},
  444. }
  445. master_signing_key = key.decode_signing_key_base64(
  446. "ed25519", master_pubkey, "2lonYOM6xYKdEsO+6KrC766xBcHnYnim1x/4LFGF8B0"
  447. )
  448. usersigning_pubkey = "Hq6gL+utB4ET+UvD5ci0kgAwsX6qP/zvf8v6OInU5iw"
  449. usersigning_key = {
  450. # private key: 4TL4AjRYwDVwD3pqQzcor+ez/euOB1/q78aTJ+czDNs
  451. "user_id": local_user,
  452. "usage": ["user_signing"],
  453. "keys": {"ed25519:" + usersigning_pubkey: usersigning_pubkey},
  454. }
  455. usersigning_signing_key = key.decode_signing_key_base64(
  456. "ed25519", usersigning_pubkey, "4TL4AjRYwDVwD3pqQzcor+ez/euOB1/q78aTJ+czDNs"
  457. )
  458. sign.sign_json(usersigning_key, local_user, master_signing_key)
  459. # private key: HvQBbU+hc2Zr+JP1sE0XwBe1pfZZEYtJNPJLZJtS+F8
  460. selfsigning_pubkey = "EmkqvokUn8p+vQAGZitOk4PWjp7Ukp3txV2TbMPEiBQ"
  461. selfsigning_key = {
  462. "user_id": local_user,
  463. "usage": ["self_signing"],
  464. "keys": {"ed25519:" + selfsigning_pubkey: selfsigning_pubkey},
  465. }
  466. selfsigning_signing_key = key.decode_signing_key_base64(
  467. "ed25519", selfsigning_pubkey, "HvQBbU+hc2Zr+JP1sE0XwBe1pfZZEYtJNPJLZJtS+F8"
  468. )
  469. sign.sign_json(selfsigning_key, local_user, master_signing_key)
  470. cross_signing_keys = {
  471. "master_key": master_key,
  472. "user_signing_key": usersigning_key,
  473. "self_signing_key": selfsigning_key,
  474. }
  475. self.get_success(
  476. self.handler.upload_signing_keys_for_user(local_user, cross_signing_keys)
  477. )
  478. # set up another user with a master key. This user will be signed by
  479. # the first user
  480. other_user = "@otherboris:" + self.hs.hostname
  481. other_master_pubkey = "fHZ3NPiKxoLQm5OoZbKa99SYxprOjNs4TwJUKP+twCM"
  482. other_master_key = {
  483. # private key: oyw2ZUx0O4GifbfFYM0nQvj9CL0b8B7cyN4FprtK8OI
  484. "user_id": other_user,
  485. "usage": ["master"],
  486. "keys": {"ed25519:" + other_master_pubkey: other_master_pubkey},
  487. }
  488. self.get_success(
  489. self.handler.upload_signing_keys_for_user(
  490. other_user, {"master_key": other_master_key}
  491. )
  492. )
  493. # test various signature failures (see below)
  494. ret = self.get_success(
  495. self.handler.upload_signatures_for_device_keys(
  496. local_user,
  497. {
  498. local_user: {
  499. # fails because the signature is invalid
  500. # should fail with INVALID_SIGNATURE
  501. device_id: {
  502. "user_id": local_user,
  503. "device_id": device_id,
  504. "algorithms": [
  505. "m.olm.curve25519-aes-sha2",
  506. RoomEncryptionAlgorithms.MEGOLM_V1_AES_SHA2,
  507. ],
  508. "keys": {
  509. "curve25519:xyz": "curve25519+key",
  510. # private key: OMkooTr76ega06xNvXIGPbgvvxAOzmQncN8VObS7aBA
  511. "ed25519:xyz": device_pubkey,
  512. },
  513. "signatures": {
  514. local_user: {
  515. "ed25519:" + selfsigning_pubkey: "something"
  516. }
  517. },
  518. },
  519. # fails because device is unknown
  520. # should fail with NOT_FOUND
  521. "unknown": {
  522. "user_id": local_user,
  523. "device_id": "unknown",
  524. "signatures": {
  525. local_user: {
  526. "ed25519:" + selfsigning_pubkey: "something"
  527. }
  528. },
  529. },
  530. # fails because the signature is invalid
  531. # should fail with INVALID_SIGNATURE
  532. master_pubkey: {
  533. "user_id": local_user,
  534. "usage": ["master"],
  535. "keys": {"ed25519:" + master_pubkey: master_pubkey},
  536. "signatures": {
  537. local_user: {"ed25519:" + device_pubkey: "something"}
  538. },
  539. },
  540. },
  541. other_user: {
  542. # fails because the device is not the user's master-signing key
  543. # should fail with NOT_FOUND
  544. "unknown": {
  545. "user_id": other_user,
  546. "device_id": "unknown",
  547. "signatures": {
  548. local_user: {
  549. "ed25519:" + usersigning_pubkey: "something"
  550. }
  551. },
  552. },
  553. other_master_pubkey: {
  554. # fails because the key doesn't match what the server has
  555. # should fail with UNKNOWN
  556. "user_id": other_user,
  557. "usage": ["master"],
  558. "keys": {
  559. "ed25519:" + other_master_pubkey: other_master_pubkey
  560. },
  561. "something": "random",
  562. "signatures": {
  563. local_user: {
  564. "ed25519:" + usersigning_pubkey: "something"
  565. }
  566. },
  567. },
  568. },
  569. },
  570. )
  571. )
  572. user_failures = ret["failures"][local_user]
  573. self.assertEqual(user_failures[device_id]["errcode"], Codes.INVALID_SIGNATURE)
  574. self.assertEqual(
  575. user_failures[master_pubkey]["errcode"], Codes.INVALID_SIGNATURE
  576. )
  577. self.assertEqual(user_failures["unknown"]["errcode"], Codes.NOT_FOUND)
  578. other_user_failures = ret["failures"][other_user]
  579. self.assertEqual(other_user_failures["unknown"]["errcode"], Codes.NOT_FOUND)
  580. self.assertEqual(
  581. other_user_failures[other_master_pubkey]["errcode"], Codes.UNKNOWN
  582. )
  583. # test successful signatures
  584. del device_key["signatures"]
  585. sign.sign_json(device_key, local_user, selfsigning_signing_key)
  586. sign.sign_json(master_key, local_user, device_signing_key)
  587. sign.sign_json(other_master_key, local_user, usersigning_signing_key)
  588. ret = self.get_success(
  589. self.handler.upload_signatures_for_device_keys(
  590. local_user,
  591. {
  592. local_user: {device_id: device_key, master_pubkey: master_key},
  593. other_user: {other_master_pubkey: other_master_key},
  594. },
  595. )
  596. )
  597. self.assertEqual(ret["failures"], {})
  598. # fetch the signed keys/devices and make sure that the signatures are there
  599. ret = self.get_success(
  600. self.handler.query_devices(
  601. {"device_keys": {local_user: [], other_user: []}},
  602. 0,
  603. local_user,
  604. "device123",
  605. )
  606. )
  607. self.assertEqual(
  608. ret["device_keys"][local_user]["xyz"]["signatures"][local_user][
  609. "ed25519:" + selfsigning_pubkey
  610. ],
  611. device_key["signatures"][local_user]["ed25519:" + selfsigning_pubkey],
  612. )
  613. self.assertEqual(
  614. ret["master_keys"][local_user]["signatures"][local_user][
  615. "ed25519:" + device_id
  616. ],
  617. master_key["signatures"][local_user]["ed25519:" + device_id],
  618. )
  619. self.assertEqual(
  620. ret["master_keys"][other_user]["signatures"][local_user][
  621. "ed25519:" + usersigning_pubkey
  622. ],
  623. other_master_key["signatures"][local_user]["ed25519:" + usersigning_pubkey],
  624. )
  625. def test_query_devices_remote_no_sync(self):
  626. """Tests that querying keys for a remote user that we don't share a room
  627. with returns the cross signing keys correctly.
  628. """
  629. remote_user_id = "@test:other"
  630. local_user_id = "@test:test"
  631. remote_master_key = "85T7JXPFBAySB/jwby4S3lBPTqY3+Zg53nYuGmu1ggY"
  632. remote_self_signing_key = "QeIiFEjluPBtI7WQdG365QKZcFs9kqmHir6RBD0//nQ"
  633. self.hs.get_federation_client().query_client_keys = mock.Mock(
  634. return_value=defer.succeed(
  635. {
  636. "device_keys": {remote_user_id: {}},
  637. "master_keys": {
  638. remote_user_id: {
  639. "user_id": remote_user_id,
  640. "usage": ["master"],
  641. "keys": {"ed25519:" + remote_master_key: remote_master_key},
  642. },
  643. },
  644. "self_signing_keys": {
  645. remote_user_id: {
  646. "user_id": remote_user_id,
  647. "usage": ["self_signing"],
  648. "keys": {
  649. "ed25519:"
  650. + remote_self_signing_key: remote_self_signing_key
  651. },
  652. }
  653. },
  654. }
  655. )
  656. )
  657. e2e_handler = self.hs.get_e2e_keys_handler()
  658. query_result = self.get_success(
  659. e2e_handler.query_devices(
  660. {
  661. "device_keys": {remote_user_id: []},
  662. },
  663. timeout=10,
  664. from_user_id=local_user_id,
  665. from_device_id="some_device_id",
  666. )
  667. )
  668. self.assertEqual(query_result["failures"], {})
  669. self.assertEqual(
  670. query_result["master_keys"],
  671. {
  672. remote_user_id: {
  673. "user_id": remote_user_id,
  674. "usage": ["master"],
  675. "keys": {"ed25519:" + remote_master_key: remote_master_key},
  676. },
  677. },
  678. )
  679. self.assertEqual(
  680. query_result["self_signing_keys"],
  681. {
  682. remote_user_id: {
  683. "user_id": remote_user_id,
  684. "usage": ["self_signing"],
  685. "keys": {
  686. "ed25519:" + remote_self_signing_key: remote_self_signing_key
  687. },
  688. }
  689. },
  690. )
  691. def test_query_devices_remote_sync(self):
  692. """Tests that querying keys for a remote user that we share a room with,
  693. but haven't yet fetched the keys for, returns the cross signing keys
  694. correctly.
  695. """
  696. remote_user_id = "@test:other"
  697. local_user_id = "@test:test"
  698. self.store.get_rooms_for_user = mock.Mock(
  699. return_value=defer.succeed({"some_room_id"})
  700. )
  701. remote_master_key = "85T7JXPFBAySB/jwby4S3lBPTqY3+Zg53nYuGmu1ggY"
  702. remote_self_signing_key = "QeIiFEjluPBtI7WQdG365QKZcFs9kqmHir6RBD0//nQ"
  703. self.hs.get_federation_client().query_user_devices = mock.Mock(
  704. return_value=defer.succeed(
  705. {
  706. "user_id": remote_user_id,
  707. "stream_id": 1,
  708. "devices": [],
  709. "master_key": {
  710. "user_id": remote_user_id,
  711. "usage": ["master"],
  712. "keys": {"ed25519:" + remote_master_key: remote_master_key},
  713. },
  714. "self_signing_key": {
  715. "user_id": remote_user_id,
  716. "usage": ["self_signing"],
  717. "keys": {
  718. "ed25519:"
  719. + remote_self_signing_key: remote_self_signing_key
  720. },
  721. },
  722. }
  723. )
  724. )
  725. e2e_handler = self.hs.get_e2e_keys_handler()
  726. query_result = self.get_success(
  727. e2e_handler.query_devices(
  728. {
  729. "device_keys": {remote_user_id: []},
  730. },
  731. timeout=10,
  732. from_user_id=local_user_id,
  733. from_device_id="some_device_id",
  734. )
  735. )
  736. self.assertEqual(query_result["failures"], {})
  737. self.assertEqual(
  738. query_result["master_keys"],
  739. {
  740. remote_user_id: {
  741. "user_id": remote_user_id,
  742. "usage": ["master"],
  743. "keys": {"ed25519:" + remote_master_key: remote_master_key},
  744. }
  745. },
  746. )
  747. self.assertEqual(
  748. query_result["self_signing_keys"],
  749. {
  750. remote_user_id: {
  751. "user_id": remote_user_id,
  752. "usage": ["self_signing"],
  753. "keys": {
  754. "ed25519:" + remote_self_signing_key: remote_self_signing_key
  755. },
  756. }
  757. },
  758. )