Validate signatures on incoming events
This commit is contained in:
parent
44a24605ad
commit
de1ec90133
|
@ -19,6 +19,7 @@ from synapse.api.events.utils import prune_event
|
||||||
from syutil.jsonutil import encode_canonical_json
|
from syutil.jsonutil import encode_canonical_json
|
||||||
from syutil.base64util import encode_base64, decode_base64
|
from syutil.base64util import encode_base64, decode_base64
|
||||||
from syutil.crypto.jsonsign import sign_json
|
from syutil.crypto.jsonsign import sign_json
|
||||||
|
from synapse.api.errors import SynapseError, Codes
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import logging
|
import logging
|
||||||
|
@ -29,15 +30,24 @@ logger = logging.getLogger(__name__)
|
||||||
def check_event_content_hash(event, hash_algorithm=hashlib.sha256):
|
def check_event_content_hash(event, hash_algorithm=hashlib.sha256):
|
||||||
"""Check whether the hash for this PDU matches the contents"""
|
"""Check whether the hash for this PDU matches the contents"""
|
||||||
computed_hash = _compute_content_hash(event, hash_algorithm)
|
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:
|
if computed_hash.name not in event.hashes:
|
||||||
raise Exception("Algorithm %s not in hashes %s" % (
|
raise SynapseError(
|
||||||
computed_hash.name, list(event.hashes)
|
400,
|
||||||
))
|
"Algorithm %s not in hashes %s" % (
|
||||||
|
computed_hash.name, list(event.hashes),
|
||||||
|
),
|
||||||
|
Codes.UNAUTHORIZED,
|
||||||
|
)
|
||||||
message_hash_base64 = event.hashes[computed_hash.name]
|
message_hash_base64 = event.hashes[computed_hash.name]
|
||||||
try:
|
try:
|
||||||
message_hash_bytes = decode_base64(message_hash_base64)
|
message_hash_bytes = decode_base64(message_hash_base64)
|
||||||
except:
|
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()
|
return message_hash_bytes == computed_hash.digest()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -17,13 +17,17 @@
|
||||||
|
|
||||||
from ._base import BaseHandler
|
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.events.room import RoomMemberEvent
|
||||||
from synapse.api.constants import Membership
|
from synapse.api.constants import Membership
|
||||||
from synapse.util.logutils import log_function
|
from synapse.util.logutils import log_function
|
||||||
from synapse.federation.pdu_codec import PduCodec
|
from synapse.federation.pdu_codec import PduCodec
|
||||||
from synapse.util.async import run_on_reactor
|
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
|
from twisted.internet import defer
|
||||||
|
|
||||||
|
@ -59,6 +63,7 @@ class FederationHandler(BaseHandler):
|
||||||
self.state_handler = hs.get_state_handler()
|
self.state_handler = hs.get_state_handler()
|
||||||
# self.auth_handler = gs.get_auth_handler()
|
# self.auth_handler = gs.get_auth_handler()
|
||||||
self.server_name = hs.hostname
|
self.server_name = hs.hostname
|
||||||
|
self.keyring = hs.get_keyring()
|
||||||
|
|
||||||
self.lock_manager = hs.get_room_lock_manager()
|
self.lock_manager = hs.get_room_lock_manager()
|
||||||
|
|
||||||
|
@ -112,6 +117,34 @@ class FederationHandler(BaseHandler):
|
||||||
|
|
||||||
logger.debug("Processing event: %s", event.event_id)
|
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:
|
if state:
|
||||||
state = [self.pdu_codec.event_from_pdu(p) for p in state]
|
state = [self.pdu_codec.event_from_pdu(p) for p in state]
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ from synapse.handlers.federation import FederationHandler
|
||||||
from synapse.server import HomeServer
|
from synapse.server import HomeServer
|
||||||
from synapse.federation.units import Pdu
|
from synapse.federation.units import Pdu
|
||||||
|
|
||||||
from mock import NonCallableMock, ANY
|
from mock import NonCallableMock, ANY, Mock
|
||||||
|
|
||||||
from ..utils import MockKey
|
from ..utils import MockKey
|
||||||
|
|
||||||
|
@ -62,6 +62,7 @@ class FederationTestCase(unittest.TestCase):
|
||||||
config=self.mock_config,
|
config=self.mock_config,
|
||||||
auth=self.auth,
|
auth=self.auth,
|
||||||
state_handler=self.state_handler,
|
state_handler=self.state_handler,
|
||||||
|
keyring=Mock(),
|
||||||
)
|
)
|
||||||
|
|
||||||
self.datastore = hs.get_datastore()
|
self.datastore = hs.get_datastore()
|
||||||
|
@ -80,6 +81,7 @@ class FederationTestCase(unittest.TestCase):
|
||||||
origin_server_ts=0,
|
origin_server_ts=0,
|
||||||
event_id="$a:b",
|
event_id="$a:b",
|
||||||
origin="b",
|
origin="b",
|
||||||
|
hashes={"sha256":"PvbCLWrTBxnBsSO7/cJ76072ySTCgI/XGadESRAe02M"},
|
||||||
)
|
)
|
||||||
|
|
||||||
self.datastore.persist_event.return_value = defer.succeed(None)
|
self.datastore.persist_event.return_value = defer.succeed(None)
|
||||||
|
|
Loading…
Reference in New Issue