def test_verify_json_falls_back_to_other_fetchers(self): """If the first fetcher cannot provide a recent enough key, we fall back""" key1 = signedjson.key.generate_signing_key(1) def get_keys1(keys_to_fetch): self.assertEqual(keys_to_fetch, {"server1": { get_key_id(key1): 1500 }}) return defer.succeed({ "server1": { get_key_id(key1): FetchKeyResult(get_verify_key(key1), 800) } }) def get_keys2(keys_to_fetch): self.assertEqual(keys_to_fetch, {"server1": { get_key_id(key1): 1500 }}) return defer.succeed({ "server1": { get_key_id(key1): FetchKeyResult(get_verify_key(key1), 1200) } }) mock_fetcher1 = keyring.KeyFetcher() mock_fetcher1.get_keys = Mock(side_effect=get_keys1) mock_fetcher2 = keyring.KeyFetcher() mock_fetcher2.get_keys = Mock(side_effect=get_keys2) kr = keyring.Keyring(self.hs, key_fetchers=(mock_fetcher1, mock_fetcher2)) json1 = {} signedjson.sign.sign_json(json1, "server1", key1) results = kr.verify_json_objects_for_server([ ("server1", json1, 1200, "test1"), ("server1", json1, 1500, "test2") ]) self.assertEqual(len(results), 2) self.get_success(results[0]) e = self.get_failure(results[1], SynapseError).value self.assertEqual(e.errcode, "M_UNAUTHORIZED") self.assertEqual(e.code, 401) # there should have been a single call to each fetcher mock_fetcher1.get_keys.assert_called_once() mock_fetcher2.get_keys.assert_called_once()
def test_verify_json_dedupes_key_requests(self): """Two requests for the same key should be deduped.""" key1 = signedjson.key.generate_signing_key(1) async def get_keys(keys_to_fetch): # there should only be one request object (with the max validity) self.assertEqual(keys_to_fetch, {"server1": {get_key_id(key1): 1500}}) return { "server1": { get_key_id(key1): FetchKeyResult(get_verify_key(key1), 1200) } } mock_fetcher = keyring.KeyFetcher() mock_fetcher.get_keys = Mock(side_effect=get_keys) kr = keyring.Keyring(self.hs, key_fetchers=(mock_fetcher,)) json1 = {} signedjson.sign.sign_json(json1, "server1", key1) # the first request should succeed; the second should fail because the key # has expired results = kr.verify_json_objects_for_server( [("server1", json1, 500, "test1"), ("server1", json1, 1500, "test2")] ) self.assertEqual(len(results), 2) self.get_success(results[0]) e = self.get_failure(results[1], SynapseError).value self.assertEqual(e.errcode, "M_UNAUTHORIZED") self.assertEqual(e.code, 401) # there should have been a single call to the fetcher mock_fetcher.get_keys.assert_called_once()
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` """ mock_fetcher = keyring.KeyFetcher() mock_fetcher.get_keys = Mock(return_value=defer.succeed({})) kr = keyring.Keyring( self.hs, key_fetchers=(StoreKeyFetcher(self.hs), mock_fetcher) ) key1 = signedjson.key.generate_signing_key(1) r = self.hs.datastore.store_server_verify_keys( "server9", time.time() * 1000, [("server9", get_key_id(key1), FetchKeyResult(get_verify_key(key1), None))], ) self.get_success(r) json1 = {} signedjson.sign.sign_json(json1, "server9", key1) # should fail immediately on an unsigned object d = _verify_json_for_server(kr, "server9", {}, 0, "test unsigned") self.failureResultOf(d, SynapseError) # should fail on a signed object with a non-zero minimum_valid_until_ms, # as it tries to refetch the keys and fails. d = _verify_json_for_server( kr, "server9", json1, 500, "test signed non-zero min" ) self.get_failure(d, SynapseError) # We expect the keyring tried to refetch the key once. mock_fetcher.get_keys.assert_called_once_with( {"server9": {get_key_id(key1): 500}} ) # should succeed on a signed object with a 0 minimum_valid_until_ms d = _verify_json_for_server( kr, "server9", json1, 0, "test signed with zero min" ) self.get_success(d)
def test_verify_json_objects_for_server_awaits_previous_requests(self): mock_fetcher = keyring.KeyFetcher() mock_fetcher.get_keys = Mock() kr = keyring.Keyring(self.hs, key_fetchers=(mock_fetcher,)) # a signed object that we are going to try to validate key1 = signedjson.key.generate_signing_key(1) json1 = {} signedjson.sign.sign_json(json1, "server10", key1) # start off a first set of lookups. We make the mock fetcher block until this # deferred completes. first_lookup_deferred = Deferred() async def first_lookup_fetch(keys_to_fetch): self.assertEquals(current_context().request, "context_11") self.assertEqual(keys_to_fetch, {"server10": {get_key_id(key1): 0}}) await make_deferred_yieldable(first_lookup_deferred) return { "server10": { get_key_id(key1): FetchKeyResult(get_verify_key(key1), 100) } } mock_fetcher.get_keys.side_effect = first_lookup_fetch async def first_lookup(): with LoggingContext("context_11") as context_11: context_11.request = "context_11" res_deferreds = kr.verify_json_objects_for_server( [("server10", json1, 0, "test10"), ("server11", {}, 0, "test11")] ) # the unsigned json should be rejected pretty quickly self.assertTrue(res_deferreds[1].called) try: await res_deferreds[1] self.assertFalse("unsigned json didn't cause a failure") except SynapseError: pass self.assertFalse(res_deferreds[0].called) res_deferreds[0].addBoth(self.check_context, None) await make_deferred_yieldable(res_deferreds[0]) d0 = ensureDeferred(first_lookup()) mock_fetcher.get_keys.assert_called_once() # a second request for a server with outstanding requests # should block rather than start a second call async def second_lookup_fetch(keys_to_fetch): self.assertEquals(current_context().request, "context_12") return { "server10": { get_key_id(key1): FetchKeyResult(get_verify_key(key1), 100) } } mock_fetcher.get_keys.reset_mock() mock_fetcher.get_keys.side_effect = second_lookup_fetch second_lookup_state = [0] async def second_lookup(): with LoggingContext("context_12") as context_12: context_12.request = "context_12" res_deferreds_2 = kr.verify_json_objects_for_server( [("server10", json1, 0, "test")] ) res_deferreds_2[0].addBoth(self.check_context, None) second_lookup_state[0] = 1 await make_deferred_yieldable(res_deferreds_2[0]) second_lookup_state[0] = 2 d2 = ensureDeferred(second_lookup()) self.pump() # the second request should be pending, but the fetcher should not yet have been # called self.assertEqual(second_lookup_state[0], 1) mock_fetcher.get_keys.assert_not_called() # complete the first request first_lookup_deferred.callback(None) # and now both verifications should succeed. self.get_success(d0) self.get_success(d2)