Make it the responsibility of the replication layer to check signature and hashes.

This commit is contained in:
Erik Johnston 2015-01-26 14:33:11 +00:00
parent 7b88619241
commit c92d64a6c3
3 changed files with 173 additions and 26 deletions

View File

@ -20,6 +20,13 @@ from .units import Edu
from synapse.util.logutils import log_function from synapse.util.logutils import log_function
from synapse.events import FrozenEvent from synapse.events import FrozenEvent
from synapse.events.utils import prune_event
from syutil.jsonutil import encode_canonical_json
from synapse.crypto.event_signing import check_event_content_hash
from synapse.api.errors import SynapseError
import logging import logging
@ -126,6 +133,11 @@ class FederationClient(object):
for p in transaction_data["pdus"] for p in transaction_data["pdus"]
] ]
for i, pdu in enumerate(pdus):
pdus[i] = yield self._check_sigs_and_hash(pdu)
# FIXME: We should handle signature failures more gracefully.
defer.returnValue(pdus) defer.returnValue(pdus)
@defer.inlineCallbacks @defer.inlineCallbacks
@ -159,12 +171,6 @@ class FederationClient(object):
transaction_data = yield self.transport_layer.get_event( transaction_data = yield self.transport_layer.get_event(
destination, event_id destination, event_id
) )
except Exception as e:
logger.info(
"Failed to get PDU %s from %s because %s",
event_id, destination, e,
)
continue
logger.debug("transaction_data %r", transaction_data) logger.debug("transaction_data %r", transaction_data)
@ -175,9 +181,19 @@ class FederationClient(object):
if pdu_list: if pdu_list:
pdu = pdu_list[0] pdu = pdu_list[0]
# TODO: We need to check signatures here
# Check signatures are correct.
pdu = yield self._check_sigs_and_hash(pdu)
break break
except Exception as e:
logger.info(
"Failed to get PDU %s from %s because %s",
event_id, destination, e,
)
continue
defer.returnValue(pdu) defer.returnValue(pdu)
@defer.inlineCallbacks @defer.inlineCallbacks
@ -208,6 +224,16 @@ class FederationClient(object):
for p in result.get("auth_chain", []) for p in result.get("auth_chain", [])
] ]
for i, pdu in enumerate(pdus):
pdus[i] = yield self._check_sigs_and_hash(pdu)
# FIXME: We should handle signature failures more gracefully.
for i, pdu in enumerate(auth_chain):
auth_chain[i] = yield self._check_sigs_and_hash(pdu)
# FIXME: We should handle signature failures more gracefully.
defer.returnValue((pdus, auth_chain)) defer.returnValue((pdus, auth_chain))
@defer.inlineCallbacks @defer.inlineCallbacks
@ -222,6 +248,11 @@ class FederationClient(object):
for p in res["auth_chain"] for p in res["auth_chain"]
] ]
for i, pdu in enumerate(auth_chain):
auth_chain[i] = yield self._check_sigs_and_hash(pdu)
# FIXME: We should handle signature failures more gracefully.
auth_chain.sort(key=lambda e: e.depth) auth_chain.sort(key=lambda e: e.depth)
defer.returnValue(auth_chain) defer.returnValue(auth_chain)
@ -260,6 +291,16 @@ class FederationClient(object):
for p in content.get("auth_chain", []) for p in content.get("auth_chain", [])
] ]
for i, pdu in enumerate(state):
state[i] = yield self._check_sigs_and_hash(pdu)
# FIXME: We should handle signature failures more gracefully.
for i, pdu in enumerate(auth_chain):
auth_chain[i] = yield self._check_sigs_and_hash(pdu)
# FIXME: We should handle signature failures more gracefully.
auth_chain.sort(key=lambda e: e.depth) auth_chain.sort(key=lambda e: e.depth)
defer.returnValue({ defer.returnValue({
@ -281,7 +322,14 @@ class FederationClient(object):
logger.debug("Got response to send_invite: %s", pdu_dict) logger.debug("Got response to send_invite: %s", pdu_dict)
defer.returnValue(self.event_from_pdu_json(pdu_dict)) pdu = self.event_from_pdu_json(pdu_dict)
# Check signatures are correct.
pdu = yield self._check_sigs_and_hash(pdu)
# FIXME: We should handle signature failures more gracefully.
defer.returnValue(pdu)
def event_from_pdu_json(self, pdu_json, outlier=False): def event_from_pdu_json(self, pdu_json, outlier=False):
event = FrozenEvent( event = FrozenEvent(
@ -291,3 +339,37 @@ class FederationClient(object):
event.internal_metadata.outlier = outlier event.internal_metadata.outlier = outlier
return event return event
@defer.inlineCallbacks
def _check_sigs_and_hash(self, pdu):
"""Throws a SynapseError if the PDU does not have the correct
signatures.
Returns:
FrozenEvent: Either the given event or it redacted if it failed the
content hash check.
"""
# Check signatures are correct.
redacted_event = prune_event(pdu)
redacted_pdu_json = redacted_event.get_pdu_json()
try:
yield self.keyring.verify_json_for_server(
pdu.origin, redacted_pdu_json
)
except SynapseError:
logger.warn(
"Signature check failed for %s redacted to %s",
encode_canonical_json(pdu.get_pdu_json()),
encode_canonical_json(redacted_pdu_json),
)
raise
if not check_event_content_hash(pdu):
logger.warn(
"Event content has been tampered, redacting %s, %s",
pdu.event_id, encode_canonical_json(pdu.get_dict())
)
defer.returnValue(redacted_event)
defer.returnValue(pdu)

View File

@ -21,6 +21,13 @@ from .units import Transaction, Edu
from synapse.util.logutils import log_function from synapse.util.logutils import log_function
from synapse.util.logcontext import PreserveLoggingContext from synapse.util.logcontext import PreserveLoggingContext
from synapse.events import FrozenEvent from synapse.events import FrozenEvent
from synapse.events.utils import prune_event
from syutil.jsonutil import encode_canonical_json
from synapse.crypto.event_signing import check_event_content_hash
from synapse.api.errors import FederationError, SynapseError
import logging import logging
@ -97,8 +104,10 @@ class FederationServer(object):
response = yield self.transaction_actions.have_responded(transaction) response = yield self.transaction_actions.have_responded(transaction)
if response: if response:
logger.debug("[%s] We've already responed to this request", logger.debug(
transaction.transaction_id) "[%s] We've already responed to this request",
transaction.transaction_id
)
defer.returnValue(response) defer.returnValue(response)
return return
@ -253,6 +262,9 @@ class FederationServer(object):
origin, pdu.event_id, do_auth=False origin, pdu.event_id, do_auth=False
) )
# FIXME: Currently we fetch an event again when we already have it
# if it has been marked as an outlier.
already_seen = ( already_seen = (
existing and ( existing and (
not existing.internal_metadata.is_outlier() not existing.internal_metadata.is_outlier()
@ -264,14 +276,27 @@ class FederationServer(object):
defer.returnValue({}) defer.returnValue({})
return return
# Check signature.
try:
pdu = yield self._check_sigs_and_hash(pdu)
except SynapseError as e:
raise FederationError(
"ERROR",
e.code,
e.msg,
affected=pdu.event_id,
)
state = None state = None
auth_chain = [] auth_chain = []
have_seen = yield self.store.have_events( have_seen = yield self.store.have_events(
[e for e, _ in pdu.prev_events] [ev for ev, _ in pdu.prev_events]
) )
fetch_state = False
# Get missing pdus if necessary. # Get missing pdus if necessary.
if not pdu.internal_metadata.is_outlier(): if not pdu.internal_metadata.is_outlier():
# We only backfill backwards to the min depth. # We only backfill backwards to the min depth.
@ -311,9 +336,13 @@ class FederationServer(object):
except: except:
# TODO(erikj): Do some more intelligent retries. # TODO(erikj): Do some more intelligent retries.
logger.exception("Failed to get PDU") logger.exception("Failed to get PDU")
fetch_state = True
else: else:
# We need to get the state at this event, since we have reached fetch_state = True
# a backward extremity edge.
if fetch_state:
# We need to get the state at this event, since we haven't
# processed all the prev events.
logger.debug( logger.debug(
"_handle_new_pdu getting state for %s", "_handle_new_pdu getting state for %s",
pdu.room_id pdu.room_id
@ -343,3 +372,37 @@ class FederationServer(object):
event.internal_metadata.outlier = outlier event.internal_metadata.outlier = outlier
return event return event
@defer.inlineCallbacks
def _check_sigs_and_hash(self, pdu):
"""Throws a SynapseError if the PDU does not have the correct
signatures.
Returns:
FrozenEvent: Either the given event or it redacted if it failed the
content hash check.
"""
# Check signatures are correct.
redacted_event = prune_event(pdu)
redacted_pdu_json = redacted_event.get_pdu_json()
try:
yield self.keyring.verify_json_for_server(
pdu.origin, redacted_pdu_json
)
except SynapseError:
logger.warn(
"Signature check failed for %s redacted to %s",
encode_canonical_json(pdu.get_pdu_json()),
encode_canonical_json(redacted_pdu_json),
)
raise
if not check_event_content_hash(pdu):
logger.warn(
"Event content has been tampered, redacting %s, %s",
pdu.event_id, encode_canonical_json(pdu.get_dict())
)
defer.returnValue(redacted_event)
defer.returnValue(pdu)

View File

@ -51,6 +51,8 @@ class ReplicationLayer(FederationClient, FederationServer):
def __init__(self, hs, transport_layer): def __init__(self, hs, transport_layer):
self.server_name = hs.hostname self.server_name = hs.hostname
self.keyring = hs.get_keyring()
self.transport_layer = transport_layer self.transport_layer = transport_layer
self.transport_layer.register_received_handler(self) self.transport_layer.register_received_handler(self)
self.transport_layer.register_request_handler(self) self.transport_layer.register_request_handler(self)