17
17
import logging
18
18
from typing import TYPE_CHECKING
19
19
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
20
25
from synapse .events import EventBase
21
26
from synapse .types .handlers .policy_server import RECOMMENDATION_OK
22
27
from synapse .util .stringutils import parse_and_validate_server_name
26
31
27
32
logger = logging .getLogger (__name__ )
28
33
34
+ POLICY_SERVER_EVENT_TYPE = "org.matrix.msc4284.policy"
35
+ POLICY_SERVER_KEY_ID = "ed25519:policy_server"
36
+
29
37
30
38
class RoomPolicyHandler :
31
39
def __init__ (self , hs : "HomeServer" ):
@@ -54,11 +62,11 @@ async def is_event_allowed(self, event: EventBase) -> bool:
54
62
Returns:
55
63
bool: True if the event is allowed in the room, False otherwise.
56
64
"""
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 :
58
66
return True # always allow policy server change events
59
67
60
68
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 , ""
62
70
)
63
71
if not policy_event :
64
72
return True # no policy server == default allow
@@ -81,6 +89,22 @@ async def is_event_allowed(self, event: EventBase) -> bool:
81
89
if not is_in_room :
82
90
return True # policy server not in room == default allow
83
91
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
+
84
108
# At this point, the server appears valid and is in the room, so ask it to check
85
109
# the event.
86
110
recommendation = await self ._federation_client .get_pdu_policy_recommendation (
@@ -90,3 +114,73 @@ async def is_event_allowed(self, event: EventBase) -> bool:
90
114
return False
91
115
92
116
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