@@ -174,8 +174,8 @@ def do_remote_query(destination):
174
174
"""This is called when we are querying the device list of a user on
175
175
a remote homeserver and their device list is not in the device list
176
176
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
+ """
179
179
180
180
destination_query = remote_queries_not_in_cache [destination ]
181
181
@@ -961,13 +961,19 @@ def _process_other_signatures(self, user_id, signatures):
961
961
return signature_list , failures
962
962
963
963
@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.
966
972
967
973
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.
971
977
This affects what signatures are fetched.
972
978
973
979
Returns:
@@ -976,16 +982,140 @@ def _get_e2e_cross_signing_verify_key(self, user_id, key_type, from_user_id=None
976
982
977
983
Raises:
978
984
NotFoundError: if the key is not found
985
+ SynapseError: if `user_id` is invalid
979
986
"""
987
+ user = UserID .from_string (user_id )
980
988
key = yield self .store .get_e2e_cross_signing_key (
981
989
user_id , key_type , from_user_id
982
990
)
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
+
983
1016
if key is None :
984
- logger .debug ("no %s key found for %s" , key_type , user_id )
985
1017
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
+
987
1019
return key , key_id , verify_key
988
1020
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
+
989
1119
990
1120
def _check_cross_signing_key (key , user_id , key_type , signing_key = None ):
991
1121
"""Check a cross-signing key uploaded by a user. Performs some basic sanity
0 commit comments