diff options
author | Hubert Chathi <hubert@uhoreg.ca> | 2020-09-03 16:35:11 -0400 |
---|---|---|
committer | Hubert Chathi <hubert@uhoreg.ca> | 2020-09-03 16:35:11 -0400 |
commit | 4f1619c4eb0570583b697462600ce84e81a9c3ba (patch) | |
tree | 98beb2a31e069925f9eddea6eb8fd67698efa8e3 | |
parent | fix nonexistent reference (diff) | |
download | synapse-4f1619c4eb0570583b697462600ce84e81a9c3ba.tar.xz |
add support for fallback keys
-rw-r--r-- | synapse/handlers/e2e_keys.py | 16 | ||||
-rw-r--r-- | synapse/handlers/sync.py | 8 | ||||
-rw-r--r-- | synapse/rest/client/v2_alpha/sync.py | 1 | ||||
-rw-r--r-- | synapse/storage/databases/main/end_to_end_keys.py | 80 | ||||
-rw-r--r-- | synapse/storage/databases/main/schema/delta/58/11fallback.sql | 24 |
5 files changed, 129 insertions, 0 deletions
diff --git a/synapse/handlers/e2e_keys.py b/synapse/handlers/e2e_keys.py index 84169c1022..0c37829afc 100644 --- a/synapse/handlers/e2e_keys.py +++ b/synapse/handlers/e2e_keys.py @@ -496,6 +496,22 @@ class E2eKeysHandler(object): log_kv( {"message": "Did not update one_time_keys", "reason": "no keys given"} ) + fallback_keys = keys.get("fallback_keys", None) + if fallback_keys and isinstance(fallback_keys, dict): + log_kv( + { + "message": "Updating fallback_keys for device.", + "user_id": user_id, + "device_id": device_id, + } + ) + await self.store.set_e2e_fallback_keys( + user_id, device_id, fallback_keys + ) + else: + log_kv( + {"message": "Did not update fallback_keys", "reason": "no keys given"} + ) # the device should have been registered already, but it may have been # deleted due to a race with a DELETE request. Or we may be using an diff --git a/synapse/handlers/sync.py b/synapse/handlers/sync.py index 5a19bac929..5b814731ca 100644 --- a/synapse/handlers/sync.py +++ b/synapse/handlers/sync.py @@ -204,6 +204,8 @@ class SyncResult: device_lists: List of user_ids whose devices have changed device_one_time_keys_count: Dict of algorithm to count for one time keys for this device + device_unused_fallback_keys: List of key types that have an unused fallback + key groups: Group updates, if any """ @@ -216,6 +218,7 @@ class SyncResult: to_device = attr.ib(type=List[JsonDict]) device_lists = attr.ib(type=DeviceLists) device_one_time_keys_count = attr.ib(type=JsonDict) + device_unused_fallback_keys = attr.ib(type=List[str]) groups = attr.ib(type=Optional[GroupsSyncResult]) def __nonzero__(self) -> bool: @@ -1025,10 +1028,14 @@ class SyncHandler(object): logger.debug("Fetching OTK data") device_id = sync_config.device_id one_time_key_counts = {} # type: JsonDict + unused_fallback_keys = [] # type: list if device_id: one_time_key_counts = await self.store.count_e2e_one_time_keys( user_id, device_id ) + unused_fallback_keys = await self.store.get_e2e_unused_fallback_keys( + user_id, device_id + ) logger.debug("Fetching group data") await self._generate_sync_entry_for_groups(sync_result_builder) @@ -1052,6 +1059,7 @@ class SyncHandler(object): device_lists=device_lists, groups=sync_result_builder.groups, device_one_time_keys_count=one_time_key_counts, + device_unused_fallback_keys=unused_fallback_keys, next_batch=sync_result_builder.now_token, ) diff --git a/synapse/rest/client/v2_alpha/sync.py b/synapse/rest/client/v2_alpha/sync.py index 3f5bf75e59..05cbd9e2eb 100644 --- a/synapse/rest/client/v2_alpha/sync.py +++ b/synapse/rest/client/v2_alpha/sync.py @@ -237,6 +237,7 @@ class SyncRestServlet(RestServlet): "leave": sync_result.groups.leave, }, "device_one_time_keys_count": sync_result.device_one_time_keys_count, + "device_unused_fallback_keys": sync_result.device_unused_fallback_keys, "next_batch": sync_result.next_batch.to_string(), } diff --git a/synapse/storage/databases/main/end_to_end_keys.py b/synapse/storage/databases/main/end_to_end_keys.py index 23f04a4887..f90cd80561 100644 --- a/synapse/storage/databases/main/end_to_end_keys.py +++ b/synapse/storage/databases/main/end_to_end_keys.py @@ -268,6 +268,46 @@ class EndToEndKeyWorkerStore(SQLBaseStore): "count_e2e_one_time_keys", _count_e2e_one_time_keys ) + async def set_e2e_fallback_keys( + self, user_id: str, device_id: str, fallback_keys: dict + ): + # fallback_keys will usually only have one item in it, so using a for + # loop (as opposed to calling simple_upsert_many_txn) won't be too bad + # FIXME: make sure that only one key per algorithm is uploaded + for key_id, fallback_key in fallback_keys.items(): + algorithm, key_id = key_id.split(":", 1) + await self.db_pool.simple_upsert( + "e2e_fallback_keys_json", + keyvalues={ + "user_id": user_id, + "device_id": device_id, + "algorithm": algorithm + }, + values={ + "key_id": key_id, + "key_json": json.dumps(fallback_key), + "used": 0 + }, + desc="set_e2e_fallback_key" + ) + + @cached(max_entries=10000) + async def get_e2e_unused_fallback_keys( + self, user_id: str, device_id: str + ): + return await self.db_pool.simple_select_onecol( + "e2e_fallback_keys_json", + keyvalues={ + "user_id": user_id, + "device_id": device_id, + "used": 0 + }, + retcol="algorithm", + desc="get_e2e_unused_fallback_keys" + ) + + # FIXME: delete fallbacks when user logs out + @defer.inlineCallbacks def get_e2e_cross_signing_key(self, user_id, key_type, from_user_id=None): """Returns a user's cross-signing key. @@ -588,15 +628,29 @@ class EndToEndKeyStore(EndToEndKeyWorkerStore, SQLBaseStore): " WHERE user_id = ? AND device_id = ? AND algorithm = ?" " LIMIT 1" ) + fallback_sql = ( + "SELECT key_id, key_json, used FROM e2e_fallback_keys_json" + " WHERE user_id = ? AND device_id = ? AND algorithm = ?" + " LIMIT 1" + ) result = {} delete = [] + used_fallbacks = [] for user_id, device_id, algorithm in query_list: user_result = result.setdefault(user_id, {}) device_result = user_result.setdefault(device_id, {}) txn.execute(sql, (user_id, device_id, algorithm)) + found = False for key_id, key_json in txn: + found = True device_result[algorithm + ":" + key_id] = key_json delete.append((user_id, device_id, algorithm, key_id)) + if not found: + txn.execute(fallback_sql, (user_id, device_id, algorithm)) + for key_id, key_json, used in txn: + device_result[algorithm + ":" + key_id] = key_json + if used == 0: + used_fallbacks.append((user_id, device_id, algorithm, key_id)) sql = ( "DELETE FROM e2e_one_time_keys_json" " WHERE user_id = ? AND device_id = ? AND algorithm = ?" @@ -613,6 +667,23 @@ class EndToEndKeyStore(EndToEndKeyWorkerStore, SQLBaseStore): self._invalidate_cache_and_stream( txn, self.count_e2e_one_time_keys, (user_id, device_id) ) + for user_id, device_id, algorithm, key_id in used_fallbacks: + self.db_pool.simple_update_txn( + txn, + "e2e_fallback_keys_json", + { + "user_id": user_id, + "device_id": device_id, + "algorithm": algorithm, + "key_id": key_id + }, + { + "used": 1 + } + ) + self._invalidate_cache_and_stream( + txn, self.get_e2e_unused_fallback_keys, (user_id, device_id) + ) return result return self.db_pool.runInteraction( @@ -646,6 +717,15 @@ class EndToEndKeyStore(EndToEndKeyWorkerStore, SQLBaseStore): table="dehydrated_devices", keyvalues={"user_id": user_id, "device_id": device_id}, ) + self.db_pool.simple_delete_txn( + txn, + table="e2e_fallback_keys_json", + keyvalues={"user_id": user_id, "device_id": device_id}, + ) + self._invalidate_cache_and_stream( + txn, self.get_e2e_unused_fallback_keys, (user_id, device_id) + ) + return self.db_pool.runInteraction( "delete_e2e_keys_by_device", delete_e2e_keys_by_device_txn diff --git a/synapse/storage/databases/main/schema/delta/58/11fallback.sql b/synapse/storage/databases/main/schema/delta/58/11fallback.sql new file mode 100644 index 0000000000..272314a4a8 --- /dev/null +++ b/synapse/storage/databases/main/schema/delta/58/11fallback.sql @@ -0,0 +1,24 @@ +/* Copyright 2020 The Matrix.org Foundation C.I.C + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +CREATE TABLE IF NOT EXISTS e2e_fallback_keys_json ( + user_id TEXT NOT NULL, -- The user this fallback key is for. + device_id TEXT NOT NULL, -- The device this fallback key is for. + algorithm TEXT NOT NULL, -- Which algorithm this fallback key is for. + key_id TEXT NOT NULL, -- An id for suppressing duplicate uploads. + key_json TEXT NOT NULL, -- The key as a JSON blob. + used SMALLINT NOT NULL DEFAULT 0, -- Whether the key has been used or not. + CONSTRAINT e2e_fallback_keys_json_uniqueness UNIQUE (user_id, device_id, algorithm) +); |