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.
 
 
 
 
 
 

274 lines
9.8 KiB

  1. # Copyright 2014-2016 OpenMarket Ltd
  2. # Copyright 2019 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 itertools
  16. import json
  17. import logging
  18. from typing import Dict, Iterable, Optional, Tuple
  19. from canonicaljson import encode_canonical_json
  20. from signedjson.key import decode_verify_key_bytes
  21. from unpaddedbase64 import decode_base64
  22. from synapse.storage.database import LoggingTransaction
  23. from synapse.storage.databases.main.cache import CacheInvalidationWorkerStore
  24. from synapse.storage.keys import FetchKeyResult, FetchKeyResultForRemote
  25. from synapse.storage.types import Cursor
  26. from synapse.types import JsonDict
  27. from synapse.util.caches.descriptors import cached, cachedList
  28. from synapse.util.iterutils import batch_iter
  29. logger = logging.getLogger(__name__)
  30. db_binary_type = memoryview
  31. class KeyStore(CacheInvalidationWorkerStore):
  32. """Persistence for signature verification keys"""
  33. async def store_server_keys_response(
  34. self,
  35. server_name: str,
  36. from_server: str,
  37. ts_added_ms: int,
  38. verify_keys: Dict[str, FetchKeyResult],
  39. response_json: JsonDict,
  40. ) -> None:
  41. """Stores the keys for the given server that we got from `from_server`.
  42. Args:
  43. server_name: The owner of the keys
  44. from_server: Which server we got the keys from
  45. ts_added_ms: When we're adding the keys
  46. verify_keys: The decoded keys
  47. response_json: The full *signed* response JSON that contains the keys.
  48. """
  49. key_json_bytes = encode_canonical_json(response_json)
  50. def store_server_keys_response_txn(txn: LoggingTransaction) -> None:
  51. self.db_pool.simple_upsert_many_txn(
  52. txn,
  53. table="server_signature_keys",
  54. key_names=("server_name", "key_id"),
  55. key_values=[(server_name, key_id) for key_id in verify_keys],
  56. value_names=(
  57. "from_server",
  58. "ts_added_ms",
  59. "ts_valid_until_ms",
  60. "verify_key",
  61. ),
  62. value_values=[
  63. (
  64. from_server,
  65. ts_added_ms,
  66. fetch_result.valid_until_ts,
  67. db_binary_type(fetch_result.verify_key.encode()),
  68. )
  69. for fetch_result in verify_keys.values()
  70. ],
  71. )
  72. self.db_pool.simple_upsert_many_txn(
  73. txn,
  74. table="server_keys_json",
  75. key_names=("server_name", "key_id", "from_server"),
  76. key_values=[
  77. (server_name, key_id, from_server) for key_id in verify_keys
  78. ],
  79. value_names=(
  80. "ts_added_ms",
  81. "ts_valid_until_ms",
  82. "key_json",
  83. ),
  84. value_values=[
  85. (
  86. ts_added_ms,
  87. fetch_result.valid_until_ts,
  88. db_binary_type(key_json_bytes),
  89. )
  90. for fetch_result in verify_keys.values()
  91. ],
  92. )
  93. # invalidate takes a tuple corresponding to the params of
  94. # _get_server_keys_json. _get_server_keys_json only takes one
  95. # param, which is itself the 2-tuple (server_name, key_id).
  96. for key_id in verify_keys:
  97. self._invalidate_cache_and_stream(
  98. txn, self._get_server_keys_json, ((server_name, key_id),)
  99. )
  100. self._invalidate_cache_and_stream(
  101. txn, self.get_server_key_json_for_remote, (server_name, key_id)
  102. )
  103. await self.db_pool.runInteraction(
  104. "store_server_keys_response", store_server_keys_response_txn
  105. )
  106. @cached()
  107. def _get_server_keys_json(
  108. self, server_name_and_key_id: Tuple[str, str]
  109. ) -> FetchKeyResult:
  110. raise NotImplementedError()
  111. @cachedList(
  112. cached_method_name="_get_server_keys_json", list_name="server_name_and_key_ids"
  113. )
  114. async def get_server_keys_json(
  115. self, server_name_and_key_ids: Iterable[Tuple[str, str]]
  116. ) -> Dict[Tuple[str, str], FetchKeyResult]:
  117. """
  118. Args:
  119. server_name_and_key_ids:
  120. iterable of (server_name, key-id) tuples to fetch keys for
  121. Returns:
  122. A map from (server_name, key_id) -> FetchKeyResult, or None if the
  123. key is unknown
  124. """
  125. keys = {}
  126. def _get_keys(txn: Cursor, batch: Tuple[Tuple[str, str], ...]) -> None:
  127. """Processes a batch of keys to fetch, and adds the result to `keys`."""
  128. # batch_iter always returns tuples so it's safe to do len(batch)
  129. where_clause = " OR (server_name=? AND key_id=?)" * len(batch)
  130. # `server_keys_json` can have multiple entries per server (one per
  131. # remote server we fetched from, if using perspectives). Order by
  132. # `ts_added_ms` so the most recently fetched one always wins.
  133. sql = f"""
  134. SELECT server_name, key_id, key_json, ts_valid_until_ms
  135. FROM server_keys_json WHERE 1=0
  136. {where_clause}
  137. ORDER BY ts_added_ms
  138. """
  139. txn.execute(sql, tuple(itertools.chain.from_iterable(batch)))
  140. for server_name, key_id, key_json_bytes, ts_valid_until_ms in txn:
  141. if ts_valid_until_ms is None:
  142. # Old keys may be stored with a ts_valid_until_ms of null,
  143. # in which case we treat this as if it was set to `0`, i.e.
  144. # it won't match key requests that define a minimum
  145. # `ts_valid_until_ms`.
  146. ts_valid_until_ms = 0
  147. # The entire signed JSON response is stored in server_keys_json,
  148. # fetch out the bits needed.
  149. key_json = json.loads(bytes(key_json_bytes))
  150. key_base64 = key_json["verify_keys"][key_id]["key"]
  151. keys[(server_name, key_id)] = FetchKeyResult(
  152. verify_key=decode_verify_key_bytes(
  153. key_id, decode_base64(key_base64)
  154. ),
  155. valid_until_ts=ts_valid_until_ms,
  156. )
  157. def _txn(txn: Cursor) -> Dict[Tuple[str, str], FetchKeyResult]:
  158. for batch in batch_iter(server_name_and_key_ids, 50):
  159. _get_keys(txn, batch)
  160. return keys
  161. return await self.db_pool.runInteraction("get_server_keys_json", _txn)
  162. @cached()
  163. def get_server_key_json_for_remote(
  164. self,
  165. server_name: str,
  166. key_id: str,
  167. ) -> Optional[FetchKeyResultForRemote]:
  168. raise NotImplementedError()
  169. @cachedList(
  170. cached_method_name="get_server_key_json_for_remote", list_name="key_ids"
  171. )
  172. async def get_server_keys_json_for_remote(
  173. self, server_name: str, key_ids: Iterable[str]
  174. ) -> Dict[str, Optional[FetchKeyResultForRemote]]:
  175. """Fetch the cached keys for the given server/key IDs.
  176. If we have multiple entries for a given key ID, returns the most recent.
  177. """
  178. rows = await self.db_pool.simple_select_many_batch(
  179. table="server_keys_json",
  180. column="key_id",
  181. iterable=key_ids,
  182. keyvalues={"server_name": server_name},
  183. retcols=(
  184. "key_id",
  185. "from_server",
  186. "ts_added_ms",
  187. "ts_valid_until_ms",
  188. "key_json",
  189. ),
  190. desc="get_server_keys_json_for_remote",
  191. )
  192. if not rows:
  193. return {}
  194. # We sort the rows so that the most recently added entry is picked up.
  195. rows.sort(key=lambda r: r["ts_added_ms"])
  196. return {
  197. row["key_id"]: FetchKeyResultForRemote(
  198. # Cast to bytes since postgresql returns a memoryview.
  199. key_json=bytes(row["key_json"]),
  200. valid_until_ts=row["ts_valid_until_ms"],
  201. added_ts=row["ts_added_ms"],
  202. )
  203. for row in rows
  204. }
  205. async def get_all_server_keys_json_for_remote(
  206. self,
  207. server_name: str,
  208. ) -> Dict[str, FetchKeyResultForRemote]:
  209. """Fetch the cached keys for the given server.
  210. If we have multiple entries for a given key ID, returns the most recent.
  211. """
  212. rows = await self.db_pool.simple_select_list(
  213. table="server_keys_json",
  214. keyvalues={"server_name": server_name},
  215. retcols=(
  216. "key_id",
  217. "from_server",
  218. "ts_added_ms",
  219. "ts_valid_until_ms",
  220. "key_json",
  221. ),
  222. desc="get_server_keys_json_for_remote",
  223. )
  224. if not rows:
  225. return {}
  226. rows.sort(key=lambda r: r["ts_added_ms"])
  227. return {
  228. row["key_id"]: FetchKeyResultForRemote(
  229. # Cast to bytes since postgresql returns a memoryview.
  230. key_json=bytes(row["key_json"]),
  231. valid_until_ts=row["ts_valid_until_ms"],
  232. added_ts=row["ts_added_ms"],
  233. )
  234. for row in rows
  235. }