Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.

Commit aead826

Browse files
committed
Merge branch 'release-v1.12.4' of github.com:matrix-org/synapse into matrix-org-hotfixes
* 'release-v1.12.4' of github.com:matrix-org/synapse: Query missing cross-signing keys on local sig upload
2 parents 4cd2a4a + 72fe2af commit aead826

File tree

3 files changed

+141
-12
lines changed

3 files changed

+141
-12
lines changed

changelog.d/7289.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix an edge-case where it was not possible to cross-sign a user which did not share a room with any user on your homeserver. The bug only affected Synapse deployments in worker mode.

synapse/federation/transport/client.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -406,13 +406,19 @@ def query_client_keys(self, destination, query_content, timeout):
406406
"device_keys": {
407407
"<user_id>": {
408408
"<device_id>": {...}
409+
} }
410+
"master_keys": {
411+
"<user_id>": {...}
412+
} }
413+
"self_signing_keys": {
414+
"<user_id>": {...}
409415
} } }
410416
411417
Args:
412418
destination(str): The server to query.
413419
query_content(dict): The user ids to query.
414420
Returns:
415-
A dict containg the device keys.
421+
A dict containing device and cross-signing keys.
416422
"""
417423
path = _create_v1_path("/user/keys/query")
418424

@@ -429,14 +435,16 @@ def query_user_devices(self, destination, user_id, timeout):
429435
Response:
430436
{
431437
"stream_id": "...",
432-
"devices": [ { ... } ]
438+
"devices": [ { ... } ],
439+
"master_key": { ... },
440+
"self_signing_key: { ... }
433441
}
434442
435443
Args:
436444
destination(str): The server to query.
437445
query_content(dict): The user ids to query.
438446
Returns:
439-
A dict containg the device keys.
447+
A dict containing device and cross-signing keys.
440448
"""
441449
path = _create_v1_path("/user/devices/%s", user_id)
442450

synapse/handlers/e2e_keys.py

Lines changed: 129 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@
1616
# limitations under the License.
1717

1818
import logging
19+
from typing import Dict, Optional, Tuple
1920

2021
from six import iteritems
2122

2223
import attr
2324
from canonicaljson import encode_canonical_json, json
2425
from signedjson.key import decode_verify_key_bytes
2526
from signedjson.sign import SignatureVerifyException, verify_signed_json
27+
from signedjson.types import VerifyKey
2628
from unpaddedbase64 import decode_base64
2729

2830
from twisted.internet import defer
@@ -174,8 +176,8 @@ def do_remote_query(destination):
174176
"""This is called when we are querying the device list of a user on
175177
a remote homeserver and their device list is not in the device list
176178
cache. If we share a room with this user and we're not querying for
177-
specific user we will update the cache
178-
with their device list."""
179+
specific user we will update the cache with their device list.
180+
"""
179181

180182
destination_query = remote_queries_not_in_cache[destination]
181183

@@ -961,13 +963,19 @@ def _process_other_signatures(self, user_id, signatures):
961963
return signature_list, failures
962964

963965
@defer.inlineCallbacks
964-
def _get_e2e_cross_signing_verify_key(self, user_id, key_type, from_user_id=None):
965-
"""Fetch the cross-signing public key from storage and interpret it.
966+
def _get_e2e_cross_signing_verify_key(
967+
self, user_id: str, key_type: str, from_user_id: str = None
968+
):
969+
"""Fetch locally or remotely query for a cross-signing public key.
970+
971+
First, attempt to fetch the cross-signing public key from storage.
972+
If that fails, query the keys from the homeserver they belong to
973+
and update our local copy.
966974
967975
Args:
968-
user_id (str): the user whose key should be fetched
969-
key_type (str): the type of key to fetch
970-
from_user_id (str): the user that we are fetching the keys for.
976+
user_id: the user whose key should be fetched
977+
key_type: the type of key to fetch
978+
from_user_id: the user that we are fetching the keys for.
971979
This affects what signatures are fetched.
972980
973981
Returns:
@@ -976,16 +984,128 @@ def _get_e2e_cross_signing_verify_key(self, user_id, key_type, from_user_id=None
976984
977985
Raises:
978986
NotFoundError: if the key is not found
987+
SynapseError: if `user_id` is invalid
979988
"""
989+
user = UserID.from_string(user_id)
990+
key_id = None
991+
verify_key = None
992+
980993
key = yield self.store.get_e2e_cross_signing_key(
981994
user_id, key_type, from_user_id
982995
)
996+
997+
# If we couldn't find the key locally, and we're looking for keys of
998+
# another user then attempt to fetch the missing key from the remote
999+
# user's server.
1000+
#
1001+
# We may run into this in possible edge cases where a user tries to
1002+
# cross-sign a remote user, but does not share any rooms with them yet.
1003+
# Thus, we would not have their key list yet. We fetch the key here,
1004+
# store it and notify clients of new, associated device IDs.
1005+
if (
1006+
key is None
1007+
and not self.is_mine(user)
1008+
# We only get "master" and "self_signing" keys from remote servers
1009+
and key_type in ["master", "self_signing"]
1010+
):
1011+
key = yield self._retrieve_cross_signing_keys_for_remote_user(
1012+
user, key_type
1013+
)
1014+
9831015
if key is None:
984-
logger.debug("no %s key found for %s", key_type, user_id)
1016+
logger.debug("No %s key found for %s", key_type, user_id)
9851017
raise NotFoundError("No %s key found for %s" % (key_type, user_id))
986-
key_id, verify_key = get_verify_key_from_cross_signing_key(key)
1018+
1019+
# If we retrieved the keys remotely, these values will already be set
1020+
if key_id is None or verify_key is None:
1021+
try:
1022+
key_id, verify_key = get_verify_key_from_cross_signing_key(key)
1023+
except ValueError as e:
1024+
logger.debug(
1025+
"Invalid %s key retrieved: %s - %s %s", key_type, key, type(e), e,
1026+
)
1027+
raise SynapseError(
1028+
502, "Invalid %s key retrieved from remote server", key_type
1029+
)
1030+
9871031
return key, key_id, verify_key
9881032

1033+
@defer.inlineCallbacks
1034+
def _retrieve_cross_signing_keys_for_remote_user(
1035+
self, user: UserID, desired_key_type: str,
1036+
) -> Tuple[Optional[Dict], Optional[str], Optional[VerifyKey]]:
1037+
"""Queries cross-signing keys for a remote user and saves them to the database
1038+
1039+
Only the key specified by `key_type` will be returned, while all retrieved keys
1040+
will be saved regardless
1041+
1042+
Args:
1043+
user: The user to query remote keys for
1044+
desired_key_type: The type of key to receive. One of "master", "self_signing"
1045+
1046+
Returns:
1047+
A tuple of the retrieved key content, the key's ID and the matching VerifyKey.
1048+
If the key cannot be retrieved, all values in the tuple will instead be None.
1049+
"""
1050+
try:
1051+
remote_result = yield self.federation.query_user_devices(
1052+
user.domain, user.to_string()
1053+
)
1054+
except Exception as e:
1055+
logger.warning(
1056+
"Unable to query %s for cross-signing keys of user %s: %s %s",
1057+
user.domain,
1058+
user.to_string(),
1059+
type(e),
1060+
e,
1061+
)
1062+
return None
1063+
1064+
# Process each of the retrieved cross-signing keys
1065+
final_key = None
1066+
final_key_id = None
1067+
final_verify_key = None
1068+
device_ids = []
1069+
for key_type in ["master", "self_signing"]:
1070+
key_content = remote_result.get(key_type + "_key")
1071+
if not key_content:
1072+
continue
1073+
1074+
# At the same time, store this key in the db for
1075+
# subsequent queries
1076+
yield self.store.set_e2e_cross_signing_key(
1077+
user.to_string(), key_type, key_content
1078+
)
1079+
1080+
# Note down the device ID attached to this key
1081+
try:
1082+
# verify_key is a VerifyKey from signedjson, which uses
1083+
# .version to denote the portion of the key ID after the
1084+
# algorithm and colon, which is the device ID
1085+
key_id, verify_key = get_verify_key_from_cross_signing_key(key_content)
1086+
except ValueError as e:
1087+
logger.debug(
1088+
"Invalid %s key retrieved: %s - %s %s",
1089+
key_type,
1090+
key_content,
1091+
type(e),
1092+
e,
1093+
)
1094+
continue
1095+
device_ids.append(verify_key.version)
1096+
1097+
# If this is the desired key type, save it and it's ID/VerifyKey
1098+
if key_type == desired_key_type:
1099+
final_key = key_content
1100+
final_verify_key = verify_key
1101+
final_key_id = key_id
1102+
1103+
# Notify clients that new devices for this user have been discovered
1104+
if device_ids:
1105+
yield self.device_handler.notify_device_update(user.to_string(), device_ids)
1106+
1107+
return final_key, final_key_id, final_verify_key
1108+
9891109

9901110
def _check_cross_signing_key(key, user_id, key_type, signing_key=None):
9911111
"""Check a cross-signing key uploaded by a user. Performs some basic sanity

0 commit comments

Comments
 (0)