summary refs log tree commit diff
diff options
context:
space:
mode:
authorPatrick Cloke <patrickc@matrix.org>2023-07-11 14:35:34 -0400
committerPatrick Cloke <patrickc@matrix.org>2023-07-17 11:05:43 -0400
commitb38ba4a8b1d6816f195330df5b2188796fc4761d (patch)
tree12390ea61593e9a796cde7469de8458fc1103de7
parentIgnore non-state events sent in state. (diff)
downloadsynapse-b38ba4a8b1d6816f195330df5b2188796fc4761d.tar.xz
Handle LPDU content hash.
-rw-r--r--synapse/crypto/event_signing.py61
-rw-r--r--synapse/events/__init__.py6
-rw-r--r--synapse/federation/federation_base.py4
3 files changed, 55 insertions, 16 deletions
diff --git a/synapse/crypto/event_signing.py b/synapse/crypto/event_signing.py
index 1a293f1df0..ef6c763433 100644
--- a/synapse/crypto/event_signing.py
+++ b/synapse/crypto/event_signing.py
@@ -36,31 +36,37 @@ logger = logging.getLogger(__name__)
 Hasher = Callable[[bytes], "hashlib._Hash"]
 
 
-@trace
-def check_event_content_hash(
-    event: EventBase, hash_algorithm: Hasher = hashlib.sha256
+def _check_dict_hash(
+    event_id: str,
+    hash_log: str,
+    hashes: Any,
+    d: JsonDict,
+    hash_algorithm: Hasher = hashlib.sha256,
 ) -> bool:
-    """Check whether the hash for this PDU matches the contents"""
-    name, expected_hash = compute_content_hash(event.get_pdu_json(), hash_algorithm)
+    name, expected_hash = compute_content_hash(d, hash_algorithm)
     logger.debug(
-        "Verifying content hash on %s (expecting: %s)",
-        event.event_id,
+        "Verifying %s hash on %s (expecting: %s)",
+        hash_log,
+        event_id,
         encode_base64(expected_hash),
     )
 
-    # some malformed events lack a 'hashes'. Protect against it being missing
-    # or a weird type by basically treating it the same as an unhashed event.
-    hashes = event.get("hashes")
     # nb it might be a immutabledict or a dict
     if not isinstance(hashes, collections.abc.Mapping):
         raise SynapseError(
-            400, "Malformed 'hashes': %s" % (type(hashes),), Codes.UNAUTHORIZED
+            400,
+            "Malformed %s hashes: %s"
+            % (
+                hash_log,
+                type(hashes),
+            ),
+            Codes.UNAUTHORIZED,
         )
 
     if name not in hashes:
         raise SynapseError(
             400,
-            "Algorithm %s not in hashes %s" % (name, list(hashes)),
+            "Algorithm %s not in %s hashes %s" % (name, hash_log, list(hashes)),
             Codes.UNAUTHORIZED,
         )
     message_hash_base64 = hashes[name]
@@ -73,6 +79,37 @@ def check_event_content_hash(
     return message_hash_bytes == expected_hash
 
 
+@trace
+def check_event_content_hash(
+    event: EventBase, hash_algorithm: Hasher = hashlib.sha256
+) -> bool:
+    """Check whether the hash for this PDU matches the contents"""
+
+    # some malformed events lack a 'hashes'. Protect against it being missing
+    # or a weird type by basically treating it the same as an unhashed event.
+    hashes = event.get("hashes")
+
+    if not _check_dict_hash(
+        event.event_id, "content", hashes, event.get_pdu_json(), hash_algorithm
+    ):
+        return False
+
+    # Check the content hash of the LPDU, if this was sent via a hub.
+    if event.room_version.linearized_matrix and event.hub_server:
+        # hashes must be a dictionary to have passed _check_dict_hash above.
+        lpdu_hashes = hashes.get("lpdu")
+        return _check_dict_hash(
+            event.event_id,
+            "linearized content",
+            lpdu_hashes,
+            event.get_linearized_pdu_json(),
+            hash_algorithm,
+        )
+
+    # Non-linearized matrix doesn't care about other checks.
+    return True
+
+
 def compute_content_hash(
     event_dict: Dict[str, Any], hash_algorithm: Hasher
 ) -> Tuple[str, bytes]:
diff --git a/synapse/events/__init__.py b/synapse/events/__init__.py
index d628abc0a6..99b9d61847 100644
--- a/synapse/events/__init__.py
+++ b/synapse/events/__init__.py
@@ -389,6 +389,9 @@ class EventBase(metaclass=abc.ABCMeta):
 
         return pdu_json
 
+    def get_linearized_pdu_json(self) -> JsonDict:
+        raise NotImplementedError()
+
     def get_templated_pdu_json(self) -> JsonDict:
         """
         Return a JSON object suitable for a templated event, as used in the
@@ -620,7 +623,8 @@ class FrozenLinearizedEvent(FrozenEventV3):
     def get_linearized_pdu_json(self) -> JsonDict:
         # Get the full PDU and then remove fields from it.
         pdu = self.get_pdu_json()
-        pdu.pop("hashes")
+        # Strip everything except for the lpdu property from the hashes.
+        pdu["hashes"] = {"lpdu": pdu["hashes"]["lpdu"]}
         pdu.pop("auth_events")
         pdu.pop("prev_events")
         return pdu
diff --git a/synapse/federation/federation_base.py b/synapse/federation/federation_base.py
index a0c5048efc..21cb3e4675 100644
--- a/synapse/federation/federation_base.py
+++ b/synapse/federation/federation_base.py
@@ -20,7 +20,7 @@ from synapse.api.errors import Codes, SynapseError
 from synapse.api.room_versions import EventFormatVersions, RoomVersion
 from synapse.crypto.event_signing import check_event_content_hash
 from synapse.crypto.keyring import Keyring
-from synapse.events import EventBase, FrozenLinearizedEvent, make_event_from_dict
+from synapse.events import EventBase, make_event_from_dict
 from synapse.events.utils import prune_event, prune_event_dict, validate_canonicaljson
 from synapse.http.servlet import assert_params_in_dict
 from synapse.logging.opentracing import log_kv, trace
@@ -253,8 +253,6 @@ async def _check_sigs_on_pdu(
     # If this is a linearized PDU we may need to check signatures of the hub
     # and sender.
     if room_version.event_format == EventFormatVersions.LINEARIZED:
-        assert isinstance(pdu, FrozenLinearizedEvent)
-
         # If the event was sent via a hub server, check the signature of the
         # sender against the Linear PDU. (But only if the sender isn't the hub.)
         #