mirror of
https://github.com/element-hq/synapse.git
synced 2024-11-27 03:58:06 +03:00
Merge pull request #4493 from matrix-org/erikj/refactor_event_signing
Refactor event signing to work on dicts
This commit is contained in:
commit
17709f8f9c
3 changed files with 104 additions and 41 deletions
1
changelog.d/4493.misc
Normal file
1
changelog.d/4493.misc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Add infrastructure to support different event formats
|
|
@ -23,14 +23,14 @@ from signedjson.sign import sign_json
|
||||||
from unpaddedbase64 import decode_base64, encode_base64
|
from unpaddedbase64 import decode_base64, encode_base64
|
||||||
|
|
||||||
from synapse.api.errors import Codes, SynapseError
|
from synapse.api.errors import Codes, SynapseError
|
||||||
from synapse.events.utils import prune_event
|
from synapse.events.utils import prune_event, prune_event_dict
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def check_event_content_hash(event, hash_algorithm=hashlib.sha256):
|
def check_event_content_hash(event, hash_algorithm=hashlib.sha256):
|
||||||
"""Check whether the hash for this PDU matches the contents"""
|
"""Check whether the hash for this PDU matches the contents"""
|
||||||
name, expected_hash = compute_content_hash(event, hash_algorithm)
|
name, expected_hash = compute_content_hash(event.get_pdu_json(), hash_algorithm)
|
||||||
logger.debug("Expecting hash: %s", encode_base64(expected_hash))
|
logger.debug("Expecting hash: %s", encode_base64(expected_hash))
|
||||||
|
|
||||||
# some malformed events lack a 'hashes'. Protect against it being missing
|
# some malformed events lack a 'hashes'. Protect against it being missing
|
||||||
|
@ -59,35 +59,70 @@ def check_event_content_hash(event, hash_algorithm=hashlib.sha256):
|
||||||
return message_hash_bytes == expected_hash
|
return message_hash_bytes == expected_hash
|
||||||
|
|
||||||
|
|
||||||
def compute_content_hash(event, hash_algorithm):
|
def compute_content_hash(event_dict, hash_algorithm):
|
||||||
event_json = event.get_pdu_json()
|
"""Compute the content hash of an event, which is the hash of the
|
||||||
event_json.pop("age_ts", None)
|
unredacted event.
|
||||||
event_json.pop("unsigned", None)
|
|
||||||
event_json.pop("signatures", None)
|
|
||||||
event_json.pop("hashes", None)
|
|
||||||
event_json.pop("outlier", None)
|
|
||||||
event_json.pop("destinations", None)
|
|
||||||
|
|
||||||
event_json_bytes = encode_canonical_json(event_json)
|
Args:
|
||||||
|
event_dict (dict): The unredacted event as a dict
|
||||||
|
hash_algorithm: A hasher from `hashlib`, e.g. hashlib.sha256, to use
|
||||||
|
to hash the event
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple[str, bytes]: A tuple of the name of hash and the hash as raw
|
||||||
|
bytes.
|
||||||
|
"""
|
||||||
|
event_dict = dict(event_dict)
|
||||||
|
event_dict.pop("age_ts", None)
|
||||||
|
event_dict.pop("unsigned", None)
|
||||||
|
event_dict.pop("signatures", None)
|
||||||
|
event_dict.pop("hashes", None)
|
||||||
|
event_dict.pop("outlier", None)
|
||||||
|
event_dict.pop("destinations", None)
|
||||||
|
|
||||||
|
event_json_bytes = encode_canonical_json(event_dict)
|
||||||
|
|
||||||
hashed = hash_algorithm(event_json_bytes)
|
hashed = hash_algorithm(event_json_bytes)
|
||||||
return (hashed.name, hashed.digest())
|
return (hashed.name, hashed.digest())
|
||||||
|
|
||||||
|
|
||||||
def compute_event_reference_hash(event, hash_algorithm=hashlib.sha256):
|
def compute_event_reference_hash(event, hash_algorithm=hashlib.sha256):
|
||||||
|
"""Computes the event reference hash. This is the hash of the redacted
|
||||||
|
event.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
event (FrozenEvent)
|
||||||
|
hash_algorithm: A hasher from `hashlib`, e.g. hashlib.sha256, to use
|
||||||
|
to hash the event
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple[str, bytes]: A tuple of the name of hash and the hash as raw
|
||||||
|
bytes.
|
||||||
|
"""
|
||||||
tmp_event = prune_event(event)
|
tmp_event = prune_event(event)
|
||||||
event_json = tmp_event.get_pdu_json()
|
event_dict = tmp_event.get_pdu_json()
|
||||||
event_json.pop("signatures", None)
|
event_dict.pop("signatures", None)
|
||||||
event_json.pop("age_ts", None)
|
event_dict.pop("age_ts", None)
|
||||||
event_json.pop("unsigned", None)
|
event_dict.pop("unsigned", None)
|
||||||
event_json_bytes = encode_canonical_json(event_json)
|
event_json_bytes = encode_canonical_json(event_dict)
|
||||||
hashed = hash_algorithm(event_json_bytes)
|
hashed = hash_algorithm(event_json_bytes)
|
||||||
return (hashed.name, hashed.digest())
|
return (hashed.name, hashed.digest())
|
||||||
|
|
||||||
|
|
||||||
def compute_event_signature(event, signature_name, signing_key):
|
def compute_event_signature(event_dict, signature_name, signing_key):
|
||||||
tmp_event = prune_event(event)
|
"""Compute the signature of the event for the given name and key.
|
||||||
redact_json = tmp_event.get_pdu_json()
|
|
||||||
|
Args:
|
||||||
|
event_dict (dict): The event as a dict
|
||||||
|
signature_name (str): The name of the entity signing the event
|
||||||
|
(typically the server's hostname).
|
||||||
|
signing_key (syutil.crypto.SigningKey): The key to sign with
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict[str, dict[str, str]]: Returns a dictionary in the same format of
|
||||||
|
an event's signatures field.
|
||||||
|
"""
|
||||||
|
redact_json = prune_event_dict(event_dict)
|
||||||
redact_json.pop("age_ts", None)
|
redact_json.pop("age_ts", None)
|
||||||
redact_json.pop("unsigned", None)
|
redact_json.pop("unsigned", None)
|
||||||
logger.debug("Signing event: %s", encode_canonical_json(redact_json))
|
logger.debug("Signing event: %s", encode_canonical_json(redact_json))
|
||||||
|
@ -98,23 +133,27 @@ def compute_event_signature(event, signature_name, signing_key):
|
||||||
|
|
||||||
def add_hashes_and_signatures(event, signature_name, signing_key,
|
def add_hashes_and_signatures(event, signature_name, signing_key,
|
||||||
hash_algorithm=hashlib.sha256):
|
hash_algorithm=hashlib.sha256):
|
||||||
# if hasattr(event, "old_state_events"):
|
"""Add content hash and sign the event
|
||||||
# state_json_bytes = encode_canonical_json(
|
|
||||||
# [e.event_id for e in event.old_state_events.values()]
|
|
||||||
# )
|
|
||||||
# hashed = hash_algorithm(state_json_bytes)
|
|
||||||
# event.state_hash = {
|
|
||||||
# hashed.name: encode_base64(hashed.digest())
|
|
||||||
# }
|
|
||||||
|
|
||||||
name, digest = compute_content_hash(event, hash_algorithm=hash_algorithm)
|
Args:
|
||||||
|
event_dict (EventBuilder): The event to add hashes to and sign
|
||||||
|
signature_name (str): The name of the entity signing the event
|
||||||
|
(typically the server's hostname).
|
||||||
|
signing_key (syutil.crypto.SigningKey): The key to sign with
|
||||||
|
hash_algorithm: A hasher from `hashlib`, e.g. hashlib.sha256, to use
|
||||||
|
to hash the event
|
||||||
|
"""
|
||||||
|
|
||||||
|
name, digest = compute_content_hash(
|
||||||
|
event.get_pdu_json(), hash_algorithm=hash_algorithm,
|
||||||
|
)
|
||||||
|
|
||||||
if not hasattr(event, "hashes"):
|
if not hasattr(event, "hashes"):
|
||||||
event.hashes = {}
|
event.hashes = {}
|
||||||
event.hashes[name] = encode_base64(digest)
|
event.hashes[name] = encode_base64(digest)
|
||||||
|
|
||||||
event.signatures = compute_event_signature(
|
event.signatures = compute_event_signature(
|
||||||
event,
|
event.get_pdu_json(),
|
||||||
signature_name=signature_name,
|
signature_name=signature_name,
|
||||||
signing_key=signing_key,
|
signing_key=signing_key,
|
||||||
)
|
)
|
||||||
|
|
|
@ -38,8 +38,31 @@ def prune_event(event):
|
||||||
This is used when we "redact" an event. We want to remove all fields that
|
This is used when we "redact" an event. We want to remove all fields that
|
||||||
the user has specified, but we do want to keep necessary information like
|
the user has specified, but we do want to keep necessary information like
|
||||||
type, state_key etc.
|
type, state_key etc.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
event (FrozenEvent)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
FrozenEvent
|
||||||
|
"""
|
||||||
|
pruned_event_dict = prune_event_dict(event.get_dict())
|
||||||
|
|
||||||
|
from . import event_type_from_format_version
|
||||||
|
return event_type_from_format_version(event.format_version)(
|
||||||
|
pruned_event_dict, event.internal_metadata.get_dict()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def prune_event_dict(event_dict):
|
||||||
|
"""Redacts the event_dict in the same way as `prune_event`, except it
|
||||||
|
operates on dicts rather than event objects
|
||||||
|
|
||||||
|
Args:
|
||||||
|
event_dict (dict)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: A copy of the pruned event dict
|
||||||
"""
|
"""
|
||||||
event_type = event.type
|
|
||||||
|
|
||||||
allowed_keys = [
|
allowed_keys = [
|
||||||
"event_id",
|
"event_id",
|
||||||
|
@ -59,13 +82,13 @@ def prune_event(event):
|
||||||
"membership",
|
"membership",
|
||||||
]
|
]
|
||||||
|
|
||||||
event_dict = event.get_dict()
|
event_type = event_dict["type"]
|
||||||
|
|
||||||
new_content = {}
|
new_content = {}
|
||||||
|
|
||||||
def add_fields(*fields):
|
def add_fields(*fields):
|
||||||
for field in fields:
|
for field in fields:
|
||||||
if field in event.content:
|
if field in event_dict["content"]:
|
||||||
new_content[field] = event_dict["content"][field]
|
new_content[field] = event_dict["content"][field]
|
||||||
|
|
||||||
if event_type == EventTypes.Member:
|
if event_type == EventTypes.Member:
|
||||||
|
@ -98,17 +121,17 @@ def prune_event(event):
|
||||||
|
|
||||||
allowed_fields["content"] = new_content
|
allowed_fields["content"] = new_content
|
||||||
|
|
||||||
allowed_fields["unsigned"] = {}
|
unsigned = {}
|
||||||
|
allowed_fields["unsigned"] = unsigned
|
||||||
|
|
||||||
if "age_ts" in event.unsigned:
|
event_unsigned = event_dict.get("unsigned", {})
|
||||||
allowed_fields["unsigned"]["age_ts"] = event.unsigned["age_ts"]
|
|
||||||
if "replaces_state" in event.unsigned:
|
|
||||||
allowed_fields["unsigned"]["replaces_state"] = event.unsigned["replaces_state"]
|
|
||||||
|
|
||||||
return type(event)(
|
if "age_ts" in event_unsigned:
|
||||||
allowed_fields,
|
unsigned["age_ts"] = event_unsigned["age_ts"]
|
||||||
internal_metadata_dict=event.internal_metadata.get_dict()
|
if "replaces_state" in event_unsigned:
|
||||||
)
|
unsigned["replaces_state"] = event_unsigned["replaces_state"]
|
||||||
|
|
||||||
|
return allowed_fields
|
||||||
|
|
||||||
|
|
||||||
def _copy_field(src, dst, field):
|
def _copy_field(src, dst, field):
|
||||||
|
|
Loading…
Reference in a new issue