summary refs log tree commit diff
path: root/tests/crypto/test_keyring.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/crypto/test_keyring.py')
-rw-r--r--tests/crypto/test_keyring.py127
1 files changed, 124 insertions, 3 deletions
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