e2e_room_keys.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2017 New Vector Ltd
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. import json
  16. from twisted.internet import defer
  17. from synapse.api.errors import StoreError
  18. from ._base import SQLBaseStore
  19. class EndToEndRoomKeyStore(SQLBaseStore):
  20. @defer.inlineCallbacks
  21. def get_e2e_room_key(self, user_id, version, room_id, session_id):
  22. """Get the encrypted E2E room key for a given session from a given
  23. backup version of room_keys. We only store the 'best' room key for a given
  24. session at a given time, as determined by the handler.
  25. Args:
  26. user_id(str): the user whose backup we're querying
  27. version(str): the version ID of the backup for the set of keys we're querying
  28. room_id(str): the ID of the room whose keys we're querying.
  29. This is a bit redundant as it's implied by the session_id, but
  30. we include for consistency with the rest of the API.
  31. session_id(str): the session whose room_key we're querying.
  32. Returns:
  33. A deferred dict giving the session_data and message metadata for
  34. this room key.
  35. """
  36. row = yield self._simple_select_one(
  37. table="e2e_room_keys",
  38. keyvalues={
  39. "user_id": user_id,
  40. "version": version,
  41. "room_id": room_id,
  42. "session_id": session_id,
  43. },
  44. retcols=(
  45. "first_message_index",
  46. "forwarded_count",
  47. "is_verified",
  48. "session_data",
  49. ),
  50. desc="get_e2e_room_key",
  51. )
  52. row["session_data"] = json.loads(row["session_data"])
  53. defer.returnValue(row)
  54. @defer.inlineCallbacks
  55. def set_e2e_room_key(self, user_id, version, room_id, session_id, room_key):
  56. """Replaces or inserts the encrypted E2E room key for a given session in
  57. a given backup
  58. Args:
  59. user_id(str): the user whose backup we're setting
  60. version(str): the version ID of the backup we're updating
  61. room_id(str): the ID of the room whose keys we're setting
  62. session_id(str): the session whose room_key we're setting
  63. room_key(dict): the room_key being set
  64. Raises:
  65. StoreError
  66. """
  67. yield self._simple_upsert(
  68. table="e2e_room_keys",
  69. keyvalues={
  70. "user_id": user_id,
  71. "room_id": room_id,
  72. "session_id": session_id,
  73. },
  74. values={
  75. "version": version,
  76. "first_message_index": room_key['first_message_index'],
  77. "forwarded_count": room_key['forwarded_count'],
  78. "is_verified": room_key['is_verified'],
  79. "session_data": json.dumps(room_key['session_data']),
  80. },
  81. lock=False,
  82. )
  83. @defer.inlineCallbacks
  84. def get_e2e_room_keys(
  85. self, user_id, version, room_id=None, session_id=None
  86. ):
  87. """Bulk get the E2E room keys for a given backup, optionally filtered to a given
  88. room, or a given session.
  89. Args:
  90. user_id(str): the user whose backup we're querying
  91. version(str): the version ID of the backup for the set of keys we're querying
  92. room_id(str): Optional. the ID of the room whose keys we're querying, if any.
  93. If not specified, we return the keys for all the rooms in the backup.
  94. session_id(str): Optional. the session whose room_key we're querying, if any.
  95. If specified, we also require the room_id to be specified.
  96. If not specified, we return all the keys in this version of
  97. the backup (or for the specified room)
  98. Returns:
  99. A deferred list of dicts giving the session_data and message metadata for
  100. these room keys.
  101. """
  102. try:
  103. version = int(version)
  104. except ValueError:
  105. defer.returnValue({'rooms': {}})
  106. keyvalues = {
  107. "user_id": user_id,
  108. "version": version,
  109. }
  110. if room_id:
  111. keyvalues['room_id'] = room_id
  112. if session_id:
  113. keyvalues['session_id'] = session_id
  114. rows = yield self._simple_select_list(
  115. table="e2e_room_keys",
  116. keyvalues=keyvalues,
  117. retcols=(
  118. "user_id",
  119. "room_id",
  120. "session_id",
  121. "first_message_index",
  122. "forwarded_count",
  123. "is_verified",
  124. "session_data",
  125. ),
  126. desc="get_e2e_room_keys",
  127. )
  128. sessions = {'rooms': {}}
  129. for row in rows:
  130. room_entry = sessions['rooms'].setdefault(row['room_id'], {"sessions": {}})
  131. room_entry['sessions'][row['session_id']] = {
  132. "first_message_index": row["first_message_index"],
  133. "forwarded_count": row["forwarded_count"],
  134. "is_verified": row["is_verified"],
  135. "session_data": json.loads(row["session_data"]),
  136. }
  137. defer.returnValue(sessions)
  138. @defer.inlineCallbacks
  139. def delete_e2e_room_keys(
  140. self, user_id, version, room_id=None, session_id=None
  141. ):
  142. """Bulk delete the E2E room keys for a given backup, optionally filtered to a given
  143. room or a given session.
  144. Args:
  145. user_id(str): the user whose backup we're deleting from
  146. version(str): the version ID of the backup for the set of keys we're deleting
  147. room_id(str): Optional. the ID of the room whose keys we're deleting, if any.
  148. If not specified, we delete the keys for all the rooms in the backup.
  149. session_id(str): Optional. the session whose room_key we're querying, if any.
  150. If specified, we also require the room_id to be specified.
  151. If not specified, we delete all the keys in this version of
  152. the backup (or for the specified room)
  153. Returns:
  154. A deferred of the deletion transaction
  155. """
  156. keyvalues = {
  157. "user_id": user_id,
  158. "version": int(version),
  159. }
  160. if room_id:
  161. keyvalues['room_id'] = room_id
  162. if session_id:
  163. keyvalues['session_id'] = session_id
  164. yield self._simple_delete(
  165. table="e2e_room_keys",
  166. keyvalues=keyvalues,
  167. desc="delete_e2e_room_keys",
  168. )
  169. @staticmethod
  170. def _get_current_version(txn, user_id):
  171. txn.execute(
  172. "SELECT MAX(version) FROM e2e_room_keys_versions "
  173. "WHERE user_id=? AND deleted=0",
  174. (user_id,)
  175. )
  176. row = txn.fetchone()
  177. if not row:
  178. raise StoreError(404, 'No current backup version')
  179. return row[0]
  180. def get_e2e_room_keys_version_info(self, user_id, version=None):
  181. """Get info metadata about a version of our room_keys backup.
  182. Args:
  183. user_id(str): the user whose backup we're querying
  184. version(str): Optional. the version ID of the backup we're querying about
  185. If missing, we return the information about the current version.
  186. Raises:
  187. StoreError: with code 404 if there are no e2e_room_keys_versions present
  188. Returns:
  189. A deferred dict giving the info metadata for this backup version, with
  190. fields including:
  191. version(str)
  192. algorithm(str)
  193. auth_data(object): opaque dict supplied by the client
  194. """
  195. def _get_e2e_room_keys_version_info_txn(txn):
  196. if version is None:
  197. this_version = self._get_current_version(txn, user_id)
  198. else:
  199. try:
  200. this_version = int(version)
  201. except ValueError:
  202. # Our versions are all ints so if we can't convert it to an integer,
  203. # it isn't there.
  204. raise StoreError(404, "No row found")
  205. result = self._simple_select_one_txn(
  206. txn,
  207. table="e2e_room_keys_versions",
  208. keyvalues={
  209. "user_id": user_id,
  210. "version": this_version,
  211. "deleted": 0,
  212. },
  213. retcols=(
  214. "version",
  215. "algorithm",
  216. "auth_data",
  217. ),
  218. )
  219. result["auth_data"] = json.loads(result["auth_data"])
  220. result["version"] = str(result["version"])
  221. return result
  222. return self.runInteraction(
  223. "get_e2e_room_keys_version_info",
  224. _get_e2e_room_keys_version_info_txn
  225. )
  226. def create_e2e_room_keys_version(self, user_id, info):
  227. """Atomically creates a new version of this user's e2e_room_keys store
  228. with the given version info.
  229. Args:
  230. user_id(str): the user whose backup we're creating a version
  231. info(dict): the info about the backup version to be created
  232. Returns:
  233. A deferred string for the newly created version ID
  234. """
  235. def _create_e2e_room_keys_version_txn(txn):
  236. txn.execute(
  237. "SELECT MAX(version) FROM e2e_room_keys_versions WHERE user_id=?",
  238. (user_id,)
  239. )
  240. current_version = txn.fetchone()[0]
  241. if current_version is None:
  242. current_version = '0'
  243. new_version = str(int(current_version) + 1)
  244. self._simple_insert_txn(
  245. txn,
  246. table="e2e_room_keys_versions",
  247. values={
  248. "user_id": user_id,
  249. "version": new_version,
  250. "algorithm": info["algorithm"],
  251. "auth_data": json.dumps(info["auth_data"]),
  252. },
  253. )
  254. return new_version
  255. return self.runInteraction(
  256. "create_e2e_room_keys_version_txn", _create_e2e_room_keys_version_txn
  257. )
  258. def update_e2e_room_keys_version(self, user_id, version, info):
  259. """Update a given backup version
  260. Args:
  261. user_id(str): the user whose backup version we're updating
  262. version(str): the version ID of the backup version we're updating
  263. info(dict): the new backup version info to store
  264. """
  265. return self._simple_update(
  266. table="e2e_room_keys_versions",
  267. keyvalues={
  268. "user_id": user_id,
  269. "version": version,
  270. },
  271. updatevalues={
  272. "auth_data": json.dumps(info["auth_data"]),
  273. },
  274. desc="update_e2e_room_keys_version"
  275. )
  276. def delete_e2e_room_keys_version(self, user_id, version=None):
  277. """Delete a given backup version of the user's room keys.
  278. Doesn't delete their actual key data.
  279. Args:
  280. user_id(str): the user whose backup version we're deleting
  281. version(str): Optional. the version ID of the backup version we're deleting
  282. If missing, we delete the current backup version info.
  283. Raises:
  284. StoreError: with code 404 if there are no e2e_room_keys_versions present,
  285. or if the version requested doesn't exist.
  286. """
  287. def _delete_e2e_room_keys_version_txn(txn):
  288. if version is None:
  289. this_version = self._get_current_version(txn, user_id)
  290. else:
  291. this_version = version
  292. return self._simple_update_one_txn(
  293. txn,
  294. table="e2e_room_keys_versions",
  295. keyvalues={
  296. "user_id": user_id,
  297. "version": this_version,
  298. },
  299. updatevalues={
  300. "deleted": 1,
  301. }
  302. )
  303. return self.runInteraction(
  304. "delete_e2e_room_keys_version",
  305. _delete_e2e_room_keys_version_txn
  306. )