diff --git a/synapse/crypto/event_signing.py b/synapse/crypto/event_signing.py index baa93b0ee4..c7e6bec8f5 100644 --- a/synapse/crypto/event_signing.py +++ b/synapse/crypto/event_signing.py @@ -19,6 +19,7 @@ from synapse.api.events.utils import prune_event from syutil.jsonutil import encode_canonical_json from syutil.base64util import encode_base64, decode_base64 from syutil.crypto.jsonsign import sign_json +from synapse.api.errors import SynapseError, Codes import hashlib import logging @@ -29,15 +30,24 @@ logger = logging.getLogger(__name__) def check_event_content_hash(event, hash_algorithm=hashlib.sha256): """Check whether the hash for this PDU matches the contents""" computed_hash = _compute_content_hash(event, hash_algorithm) + logging.debug("Expecting hash: %s", encode_base64(computed_hash.digest())) if computed_hash.name not in event.hashes: - raise Exception("Algorithm %s not in hashes %s" % ( - computed_hash.name, list(event.hashes) - )) + raise SynapseError( + 400, + "Algorithm %s not in hashes %s" % ( + computed_hash.name, list(event.hashes), + ), + Codes.UNAUTHORIZED, + ) message_hash_base64 = event.hashes[computed_hash.name] try: message_hash_bytes = decode_base64(message_hash_base64) except: - raise Exception("Invalid base64: %s" % (message_hash_base64,)) + raise SynapseError( + 400, + "Invalid base64: %s" % (message_hash_base64,), + Codes.UNAUTHORIZED, + ) return message_hash_bytes == computed_hash.digest() diff --git a/synapse/handlers/federation.py b/synapse/handlers/federation.py index 5e096f4652..fce935b444 100644 --- a/synapse/handlers/federation.py +++ b/synapse/handlers/federation.py @@ -17,13 +17,17 @@ from ._base import BaseHandler -from synapse.api.errors import AuthError, FederationError +from synapse.api.events.utils import prune_event +from synapse.api.errors import AuthError, FederationError, SynapseError from synapse.api.events.room import RoomMemberEvent from synapse.api.constants import Membership from synapse.util.logutils import log_function from synapse.federation.pdu_codec import PduCodec from synapse.util.async import run_on_reactor -from synapse.crypto.event_signing import compute_event_signature +from synapse.crypto.event_signing import ( + compute_event_signature, check_event_content_hash +) +from syutil.jsonutil import encode_canonical_json from twisted.internet import defer @@ -59,6 +63,7 @@ class FederationHandler(BaseHandler): self.state_handler = hs.get_state_handler() # self.auth_handler = gs.get_auth_handler() self.server_name = hs.hostname + self.keyring = hs.get_keyring() self.lock_manager = hs.get_room_lock_manager() @@ -112,6 +117,34 @@ class FederationHandler(BaseHandler): logger.debug("Processing event: %s", event.event_id) + redacted_event = prune_event(event) + redacted_event.origin = pdu.origin + redacted_event.origin_server_ts = pdu.origin_server_ts + redacted_pdu = self.pdu_codec.pdu_from_event(redacted_event) + + redacted_pdu_json = redacted_pdu.get_dict() + try: + yield self.keyring.verify_json_for_server( + event.origin, redacted_pdu_json + ) + except SynapseError as e: + logger.warn("Signature check failed for %s redacted to %s", + encode_canonical_json(pdu.get_dict()), + encode_canonical_json(redacted_pdu_json), + ) + raise FederationError( + "ERROR", + e.code, + e.msg, + affected=event.event_id, + ) + + if not check_event_content_hash(pdu): + logger.warn( + "Event content has been tampered, redacting %s", event.event_id + ) + event = redacted_event + if state: state = [self.pdu_codec.event_from_pdu(p) for p in state] diff --git a/tests/handlers/test_federation.py b/tests/handlers/test_federation.py index e386cddb38..3f17ca8fb0 100644 --- a/tests/handlers/test_federation.py +++ b/tests/handlers/test_federation.py @@ -23,7 +23,7 @@ from synapse.handlers.federation import FederationHandler from synapse.server import HomeServer from synapse.federation.units import Pdu -from mock import NonCallableMock, ANY +from mock import NonCallableMock, ANY, Mock from ..utils import MockKey @@ -62,6 +62,7 @@ class FederationTestCase(unittest.TestCase): config=self.mock_config, auth=self.auth, state_handler=self.state_handler, + keyring=Mock(), ) self.datastore = hs.get_datastore() @@ -80,6 +81,7 @@ class FederationTestCase(unittest.TestCase): origin_server_ts=0, event_id="$a:b", origin="b", + hashes={"sha256":"PvbCLWrTBxnBsSO7/cJ76072ySTCgI/XGadESRAe02M"}, ) self.datastore.persist_event.return_value = defer.succeed(None)