Send device updates, modeled after SigningKeyEduUpdater._handle_signing_key_updates

pull/7289/head
Andrew Morgan 2020-04-17 12:30:54 +01:00
parent f41730078e
commit 2f8705143f
1 changed files with 56 additions and 22 deletions

View File

@ -16,7 +16,7 @@
# limitations under the License. # limitations under the License.
import logging import logging
from typing import Dict, Optional from typing import Dict, Optional, Tuple
from six import iteritems from six import iteritems
@ -24,6 +24,7 @@ import attr
from canonicaljson import encode_canonical_json, json from canonicaljson import encode_canonical_json, json
from signedjson.key import decode_verify_key_bytes from signedjson.key import decode_verify_key_bytes
from signedjson.sign import SignatureVerifyException, verify_signed_json from signedjson.sign import SignatureVerifyException, verify_signed_json
from signedjson.types import VerifyKey
from unpaddedbase64 import decode_base64 from unpaddedbase64 import decode_base64
from twisted.internet import defer from twisted.internet import defer
@ -986,17 +987,21 @@ class E2eKeysHandler(object):
SynapseError: if `user_id` is invalid SynapseError: if `user_id` is invalid
""" """
user = UserID.from_string(user_id) user = UserID.from_string(user_id)
key_id = None
verify_key = None
key = yield self.store.get_e2e_cross_signing_key( key = yield self.store.get_e2e_cross_signing_key(
user_id, key_type, from_user_id user_id, key_type, from_user_id
) )
# If we still can't find the key, and we're looking for keys of another user # If we couldn't find the key locally, and we're looking for keys of
# then attempt to fetch the missing key from the remote user's server. # another user then attempt to fetch the missing key from the remote
# user's server.
# #
# We may run into this in possible edge cases where a user tries to # We may run into this in possible edge cases where a user tries to
# cross-sign a remote user, but does not share any rooms with them yet. # cross-sign a remote user, but does not share any rooms with them yet.
# Thus, we would not have their key list yet. We fetch the key here and # Thus, we would not have their key list yet. We fetch the key here,
# store it just in case. # store it and notify clients of new, associated device IDs.
if ( if (
key is None key is None
and not self.is_mine(user) and not self.is_mine(user)
@ -1011,22 +1016,24 @@ class E2eKeysHandler(object):
logger.debug("No %s key found for %s", key_type, user_id) logger.debug("No %s key found for %s", key_type, user_id)
raise NotFoundError("No %s key found for %s" % (key_type, user_id)) raise NotFoundError("No %s key found for %s" % (key_type, user_id))
try: # If we retrieved the keys remotely, these values will already be set
key_id, verify_key = get_verify_key_from_cross_signing_key(key) if key_id is None or verify_key is None:
except ValueError as e: try:
logger.debug( key_id, verify_key = get_verify_key_from_cross_signing_key(key)
"Invalid %s key retrieved: %s - %s %s", key_type, key, type(e), e, except ValueError as e:
) logger.debug(
raise SynapseError( "Invalid %s key retrieved: %s - %s %s", key_type, key, type(e), e,
502, "Invalid %s key retrieved from remote server", key_type )
) raise SynapseError(
502, "Invalid %s key retrieved from remote server", key_type
)
return key, key_id, verify_key return key, key_id, verify_key
@defer.inlineCallbacks @defer.inlineCallbacks
def _retrieve_cross_signing_keys_for_remote_user( def _retrieve_cross_signing_keys_for_remote_user(
self, user: UserID, desired_key_type: str, self, user: UserID, desired_key_type: str,
) -> Optional[Dict]: ) -> Tuple[Optional[Dict], Optional[str], Optional[VerifyKey]]:
"""Queries cross-signing keys for a remote user and saves them to the database """Queries cross-signing keys for a remote user and saves them to the database
Only the key specified by `key_type` will be returned, while all retrieved keys Only the key specified by `key_type` will be returned, while all retrieved keys
@ -1037,7 +1044,8 @@ class E2eKeysHandler(object):
desired_key_type: The type of key to receive. One of "master", "self_signing" desired_key_type: The type of key to receive. One of "master", "self_signing"
Returns: Returns:
The retrieved key content, or None if the key could not be retrieved A tuple of the retrieved key content, the key's ID and the matching VerifyKey.
If the key cannot be retrieved, all values in the tuple will instead be None.
""" """
try: try:
remote_result = yield self.federation.query_user_devices( remote_result = yield self.federation.query_user_devices(
@ -1054,23 +1062,49 @@ class E2eKeysHandler(object):
return None return None
# Process each of the retrieved cross-signing keys # Process each of the retrieved cross-signing keys
key = None final_key = None
final_key_id = None
final_verify_key = None
device_ids = []
for key_type in ["master", "self_signing"]: for key_type in ["master", "self_signing"]:
key_content = remote_result.get(key_type + "_key") key_content = remote_result.get(key_type + "_key")
if not key_content: if not key_content:
continue continue
# If this is the desired key type, return it
if key_type == desired_key_type:
key = key_content
# At the same time, store this key in the db for # At the same time, store this key in the db for
# subsequent queries # subsequent queries
yield self.store.set_e2e_cross_signing_key( yield self.store.set_e2e_cross_signing_key(
user.to_string(), key_type, key_content user.to_string(), key_type, key_content
) )
return key # Note down the device ID attached to this key
try:
# verify_key is a VerifyKey from signedjson, which uses
# .version to denote the portion of the key ID after the
# algorithm and colon, which is the device ID
key_id, verify_key = get_verify_key_from_cross_signing_key(key_content)
except ValueError as e:
logger.debug(
"Invalid %s key retrieved: %s - %s %s",
key_type,
key_content,
type(e),
e,
)
continue
device_ids.append(verify_key.version)
# If this is the desired key type, save it and it's ID/VerifyKey
if key_type == desired_key_type:
final_key = key_content
final_verify_key = verify_key
final_key_id = key_id
# Notify clients that new devices for this user have been discovered
if device_ids:
yield self.device_handler.notify_device_update(user.to_string(), device_ids)
return final_key, final_key_id, final_verify_key
def _check_cross_signing_key(key, user_id, key_type, signing_key=None): def _check_cross_signing_key(key, user_id, key_type, signing_key=None):