16
16
# limitations under the License.
17
17
18
18
import logging
19
+ from typing import Dict , Optional , Tuple
19
20
20
21
from six import iteritems
21
22
22
23
import attr
23
24
from canonicaljson import encode_canonical_json , json
24
25
from signedjson .key import decode_verify_key_bytes
25
26
from signedjson .sign import SignatureVerifyException , verify_signed_json
27
+ from signedjson .types import VerifyKey
26
28
from unpaddedbase64 import decode_base64
27
29
28
30
from twisted .internet import defer
@@ -174,8 +176,8 @@ def do_remote_query(destination):
174
176
"""This is called when we are querying the device list of a user on
175
177
a remote homeserver and their device list is not in the device list
176
178
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
+ """
179
181
180
182
destination_query = remote_queries_not_in_cache [destination ]
181
183
@@ -961,13 +963,19 @@ def _process_other_signatures(self, user_id, signatures):
961
963
return signature_list , failures
962
964
963
965
@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.
966
974
967
975
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.
971
979
This affects what signatures are fetched.
972
980
973
981
Returns:
@@ -976,16 +984,128 @@ def _get_e2e_cross_signing_verify_key(self, user_id, key_type, from_user_id=None
976
984
977
985
Raises:
978
986
NotFoundError: if the key is not found
987
+ SynapseError: if `user_id` is invalid
979
988
"""
989
+ user = UserID .from_string (user_id )
990
+ key_id = None
991
+ verify_key = None
992
+
980
993
key = yield self .store .get_e2e_cross_signing_key (
981
994
user_id , key_type , from_user_id
982
995
)
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
+
983
1015
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 )
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
+
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
+
987
1031
return key , key_id , verify_key
988
1032
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
+
989
1109
990
1110
def _check_cross_signing_key (key , user_id , key_type , signing_key = None ):
991
1111
"""Check a cross-signing key uploaded by a user. Performs some basic sanity
0 commit comments