-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Query missing cross-signing keys on local sig upload #7289
Changes from 24 commits
8348481
1063495
cc86457
c265bc7
39ed9f6
fd8d154
759b6b0
03d2c8c
b386658
bd9a671
745e653
f8b6f14
37ae643
83861c3
671178b
2d88b5d
f417300
2f87051
5990d1c
4f8ba5c
9240abc
3282423
95dd9d5
4f41f37
6d559ba
1b4dda5
7cb1e48
74eaac0
b08b7c7
de29d1f
8484a72
2932b9b
ebea2ee
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Fix a bug with cross-signing devices with remote users when they did not share a room with any user on the local homeserver. | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -174,8 +174,8 @@ def do_remote_query(destination): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """This is called when we are querying the device list of a user on | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| a remote homeserver and their device list is not in the device list | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cache. If we share a room with this user and we're not querying for | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| specific user we will update the cache | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| with their device list.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| specific user we will update the cache with their device list. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
richvdh marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| destination_query = remote_queries_not_in_cache[destination] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -961,13 +961,19 @@ def _process_other_signatures(self, user_id, signatures): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return signature_list, failures | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @defer.inlineCallbacks | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _get_e2e_cross_signing_verify_key(self, user_id, key_type, from_user_id=None): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Fetch the cross-signing public key from storage and interpret it. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _get_e2e_cross_signing_verify_key( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self, user_id: str, key_type: str, from_user_id: str = None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Fetch locally or remotely query for a cross-signing public key. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| First, attempt to fetch the cross-signing public key from storage. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| If that fails, query the keys from the homeserver they belong to | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| and update our local copy. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Args: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user_id (str): the user whose key should be fetched | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| key_type (str): the type of key to fetch | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from_user_id (str): the user that we are fetching the keys for. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user_id: the user whose key should be fetched | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| key_type: the type of key to fetch | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from_user_id: the user that we are fetching the keys for. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| This affects what signatures are fetched. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Returns: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -976,16 +982,126 @@ def _get_e2e_cross_signing_verify_key(self, user_id, key_type, from_user_id=None | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Raises: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| NotFoundError: if the key is not found | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SynapseError: if `user_id` is invalid | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user = UserID.from_string(user_id) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
richvdh marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| key = yield self.store.get_e2e_cross_signing_key( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user_id, key_type, from_user_id | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # If we couldn't find the key locally, and we're looking for keys of | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 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 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 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, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # store it and notify clients of new, associated device IDs. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| key is None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| and not self.is_mine(user) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # We only get "master" and "self_signing" keys from remote servers | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| and key_type in ["master", "self_signing"] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| key, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| key_id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| verify_key, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) = yield self._retrieve_cross_signing_keys_for_remote_user(user, key_type) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if key is None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| logger.debug("no %s key found for %s", key_type, user_id) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
anoadragon453 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| logger.warning("No %s key found for %s", key_type, user_id) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise NotFoundError("No %s key found for %s" % (key_type, user_id)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| key_id, verify_key = get_verify_key_from_cross_signing_key(key) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| key_id, verify_key = get_verify_key_from_cross_signing_key(key) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except ValueError as e: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| logger.warning( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Invalid %s key retrieved: %s - %s %s", key_type, key, type(e), e, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise SynapseError( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 502, "Invalid %s key retrieved from remote server" % (key_type,) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
anoadragon453 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return key, key_id, verify_key | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @defer.inlineCallbacks | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _retrieve_cross_signing_keys_for_remote_user( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self, user: UserID, desired_key_type: str, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """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 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| will be saved regardless | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Args: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user: The user to query remote keys for | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| desired_key_type: The type of key to receive. One of "master", "self_signing" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Returns: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Deferred[Tuple[Optional[Dict], Optional[str], Optional[VerifyKey]]]: 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: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| remote_result = yield self.federation.query_user_devices( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user.domain, user.to_string() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except Exception as e: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| logger.warning( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Unable to query %s for cross-signing keys of user %s: %s %s", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user.domain, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user.to_string(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type(e), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| e, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return None, None, None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Process each of the retrieved cross-signing keys | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| final_key = None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| final_key_id = None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| final_verify_key = None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| device_ids = [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for key_type in ["master", "self_signing"]: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| key_content = remote_result.get(key_type + "_key") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not key_content: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # At the same time, store this key in the db for | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # subsequent queries | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| yield self.store.set_e2e_cross_signing_key( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user.to_string(), key_type, key_content | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for user_id, keys in remote_result["device_keys"].items(): | |
| if user_id in destination_query: | |
| results[user_id] = keys | |
| if "master_keys" in remote_result: | |
| for user_id, key in remote_result["master_keys"].items(): | |
| if user_id in destination_query: | |
| cross_signing_keys["master_keys"][user_id] = key | |
| if "self_signing_keys" in remote_result: | |
| for user_id, key in remote_result["self_signing_keys"].items(): | |
| if user_id in destination_query: | |
| cross_signing_keys["self_signing_keys"][user_id] = key |
I'd hate to duplicate but that could be lifted and modified.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Speaking of verification, one thing that's confusing me is that in the loop of _retrieve_cross_signing_keys_for_remote_user, remote_result should look something like this:
synapse/synapse/federation/transport/client.py
Lines 404 to 415 in fd8d154
| Response: | |
| { | |
| "device_keys": { | |
| "<user_id>": { | |
| "<device_id>": {...} | |
| } } | |
| "master_keys": { | |
| "<user_id>": {...} | |
| } } | |
| "self_signing_keys": { | |
| "<user_id>": {...} | |
| } } } |
Thus key_content should look something like:
synapse/synapse/federation/transport/client.py
Lines 407 to 408 in fd8d154
| "<user_id>": { | |
| "<device_id>": {...} |
And thus we should need to extract the key_contents from that dictionary before we pass it to get_verify_key_from_cross_signing_key.
And yet this PR works, so something isn't lining up here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Already, so my docstring update is incorrect, as the thing I actually get back from query_client_keys is in the form:
{
"user_id": "@test18:workerstest.amorgan.xyz",
"stream_id": 213,
"devices": [
{
"device_id": "MLYKYOAKEZ"
},
{
"device_id": "NBICDLRVHV",
"keys": {
"algorithms": [
"m.olm.v1.curve25519-aes-sha2",
"m.megolm.v1.aes-sha2"
],
"device_id": "NBICDLRVHV",
"keys": {
"curve25519:NBICDLRVHV": "E9ryKjylPorQIxyRA4FsJwi9d2bPECLvWKjgcuqmamU",
"ed25519:NBICDLRVHV": "yk5AMkCyhogBCQt1CrVbFL9weRb/5iExmIDYd7JmBhE"
},
"signatures": {
"@test18:workerstest.amorgan.xyz": {
"ed25519:NBICDLRVHV": "JTAL+uIduDDJjdQneocTbjqjIDMljqDOq8bJ6ZgAYOQ01wMHrX4qRmQufQT9KAiYoXQ+b6UY+ZNEIFjyi1rjCg",
"ed25519:cEYsFkRyHrfj1fLLgq4rA+JyvOwHda2mO092N4scL/E": "RQSm3aYjvWMWHx0GHZPRRKvj90TEdlqjOGXulXBUSDEuI2P64A7N/7/uSdx//BhqOgcmZSS/9o1Awlpb+KiiCg"
}
},
"user_id": "@test18:workerstest.amorgan.xyz"
},
"device_display_name": "https://riot.im/develop/ via Firefox on Linux"
}
],
"master_key": {
"user_id": "@test18:workerstest.amorgan.xyz",
"usage": [
"master"
],
"keys": {
"ed25519:RPg3wmdeZOvjMCmi3/pP9+4ndSW9W2dhE/RkLyCdr+E": "RPg3wmdeZOvjMCmi3/pP9+4ndSW9W2dhE/RkLyCdr+E"
},
"signatures": {
"@test18:workerstest.amorgan.xyz": {
"ed25519:NBICDLRVHV": "YLdKUD4aDmJx/vqIWjbqXTOZXv+cU1ba8Z9hw44vVn32uhLdJAiGC+L/6HeBTz7+Wh+NdWGBiy+usea3408bDA"
}
}
},
"self_signing_key": {
"user_id": "@test18:workerstest.amorgan.xyz",
"usage": [
"self_signing"
],
"keys": {
"ed25519:cEYsFkRyHrfj1fLLgq4rA+JyvOwHda2mO092N4scL/E": "cEYsFkRyHrfj1fLLgq4rA+JyvOwHda2mO092N4scL/E"
},
"signatures": {
"@test18:workerstest.amorgan.xyz": {
"ed25519:RPg3wmdeZOvjMCmi3/pP9+4ndSW9W2dhE/RkLyCdr+E": "1H9aaL9623L764uGAezZM5jH3Gp5DLwto4s9rz+IP3bGQqLo0Cyi2bWK54aJbVrJ6akwxahZRs/n9vLDn3nIDQ"
}
}
}
}
Lemme update that.
In terms of verification, I think we just need to check the user_id matches the user we're querying for, and that there's a keys field under each *_keys key.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aaand I was looking at the output of query_user_devices, not query_client_keys. Have updated the right docstring now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So to conclude, we now validate the key by checking it belongs to the right user, and has a valid keys key dict in it with get_verify_key_from_cross_signing_key.
Uh oh!
There was an error while loading. Please reload this page.