1717import logging
1818from typing import TYPE_CHECKING
1919
20+ from signedjson .key import decode_verify_key_bytes
21+ from unpaddedbase64 import decode_base64
22+
23+ from synapse .api .errors import SynapseError
24+ from synapse .crypto .keyring import VerifyJsonRequest
2025from synapse .events import EventBase
2126from synapse .types .handlers .policy_server import RECOMMENDATION_OK
2227from synapse .util .stringutils import parse_and_validate_server_name
2631
2732logger = logging .getLogger (__name__ )
2833
34+ POLICY_SERVER_EVENT_TYPE = "org.matrix.msc4284.policy"
35+ POLICY_SERVER_KEY_ID = "ed25519:policy_server"
36+
2937
3038class RoomPolicyHandler :
3139 def __init__ (self , hs : "HomeServer" ):
@@ -54,11 +62,11 @@ async def is_event_allowed(self, event: EventBase) -> bool:
5462 Returns:
5563 bool: True if the event is allowed in the room, False otherwise.
5664 """
57- if event .type == "org.matrix.msc4284.policy" and event .state_key is not None :
65+ if event .type == POLICY_SERVER_EVENT_TYPE and event .state_key is not None :
5866 return True # always allow policy server change events
5967
6068 policy_event = await self ._storage_controllers .state .get_current_state_event (
61- event .room_id , "org.matrix.msc4284.policy" , ""
69+ event .room_id , POLICY_SERVER_EVENT_TYPE , ""
6270 )
6371 if not policy_event :
6472 return True # no policy server == default allow
@@ -81,6 +89,22 @@ async def is_event_allowed(self, event: EventBase) -> bool:
8189 if not is_in_room :
8290 return True # policy server not in room == default allow
8391
92+ # Check if the event has been signed with the public key in the policy server state event.
93+ # If it is, we can save an HTTP hit.
94+ # We actually want to get the policy server state event BEFORE THE EVENT rather than
95+ # the current state value, else changing the public key will cause all of these checks to fail.
96+ # However, if we are checking outlier events (which we will due to is_event_allowed being called
97+ # near the edges at _check_sigs_and_hash) we won't know the state before the event, so the
98+ # only safe option is to use the current state
99+ public_key = policy_event .content .get ("public_key" , None )
100+ if public_key is not None and isinstance (public_key , str ):
101+ valid = await self ._verify_policy_server_signature (
102+ event , policy_server , public_key
103+ )
104+ if valid :
105+ return True
106+ # fallthrough to hit /check manually
107+
84108 # At this point, the server appears valid and is in the room, so ask it to check
85109 # the event.
86110 recommendation = await self ._federation_client .get_pdu_policy_recommendation (
@@ -90,3 +114,73 @@ async def is_event_allowed(self, event: EventBase) -> bool:
90114 return False
91115
92116 return True # default allow
117+
118+ async def _verify_policy_server_signature (
119+ self , event : EventBase , policy_server : str , public_key : str
120+ ) -> bool :
121+ # check the event is signed with this (via, public_key).
122+ verify_json_req = VerifyJsonRequest .from_event (policy_server , event , 0 )
123+ try :
124+ key_bytes = decode_base64 (public_key )
125+ verify_key = decode_verify_key_bytes (POLICY_SERVER_KEY_ID , key_bytes )
126+ # We would normally use KeyRing.verify_event_for_server but we can't here as we don't
127+ # want to fetch the server key, and instead want to use the public key in the state event.
128+ await self ._hs .get_keyring ().process_json (verify_key , verify_json_req )
129+ # if the event is correctly signed by the public key in the policy server state event = Allow
130+ return True
131+ except Exception as ex :
132+ logger .warning (
133+ "failed to verify event using public key in policy server event: %s" , ex
134+ )
135+ return False
136+
137+ async def ask_policy_server_to_sign_event (
138+ self , event : EventBase , verify : bool = False
139+ ) -> None :
140+ """Ask the policy server to sign this event. The signature is added to the event signatures block.
141+
142+ Does nothing if there is no policy server state event in the room. If the policy server
143+ refuses to sign the event (as it's marked as spam) does nothing.
144+
145+ Args:
146+ event: The event to sign
147+ verify: If True, verify that the signature is correctly signed by the public_key in the
148+ policy server state event.
149+ Raises:
150+ if verify=True and the policy server signed the event with an invalid signature. Does
151+ not raise if the policy server refuses to sign the event.
152+ """
153+ policy_event = await self ._storage_controllers .state .get_current_state_event (
154+ event .room_id , POLICY_SERVER_EVENT_TYPE , ""
155+ )
156+ if not policy_event :
157+ return
158+ policy_server = policy_event .content .get ("via" , None )
159+ if policy_server is None or not isinstance (policy_server , str ):
160+ return
161+ # Only ask to sign events if the policy state event has a public_key (so they can be subsequently verified)
162+ public_key = policy_event .content .get ("public_key" , None )
163+ if public_key is None or not isinstance (public_key , str ):
164+ return
165+
166+ # Ask the policy server to sign this event.
167+ # We set a smallish timeout here as we don't want to block event sending too long.
168+ signature = await self ._federation_client .ask_policy_server_to_sign_event (
169+ policy_server ,
170+ event ,
171+ timeout = 3000 ,
172+ )
173+ if (
174+ # the policy server returns {} if it refuses to sign the event.
175+ signature and len (signature ) > 0
176+ ):
177+ event .signatures .update (signature )
178+ if verify :
179+ is_valid = await self ._verify_policy_server_signature (
180+ event , policy_server , public_key
181+ )
182+ if not is_valid :
183+ raise SynapseError (
184+ 500 ,
185+ f"policy server { policy_server } failed to sign event correctly" ,
186+ )
0 commit comments