Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.

Commit 9cd13c5

Browse files
authored
Fix perspectives requests for multiple keys for the same server (#11440)
If we tried to request multiple keys for the same server, we would end up dropping some of those requests.
1 parent 7b4e228 commit 9cd13c5

File tree

3 files changed

+91
-11
lines changed

3 files changed

+91
-11
lines changed

changelog.d/11440.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix a bug introduced in Synapse 1.36 which could cause problems fetching event-signing keys from trusted key servers.

synapse/crypto/keyring.py

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -667,28 +667,36 @@ async def get_server_verify_key_v2_indirect(
667667
perspective_name,
668668
)
669669

670+
request: JsonDict = {}
671+
for queue_value in keys_to_fetch:
672+
# there may be multiple requests for each server, so we have to merge
673+
# them intelligently.
674+
request_for_server = {
675+
key_id: {
676+
"minimum_valid_until_ts": queue_value.minimum_valid_until_ts,
677+
}
678+
for key_id in queue_value.key_ids
679+
}
680+
request.setdefault(queue_value.server_name, {}).update(request_for_server)
681+
682+
logger.debug("Request to notary server %s: %s", perspective_name, request)
683+
670684
try:
671685
query_response = await self.client.post_json(
672686
destination=perspective_name,
673687
path="/_matrix/key/v2/query",
674-
data={
675-
"server_keys": {
676-
queue_value.server_name: {
677-
key_id: {
678-
"minimum_valid_until_ts": queue_value.minimum_valid_until_ts,
679-
}
680-
for key_id in queue_value.key_ids
681-
}
682-
for queue_value in keys_to_fetch
683-
}
684-
},
688+
data={"server_keys": request},
685689
)
686690
except (NotRetryingDestination, RequestSendFailed) as e:
687691
# these both have str() representations which we can't really improve upon
688692
raise KeyLookupError(str(e))
689693
except HttpResponseException as e:
690694
raise KeyLookupError("Remote server returned an error: %s" % (e,))
691695

696+
logger.debug(
697+
"Response from notary server %s: %s", perspective_name, query_response
698+
)
699+
692700
keys: Dict[str, Dict[str, FetchKeyResult]] = {}
693701
added_keys: List[Tuple[str, str, FetchKeyResult]] = []
694702

tests/crypto/test_keyring.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from nacl.signing import SigningKey
2323
from signedjson.key import encode_verify_key_base64, get_verify_key
2424

25+
from twisted.internet import defer
2526
from twisted.internet.defer import Deferred, ensureDeferred
2627

2728
from synapse.api.errors import SynapseError
@@ -577,6 +578,76 @@ def test_get_keys_from_perspectives(self):
577578
bytes(res["key_json"]), canonicaljson.encode_canonical_json(response)
578579
)
579580

581+
def test_get_multiple_keys_from_perspectives(self):
582+
"""Check that we can correctly request multiple keys for the same server"""
583+
584+
fetcher = PerspectivesKeyFetcher(self.hs)
585+
586+
SERVER_NAME = "server2"
587+
588+
testkey1 = signedjson.key.generate_signing_key("ver1")
589+
testverifykey1 = signedjson.key.get_verify_key(testkey1)
590+
testverifykey1_id = "ed25519:ver1"
591+
592+
testkey2 = signedjson.key.generate_signing_key("ver2")
593+
testverifykey2 = signedjson.key.get_verify_key(testkey2)
594+
testverifykey2_id = "ed25519:ver2"
595+
596+
VALID_UNTIL_TS = 200 * 1000
597+
598+
response1 = self.build_perspectives_response(
599+
SERVER_NAME,
600+
testkey1,
601+
VALID_UNTIL_TS,
602+
)
603+
response2 = self.build_perspectives_response(
604+
SERVER_NAME,
605+
testkey2,
606+
VALID_UNTIL_TS,
607+
)
608+
609+
async def post_json(destination, path, data, **kwargs):
610+
self.assertEqual(destination, self.mock_perspective_server.server_name)
611+
self.assertEqual(path, "/_matrix/key/v2/query")
612+
613+
# check that the request is for the expected keys
614+
q = data["server_keys"]
615+
616+
self.assertEqual(
617+
list(q[SERVER_NAME].keys()), [testverifykey1_id, testverifykey2_id]
618+
)
619+
return {"server_keys": [response1, response2]}
620+
621+
self.http_client.post_json.side_effect = post_json
622+
623+
# fire off two separate requests; they should get merged together into a
624+
# single HTTP hit.
625+
request1_d = defer.ensureDeferred(
626+
fetcher.get_keys(SERVER_NAME, [testverifykey1_id], 0)
627+
)
628+
request2_d = defer.ensureDeferred(
629+
fetcher.get_keys(SERVER_NAME, [testverifykey2_id], 0)
630+
)
631+
632+
keys1 = self.get_success(request1_d)
633+
self.assertIn(testverifykey1_id, keys1)
634+
k = keys1[testverifykey1_id]
635+
self.assertEqual(k.valid_until_ts, VALID_UNTIL_TS)
636+
self.assertEqual(k.verify_key, testverifykey1)
637+
self.assertEqual(k.verify_key.alg, "ed25519")
638+
self.assertEqual(k.verify_key.version, "ver1")
639+
640+
keys2 = self.get_success(request2_d)
641+
self.assertIn(testverifykey2_id, keys2)
642+
k = keys2[testverifykey2_id]
643+
self.assertEqual(k.valid_until_ts, VALID_UNTIL_TS)
644+
self.assertEqual(k.verify_key, testverifykey2)
645+
self.assertEqual(k.verify_key.alg, "ed25519")
646+
self.assertEqual(k.verify_key.version, "ver2")
647+
648+
# finally, ensure that only one request was sent
649+
self.assertEqual(self.http_client.post_json.call_count, 1)
650+
580651
def test_get_perspectives_own_key(self):
581652
"""Check that we can get the perspectives server's own keys
582653

0 commit comments

Comments
 (0)