Add MSC4243 flag, verify event signatures from the user ID
This commit is contained in:
@@ -116,6 +116,8 @@ class RoomVersion:
|
||||
msc4289_creator_power_enabled: bool
|
||||
# MSC4291: Room IDs as hashes of the create event
|
||||
msc4291_room_ids_as_hashes: bool
|
||||
# MSC4243: User ID localparts as Account Keys
|
||||
msc4243_account_keys: bool
|
||||
|
||||
|
||||
class RoomVersions:
|
||||
@@ -140,6 +142,7 @@ class RoomVersions:
|
||||
msc3757_enabled=False,
|
||||
msc4289_creator_power_enabled=False,
|
||||
msc4291_room_ids_as_hashes=False,
|
||||
msc4243_account_keys=False,
|
||||
)
|
||||
V2 = RoomVersion(
|
||||
"2",
|
||||
@@ -162,6 +165,7 @@ class RoomVersions:
|
||||
msc3757_enabled=False,
|
||||
msc4289_creator_power_enabled=False,
|
||||
msc4291_room_ids_as_hashes=False,
|
||||
msc4243_account_keys=False,
|
||||
)
|
||||
V3 = RoomVersion(
|
||||
"3",
|
||||
@@ -184,6 +188,7 @@ class RoomVersions:
|
||||
msc3757_enabled=False,
|
||||
msc4289_creator_power_enabled=False,
|
||||
msc4291_room_ids_as_hashes=False,
|
||||
msc4243_account_keys=False,
|
||||
)
|
||||
V4 = RoomVersion(
|
||||
"4",
|
||||
@@ -206,6 +211,7 @@ class RoomVersions:
|
||||
msc3757_enabled=False,
|
||||
msc4289_creator_power_enabled=False,
|
||||
msc4291_room_ids_as_hashes=False,
|
||||
msc4243_account_keys=False,
|
||||
)
|
||||
V5 = RoomVersion(
|
||||
"5",
|
||||
@@ -228,6 +234,7 @@ class RoomVersions:
|
||||
msc3757_enabled=False,
|
||||
msc4289_creator_power_enabled=False,
|
||||
msc4291_room_ids_as_hashes=False,
|
||||
msc4243_account_keys=False,
|
||||
)
|
||||
V6 = RoomVersion(
|
||||
"6",
|
||||
@@ -250,6 +257,7 @@ class RoomVersions:
|
||||
msc3757_enabled=False,
|
||||
msc4289_creator_power_enabled=False,
|
||||
msc4291_room_ids_as_hashes=False,
|
||||
msc4243_account_keys=False,
|
||||
)
|
||||
V7 = RoomVersion(
|
||||
"7",
|
||||
@@ -272,6 +280,7 @@ class RoomVersions:
|
||||
msc3757_enabled=False,
|
||||
msc4289_creator_power_enabled=False,
|
||||
msc4291_room_ids_as_hashes=False,
|
||||
msc4243_account_keys=False,
|
||||
)
|
||||
V8 = RoomVersion(
|
||||
"8",
|
||||
@@ -294,6 +303,7 @@ class RoomVersions:
|
||||
msc3757_enabled=False,
|
||||
msc4289_creator_power_enabled=False,
|
||||
msc4291_room_ids_as_hashes=False,
|
||||
msc4243_account_keys=False,
|
||||
)
|
||||
V9 = RoomVersion(
|
||||
"9",
|
||||
@@ -316,6 +326,7 @@ class RoomVersions:
|
||||
msc3757_enabled=False,
|
||||
msc4289_creator_power_enabled=False,
|
||||
msc4291_room_ids_as_hashes=False,
|
||||
msc4243_account_keys=False,
|
||||
)
|
||||
V10 = RoomVersion(
|
||||
"10",
|
||||
@@ -338,6 +349,7 @@ class RoomVersions:
|
||||
msc3757_enabled=False,
|
||||
msc4289_creator_power_enabled=False,
|
||||
msc4291_room_ids_as_hashes=False,
|
||||
msc4243_account_keys=False,
|
||||
)
|
||||
MSC1767v10 = RoomVersion(
|
||||
# MSC1767 (Extensible Events) based on room version "10"
|
||||
@@ -361,6 +373,7 @@ class RoomVersions:
|
||||
msc3757_enabled=False,
|
||||
msc4289_creator_power_enabled=False,
|
||||
msc4291_room_ids_as_hashes=False,
|
||||
msc4243_account_keys=False,
|
||||
)
|
||||
MSC3757v10 = RoomVersion(
|
||||
# MSC3757 (Restricting who can overwrite a state event) based on room version "10"
|
||||
@@ -384,6 +397,7 @@ class RoomVersions:
|
||||
msc3757_enabled=True,
|
||||
msc4289_creator_power_enabled=False,
|
||||
msc4291_room_ids_as_hashes=False,
|
||||
msc4243_account_keys=False,
|
||||
)
|
||||
V11 = RoomVersion(
|
||||
"11",
|
||||
@@ -406,6 +420,7 @@ class RoomVersions:
|
||||
msc3757_enabled=False,
|
||||
msc4289_creator_power_enabled=False,
|
||||
msc4291_room_ids_as_hashes=False,
|
||||
msc4243_account_keys=False,
|
||||
)
|
||||
MSC3757v11 = RoomVersion(
|
||||
# MSC3757 (Restricting who can overwrite a state event) based on room version "11"
|
||||
@@ -429,6 +444,7 @@ class RoomVersions:
|
||||
msc3757_enabled=True,
|
||||
msc4289_creator_power_enabled=False,
|
||||
msc4291_room_ids_as_hashes=False,
|
||||
msc4243_account_keys=False,
|
||||
)
|
||||
HydraV11 = RoomVersion(
|
||||
"org.matrix.hydra.11",
|
||||
@@ -451,6 +467,7 @@ class RoomVersions:
|
||||
msc3757_enabled=False,
|
||||
msc4289_creator_power_enabled=True, # Changed from v11
|
||||
msc4291_room_ids_as_hashes=True, # Changed from v11
|
||||
msc4243_account_keys=False,
|
||||
)
|
||||
V12 = RoomVersion(
|
||||
"12",
|
||||
@@ -473,6 +490,30 @@ class RoomVersions:
|
||||
msc3757_enabled=False,
|
||||
msc4289_creator_power_enabled=True, # Changed from v11
|
||||
msc4291_room_ids_as_hashes=True, # Changed from v11
|
||||
msc4243_account_keys=False,
|
||||
)
|
||||
MSC4243v12 = RoomVersion(
|
||||
"org.matrix.12.4243",
|
||||
RoomDisposition.STABLE,
|
||||
EventFormatVersions.ROOM_V11_HYDRA_PLUS,
|
||||
StateResolutionVersions.V2_1, # Changed from v11
|
||||
enforce_key_validity=False, # No longer enforce key validity.
|
||||
special_case_aliases_auth=False,
|
||||
strict_canonicaljson=True,
|
||||
limit_notifications_power_levels=True,
|
||||
implicit_room_creator=True, # Used by MSC3820
|
||||
updated_redaction_rules=True, # Used by MSC3820
|
||||
restricted_join_rule=True,
|
||||
restricted_join_rule_fix=True,
|
||||
knock_join_rule=True,
|
||||
msc3389_relation_redactions=False,
|
||||
knock_restricted_join_rule=True,
|
||||
enforce_int_power_levels=True,
|
||||
msc3931_push_features=(),
|
||||
msc3757_enabled=False,
|
||||
msc4289_creator_power_enabled=True,
|
||||
msc4291_room_ids_as_hashes=True,
|
||||
msc4243_account_keys=True,
|
||||
)
|
||||
|
||||
|
||||
@@ -494,6 +535,7 @@ KNOWN_ROOM_VERSIONS: Dict[str, RoomVersion] = {
|
||||
RoomVersions.MSC3757v10,
|
||||
RoomVersions.MSC3757v11,
|
||||
RoomVersions.HydraV11,
|
||||
RoomVersions.MSC4243v12,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ from synapse.events import EventBase
|
||||
from synapse.events.utils import prune_event_dict
|
||||
from synapse.logging.context import make_deferred_yieldable, run_in_background
|
||||
from synapse.storage.keys import FetchKeyResult
|
||||
from synapse.types import JsonDict
|
||||
from synapse.types import JsonDict, UserID
|
||||
from synapse.util import unwrapFirstError
|
||||
from synapse.util.async_helpers import yieldable_gather_results
|
||||
from synapse.util.batching_queue import BatchingQueue
|
||||
@@ -83,6 +83,7 @@ class VerifyJsonRequest:
|
||||
get_json_object: Callable[[], JsonDict]
|
||||
minimum_valid_until_ts: int
|
||||
key_ids: List[str]
|
||||
key_ids_are_public_keys: bool = False
|
||||
|
||||
@staticmethod
|
||||
def from_json_object(
|
||||
@@ -118,6 +119,7 @@ class VerifyJsonRequest:
|
||||
lambda: prune_event_dict(event.room_version, event.get_pdu_json()),
|
||||
minimum_valid_until_ms,
|
||||
key_ids=key_ids,
|
||||
key_ids_are_public_keys=False,
|
||||
)
|
||||
|
||||
|
||||
@@ -265,6 +267,38 @@ class Keyring:
|
||||
)
|
||||
)
|
||||
|
||||
async def verify_event_for_account_key(
|
||||
self,
|
||||
user_id_str: str,
|
||||
event: EventBase,
|
||||
) -> None:
|
||||
"""Verify that the given event has been signed by the provided account key, as determined by
|
||||
the user_id provided.
|
||||
|
||||
Args:
|
||||
user_id_str: The MSC4243 user ID, consisting of an account key localpart and a domain.
|
||||
event: The PDU that should be signed with the account key. Room version must support MSC4243.
|
||||
"""
|
||||
assert event.room_version.msc4243_account_keys
|
||||
user_id = UserID.from_string(user_id_str)
|
||||
key_ids = list(event.signatures.get(user_id.domain, []))
|
||||
# only keep the key ID that matches the desired user ID we want to verify as.
|
||||
# Events can be signed by multiple parties e.g invites, restricted joins
|
||||
expected_key_id = "ed25519:" + user_id.localpart
|
||||
key_ids = [key_id for key_id in key_ids if key_id == expected_key_id]
|
||||
assert len(key_ids) == 1 # the user must have signed the event.
|
||||
await self.process_request(
|
||||
VerifyJsonRequest(
|
||||
user_id.domain,
|
||||
# We defer creating the redacted json object, as it uses a lot more
|
||||
# memory than the Event object itself.
|
||||
lambda: prune_event_dict(event.room_version, event.get_pdu_json()),
|
||||
0, # No validity times
|
||||
key_ids=key_ids,
|
||||
key_ids_are_public_keys=True,
|
||||
)
|
||||
)
|
||||
|
||||
async def process_request(self, verify_request: VerifyJsonRequest) -> None:
|
||||
"""Processes the `VerifyJsonRequest`. Raises if the object is not signed
|
||||
by the server, the signatures don't match or we failed to fetch the
|
||||
@@ -278,6 +312,15 @@ class Keyring:
|
||||
Codes.UNAUTHORIZED,
|
||||
)
|
||||
|
||||
if verify_request.key_ids_are_public_keys:
|
||||
# No need to fetch keys as we have them already.
|
||||
assert len(verify_request.key_ids) == 1
|
||||
key_id = verify_request.key_ids[0]
|
||||
key_bytes = decode_base64(key_id.removeprefix("ed25519:"))
|
||||
verify_key = decode_verify_key_bytes(key_id, key_bytes)
|
||||
await self._process_json(verify_key, verify_request)
|
||||
return
|
||||
|
||||
found_keys: Dict[str, FetchKeyResult] = {}
|
||||
|
||||
# If we are the originating server, short-circuit the key-fetch for any keys
|
||||
|
||||
@@ -241,16 +241,28 @@ async def _check_sigs_on_pdu(
|
||||
sender_domain = get_domain_from_id(pdu.sender)
|
||||
if not _is_invite_via_3pid(pdu):
|
||||
try:
|
||||
await keyring.verify_event_for_server(
|
||||
sender_domain,
|
||||
pdu,
|
||||
pdu.origin_server_ts if room_version.enforce_key_validity else 0,
|
||||
)
|
||||
if pdu.room_version.msc4243_account_keys:
|
||||
await keyring.verify_event_for_account_key(
|
||||
pdu.sender,
|
||||
pdu,
|
||||
)
|
||||
else:
|
||||
await keyring.verify_event_for_server(
|
||||
sender_domain,
|
||||
pdu,
|
||||
pdu.origin_server_ts if room_version.enforce_key_validity else 0,
|
||||
)
|
||||
except Exception as e:
|
||||
raise InvalidEventSignatureError(
|
||||
f"unable to verify signature for sender domain {sender_domain}: {e}",
|
||||
pdu.event_id,
|
||||
) from None
|
||||
if pdu.room_version.msc4243_account_keys:
|
||||
raise InvalidEventSignatureError(
|
||||
f"unable to verify signature for account key {pdu.sender}: {e}",
|
||||
pdu.event_id,
|
||||
) from None
|
||||
else:
|
||||
raise InvalidEventSignatureError(
|
||||
f"unable to verify signature for sender domain {sender_domain}: {e}",
|
||||
pdu.event_id,
|
||||
) from None
|
||||
|
||||
# now let's look for events where the sender's domain is different to the
|
||||
# event id's domain (normally only the case for joins/leaves), and add additional
|
||||
@@ -283,11 +295,17 @@ async def _check_sigs_on_pdu(
|
||||
pdu.content[EventContentFields.AUTHORISING_USER]
|
||||
)
|
||||
try:
|
||||
await keyring.verify_event_for_server(
|
||||
authorising_server,
|
||||
pdu,
|
||||
pdu.origin_server_ts if room_version.enforce_key_validity else 0,
|
||||
)
|
||||
if pdu.room_version.msc4243_account_keys:
|
||||
await keyring.verify_event_for_account_key(
|
||||
pdu.content[EventContentFields.AUTHORISING_USER],
|
||||
pdu,
|
||||
)
|
||||
else:
|
||||
await keyring.verify_event_for_server(
|
||||
authorising_server,
|
||||
pdu,
|
||||
pdu.origin_server_ts if room_version.enforce_key_validity else 0,
|
||||
)
|
||||
except Exception as e:
|
||||
raise InvalidEventSignatureError(
|
||||
f"unable to verify signature for authorising serve {authorising_server}: {e}",
|
||||
|
||||
@@ -34,12 +34,15 @@ from twisted.internet.defer import Deferred, ensureDeferred
|
||||
from twisted.internet.testing import MemoryReactor
|
||||
|
||||
from synapse.api.errors import SynapseError
|
||||
from synapse.api.room_versions import RoomVersions
|
||||
from synapse.crypto import keyring
|
||||
from synapse.crypto.event_signing import compute_event_signature
|
||||
from synapse.crypto.keyring import (
|
||||
PerspectivesKeyFetcher,
|
||||
ServerKeyFetcher,
|
||||
StoreKeyFetcher,
|
||||
)
|
||||
from synapse.events import make_event_from_dict
|
||||
from synapse.logging.context import (
|
||||
ContextRequest,
|
||||
LoggingContext,
|
||||
@@ -388,6 +391,35 @@ class KeyringTestCase(unittest.HomeserverTestCase):
|
||||
mock_fetcher1.get_keys.assert_called_once()
|
||||
mock_fetcher2.get_keys.assert_called_once()
|
||||
|
||||
def test_verify_event_for_account_key(self) -> None:
|
||||
"""Test basic functionality of verify_event_for_account_key.
|
||||
- That it parses the user ID correctly.
|
||||
- That it doesn't rely on key fetchers.
|
||||
"""
|
||||
room_version = RoomVersions.MSC4243v12
|
||||
|
||||
# Make a signing key and replace the key ID from '1' to be the base64 public key
|
||||
signing_key = signedjson.key.generate_signing_key("1")
|
||||
verify_key_str = encode_verify_key_base64(get_verify_key(signing_key))
|
||||
signing_key.version = verify_key_str
|
||||
domain = "can.be.anything.com"
|
||||
signing_user_id = f"@{verify_key_str}:{domain}"
|
||||
|
||||
event_dict = {
|
||||
"type": "m.room.create",
|
||||
"state_key": "",
|
||||
"sender": signing_user_id,
|
||||
"content": {
|
||||
"room_version": room_version.identifier,
|
||||
},
|
||||
}
|
||||
event_dict["signatures"] = compute_event_signature(
|
||||
room_version, event_dict, signature_name=domain, signing_key=signing_key
|
||||
)
|
||||
event = make_event_from_dict(event_dict, room_version)
|
||||
kr = keyring.Keyring(self.hs, key_fetchers=None)
|
||||
self.get_success(kr.verify_event_for_account_key(signing_user_id, event))
|
||||
|
||||
|
||||
@logcontext_clean
|
||||
class ServerKeyFetcherTestCase(unittest.HomeserverTestCase):
|
||||
|
||||
Reference in New Issue
Block a user