@@ -174,8 +174,8 @@ def do_remote_query(destination):
174174 """This is called when we are querying the device list of a user on
175175 a remote homeserver and their device list is not in the device list
176176 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. """
177+ specific user we will update the cache with their device list.
178+ """
179179
180180 destination_query = remote_queries_not_in_cache [destination ]
181181
@@ -961,13 +961,19 @@ def _process_other_signatures(self, user_id, signatures):
961961 return signature_list , failures
962962
963963 @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.
964+ def _get_e2e_cross_signing_verify_key (
965+ self , user_id : str , key_type : str , from_user_id : str = None
966+ ):
967+ """Fetch locally or remotely query for a cross-signing public key.
968+
969+ First, attempt to fetch the cross-signing public key from storage.
970+ If that fails, query the keys from the homeserver they belong to
971+ and update our local copy.
966972
967973 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.
974+ user_id: the user whose key should be fetched
975+ key_type: the type of key to fetch
976+ from_user_id: the user that we are fetching the keys for.
971977 This affects what signatures are fetched.
972978
973979 Returns:
@@ -976,16 +982,140 @@ def _get_e2e_cross_signing_verify_key(self, user_id, key_type, from_user_id=None
976982
977983 Raises:
978984 NotFoundError: if the key is not found
985+ SynapseError: if `user_id` is invalid
979986 """
987+ user = UserID .from_string (user_id )
980988 key = yield self .store .get_e2e_cross_signing_key (
981989 user_id , key_type , from_user_id
982990 )
991+
992+ if key :
993+ # We found a copy of this key in our database. Decode and return it
994+ key_id , verify_key = get_verify_key_from_cross_signing_key (key )
995+ return key , key_id , verify_key
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 instead fetch the key,
1004+ # store it and notify clients of new, associated device IDs.
1005+ if self .is_mine (user ) or key_type not in ["master" , "self_signing" ]:
1006+ # Note that master and self_signing keys are the only cross-signing keys we
1007+ # can request over federation
1008+ raise NotFoundError ("No %s key found for %s" % (key_type , user_id ))
1009+
1010+ (
1011+ key ,
1012+ key_id ,
1013+ verify_key ,
1014+ ) = yield self ._retrieve_cross_signing_keys_for_remote_user (user , key_type )
1015+
9831016 if key is None :
984- 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+
9871019 return key , key_id , verify_key
9881020
1021+ @defer .inlineCallbacks
1022+ def _retrieve_cross_signing_keys_for_remote_user (
1023+ self , user : UserID , desired_key_type : str ,
1024+ ):
1025+ """Queries cross-signing keys for a remote user and saves them to the database
1026+
1027+ Only the key specified by `key_type` will be returned, while all retrieved keys
1028+ will be saved regardless
1029+
1030+ Args:
1031+ user: The user to query remote keys for
1032+ desired_key_type: The type of key to receive. One of "master", "self_signing"
1033+
1034+ Returns:
1035+ Deferred[Tuple[Optional[Dict], Optional[str], Optional[VerifyKey]]]: A tuple
1036+ of the retrieved key content, the key's ID and the matching VerifyKey.
1037+ If the key cannot be retrieved, all values in the tuple will instead be None.
1038+ """
1039+ try :
1040+ remote_result = yield self .federation .query_user_devices (
1041+ user .domain , user .to_string ()
1042+ )
1043+ except Exception as e :
1044+ logger .warning (
1045+ "Unable to query %s for cross-signing keys of user %s: %s %s" ,
1046+ user .domain ,
1047+ user .to_string (),
1048+ type (e ),
1049+ e ,
1050+ )
1051+ return None , None , None
1052+
1053+ # Process each of the retrieved cross-signing keys
1054+ desired_key = None
1055+ desired_key_id = None
1056+ desired_verify_key = None
1057+ retrieved_device_ids = []
1058+ for key_type in ["master" , "self_signing" ]:
1059+ key_content = remote_result .get (key_type + "_key" )
1060+ if not key_content :
1061+ continue
1062+
1063+ # Ensure these keys belong to the correct user
1064+ if "user_id" not in key_content :
1065+ logger .warning (
1066+ "Invalid %s key retrieved, missing user_id field: %s" ,
1067+ key_type ,
1068+ key_content ,
1069+ )
1070+ continue
1071+ if user .to_string () != key_content ["user_id" ]:
1072+ logger .warning (
1073+ "Found %s key of user %s when querying for keys of user %s" ,
1074+ key_type ,
1075+ key_content ["user_id" ],
1076+ user .to_string (),
1077+ )
1078+ continue
1079+
1080+ # Validate the key contents
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 .warning (
1088+ "Invalid %s key retrieved: %s - %s %s" ,
1089+ key_type ,
1090+ key_content ,
1091+ type (e ),
1092+ e ,
1093+ )
1094+ continue
1095+
1096+ # Note down the device ID attached to this key
1097+ retrieved_device_ids .append (verify_key .version )
1098+
1099+ # If this is the desired key type, save it and its ID/VerifyKey
1100+ if key_type == desired_key_type :
1101+ desired_key = key_content
1102+ desired_verify_key = verify_key
1103+ desired_key_id = key_id
1104+
1105+ # At the same time, store this key in the db for subsequent queries
1106+ yield self .store .set_e2e_cross_signing_key (
1107+ user .to_string (), key_type , key_content
1108+ )
1109+
1110+ # Notify clients that new devices for this user have been discovered
1111+ if retrieved_device_ids :
1112+ # XXX is this necessary?
1113+ yield self .device_handler .notify_device_update (
1114+ user .to_string (), retrieved_device_ids
1115+ )
1116+
1117+ return desired_key , desired_key_id , desired_verify_key
1118+
9891119
9901120def _check_cross_signing_key (key , user_id , key_type , signing_key = None ):
9911121 """Check a cross-signing key uploaded by a user. Performs some basic sanity
0 commit comments