diff --git a/tests/crypto/test_keyring.py b/tests/crypto/test_keyring.py
index cbecc1c20f..17a9fb63a1 100644
--- a/tests/crypto/test_keyring.py
+++ b/tests/crypto/test_keyring.py
@@ -1,4 +1,4 @@
-# Copyright 2017 New Vector Ltd
+# Copyright 2017-2021 The Matrix.org Foundation C.I.C
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@ import signedjson.sign
from nacl.signing import SigningKey
from signedjson.key import encode_verify_key_base64, get_verify_key
+from twisted.internet import defer
from twisted.internet.defer import Deferred, ensureDeferred
from synapse.api.errors import SynapseError
@@ -40,7 +41,7 @@ from synapse.storage.keys import FetchKeyResult
from tests import unittest
from tests.test_utils import make_awaitable
-from tests.unittest import logcontext_clean
+from tests.unittest import logcontext_clean, override_config
class MockPerspectiveServer:
@@ -197,7 +198,7 @@ class KeyringTestCase(unittest.HomeserverTestCase):
# self.assertFalse(d.called)
self.get_success(d)
- def test_verify_for_server_locally(self):
+ def test_verify_for_local_server(self):
"""Ensure that locally signed JSON can be verified without fetching keys
over federation
"""
@@ -209,6 +210,56 @@ class KeyringTestCase(unittest.HomeserverTestCase):
d = kr.verify_json_for_server(self.hs.hostname, json1, 0)
self.get_success(d)
+ OLD_KEY = signedjson.key.generate_signing_key("old")
+
+ @override_config(
+ {
+ "old_signing_keys": {
+ f"{OLD_KEY.alg}:{OLD_KEY.version}": {
+ "key": encode_verify_key_base64(OLD_KEY.verify_key),
+ "expired_ts": 1000,
+ }
+ }
+ }
+ )
+ def test_verify_for_local_server_old_key(self):
+ """Can also use keys in old_signing_keys for verification"""
+ json1 = {}
+ signedjson.sign.sign_json(json1, self.hs.hostname, self.OLD_KEY)
+
+ kr = keyring.Keyring(self.hs)
+ d = kr.verify_json_for_server(self.hs.hostname, json1, 0)
+ self.get_success(d)
+
+ def test_verify_for_local_server_unknown_key(self):
+ """Local keys that we no longer have should be fetched via the fetcher"""
+
+ # the key we'll sign things with (nb, not known to the Keyring)
+ key2 = signedjson.key.generate_signing_key("2")
+
+ # set up a mock fetcher which will return the key
+ async def get_keys(
+ server_name: str, key_ids: List[str], minimum_valid_until_ts: int
+ ) -> Dict[str, FetchKeyResult]:
+ self.assertEqual(server_name, self.hs.hostname)
+ self.assertEqual(key_ids, [get_key_id(key2)])
+
+ return {get_key_id(key2): FetchKeyResult(get_verify_key(key2), 1200)}
+
+ mock_fetcher = Mock()
+ mock_fetcher.get_keys = Mock(side_effect=get_keys)
+ kr = keyring.Keyring(
+ self.hs, key_fetchers=(StoreKeyFetcher(self.hs), mock_fetcher)
+ )
+
+ # sign the json
+ json1 = {}
+ signedjson.sign.sign_json(json1, self.hs.hostname, key2)
+
+ # ... and check we can verify it.
+ d = kr.verify_json_for_server(self.hs.hostname, json1, 0)
+ self.get_success(d)
+
def test_verify_json_for_server_with_null_valid_until_ms(self):
"""Tests that we correctly handle key requests for keys we've stored
with a null `ts_valid_until_ms`
@@ -527,6 +578,76 @@ class PerspectivesKeyFetcherTestCase(unittest.HomeserverTestCase):
bytes(res["key_json"]), canonicaljson.encode_canonical_json(response)
)
+ def test_get_multiple_keys_from_perspectives(self):
+ """Check that we can correctly request multiple keys for the same server"""
+
+ fetcher = PerspectivesKeyFetcher(self.hs)
+
+ SERVER_NAME = "server2"
+
+ testkey1 = signedjson.key.generate_signing_key("ver1")
+ testverifykey1 = signedjson.key.get_verify_key(testkey1)
+ testverifykey1_id = "ed25519:ver1"
+
+ testkey2 = signedjson.key.generate_signing_key("ver2")
+ testverifykey2 = signedjson.key.get_verify_key(testkey2)
+ testverifykey2_id = "ed25519:ver2"
+
+ VALID_UNTIL_TS = 200 * 1000
+
+ response1 = self.build_perspectives_response(
+ SERVER_NAME,
+ testkey1,
+ VALID_UNTIL_TS,
+ )
+ response2 = self.build_perspectives_response(
+ SERVER_NAME,
+ testkey2,
+ VALID_UNTIL_TS,
+ )
+
+ async def post_json(destination, path, data, **kwargs):
+ self.assertEqual(destination, self.mock_perspective_server.server_name)
+ self.assertEqual(path, "/_matrix/key/v2/query")
+
+ # check that the request is for the expected keys
+ q = data["server_keys"]
+
+ self.assertEqual(
+ list(q[SERVER_NAME].keys()), [testverifykey1_id, testverifykey2_id]
+ )
+ return {"server_keys": [response1, response2]}
+
+ self.http_client.post_json.side_effect = post_json
+
+ # fire off two separate requests; they should get merged together into a
+ # single HTTP hit.
+ request1_d = defer.ensureDeferred(
+ fetcher.get_keys(SERVER_NAME, [testverifykey1_id], 0)
+ )
+ request2_d = defer.ensureDeferred(
+ fetcher.get_keys(SERVER_NAME, [testverifykey2_id], 0)
+ )
+
+ keys1 = self.get_success(request1_d)
+ self.assertIn(testverifykey1_id, keys1)
+ k = keys1[testverifykey1_id]
+ self.assertEqual(k.valid_until_ts, VALID_UNTIL_TS)
+ self.assertEqual(k.verify_key, testverifykey1)
+ self.assertEqual(k.verify_key.alg, "ed25519")
+ self.assertEqual(k.verify_key.version, "ver1")
+
+ keys2 = self.get_success(request2_d)
+ self.assertIn(testverifykey2_id, keys2)
+ k = keys2[testverifykey2_id]
+ self.assertEqual(k.valid_until_ts, VALID_UNTIL_TS)
+ self.assertEqual(k.verify_key, testverifykey2)
+ self.assertEqual(k.verify_key.alg, "ed25519")
+ self.assertEqual(k.verify_key.version, "ver2")
+
+ # finally, ensure that only one request was sent
+ self.assertEqual(self.http_client.post_json.call_count, 1)
+
def test_get_perspectives_own_key(self):
"""Check that we can get the perspectives server's own keys
|