Merge branch 'master' into develop
This commit is contained in:
@@ -1,3 +1,12 @@
|
||||
# Synapse 1.147.1 (2026-02-12)
|
||||
|
||||
## Internal Changes
|
||||
|
||||
- Block federation requests and events authenticated using a known insecure signing key. See [CVE-2026-24044](https://www.cve.org/CVERecord?id=CVE-2026-24044) / [ELEMENTSEC-2025-1670](https://github.com/element-hq/ess-helm/security/advisories/GHSA-qwcj-h6m8-vp6q). ([\#19459](https://github.com/element-hq/synapse/issues/19459))
|
||||
|
||||
|
||||
|
||||
|
||||
# Synapse 1.147.0 (2026-02-10)
|
||||
|
||||
No significant changes since 1.147.0rc1.
|
||||
|
||||
6
debian/changelog
vendored
6
debian/changelog
vendored
@@ -1,3 +1,9 @@
|
||||
matrix-synapse-py3 (1.147.1) stable; urgency=medium
|
||||
|
||||
* New synapse release 1.147.1.
|
||||
|
||||
-- Synapse Packaging team <packages@matrix.org> Thu, 12 Feb 2026 15:45:15 +0000
|
||||
|
||||
matrix-synapse-py3 (1.147.0) stable; urgency=medium
|
||||
|
||||
* New synapse release 1.147.0.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "matrix-synapse"
|
||||
version = "1.147.0"
|
||||
version = "1.147.1"
|
||||
description = "Homeserver for the Matrix decentralised comms protocol"
|
||||
readme = "README.rst"
|
||||
authors = [
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
import abc
|
||||
import logging
|
||||
from contextlib import ExitStack
|
||||
from http import HTTPStatus
|
||||
from typing import TYPE_CHECKING, Callable, Iterable
|
||||
|
||||
import attr
|
||||
@@ -60,6 +61,15 @@ if TYPE_CHECKING:
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# List of Unpadded Base64 server signing keys that are known to be vulnerable to attack.
|
||||
# Incoming requests from homeservers using any of these keys should be refused.
|
||||
# Events containing signatures using any of these keys should be refused.
|
||||
BANNED_SERVER_SIGNING_KEYS = (
|
||||
# ELEMENTSEC-2025-1670
|
||||
"l/O9hxMVKB6Lg+3Hqf0FQQZhVESQcMzbPN1Cz2nM3og=",
|
||||
)
|
||||
|
||||
|
||||
@attr.s(slots=True, frozen=True, cmp=False, auto_attribs=True)
|
||||
class VerifyJsonRequest:
|
||||
"""
|
||||
@@ -349,6 +359,19 @@ class Keyring:
|
||||
if key_result.valid_until_ts < verify_request.minimum_valid_until_ts:
|
||||
continue
|
||||
|
||||
key = encode_verify_key_base64(key_result.verify_key)
|
||||
if key in BANNED_SERVER_SIGNING_KEYS:
|
||||
raise SynapseError(
|
||||
HTTPStatus.UNAUTHORIZED,
|
||||
"Server signing key %s:%s for server %s has been banned by this server"
|
||||
% (
|
||||
key_result.verify_key.alg,
|
||||
key_result.verify_key.version,
|
||||
verify_request.server_name,
|
||||
),
|
||||
Codes.UNAUTHORIZED,
|
||||
)
|
||||
|
||||
await self.process_json(key_result.verify_key, verify_request)
|
||||
verified = True
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
#
|
||||
import time
|
||||
from typing import Any, cast
|
||||
from unittest.mock import Mock
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import attr
|
||||
import canonicaljson
|
||||
@@ -238,6 +238,51 @@ class KeyringTestCase(unittest.HomeserverTestCase):
|
||||
# self.assertFalse(d.called)
|
||||
self.get_success(d)
|
||||
|
||||
def test_verify_json_for_server_using_banned_key(self) -> None:
|
||||
"""Ensure that JSON signed using a banned server_signing_key fails verification."""
|
||||
kr = keyring.Keyring(self.hs)
|
||||
|
||||
banned_signing_key = signedjson.key.generate_signing_key("1")
|
||||
r = self.hs.get_datastores().main.store_server_keys_response(
|
||||
"server9",
|
||||
from_server="test",
|
||||
ts_added_ms=int(time.time() * 1000),
|
||||
verify_keys={
|
||||
get_key_id(banned_signing_key): FetchKeyResult(
|
||||
verify_key=get_verify_key(banned_signing_key), valid_until_ts=1000
|
||||
)
|
||||
},
|
||||
# The entire response gets signed & stored, just include the bits we
|
||||
# care about.
|
||||
response_json={
|
||||
"verify_keys": {
|
||||
get_key_id(banned_signing_key): {
|
||||
"key": encode_verify_key_base64(
|
||||
get_verify_key(banned_signing_key)
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
self.get_success(r)
|
||||
|
||||
json1: JsonDict = {}
|
||||
signedjson.sign.sign_json(json1, "server9", banned_signing_key)
|
||||
|
||||
# Ensure the signatures check out normally
|
||||
d = kr.verify_json_for_server("server9", json1, 500)
|
||||
self.get_success(d)
|
||||
|
||||
# Patch the list of banned signing keys and ensure the signature check fails
|
||||
with patch.object(
|
||||
keyring,
|
||||
"BANNED_SERVER_SIGNING_KEYS",
|
||||
(encode_verify_key_base64(get_verify_key(banned_signing_key))),
|
||||
):
|
||||
# should fail on a signed object signed by the banned key
|
||||
d = kr.verify_json_for_server("server9", json1, 500)
|
||||
self.get_failure(d, SynapseError)
|
||||
|
||||
def test_verify_for_local_server(self) -> None:
|
||||
"""Ensure that locally signed JSON can be verified without fetching keys
|
||||
over federation
|
||||
|
||||
68
tests/federation/test_federation_base.py
Normal file
68
tests/federation/test_federation_base.py
Normal file
@@ -0,0 +1,68 @@
|
||||
#
|
||||
# This file is licensed under the Affero General Public License (AGPL) version 3.
|
||||
#
|
||||
# Copyright (C) 2026 New Vector, Ltd
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# See the GNU Affero General Public License for more details:
|
||||
# <https://www.gnu.org/licenses/agpl_3.0.html>.
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from signedjson.key import encode_verify_key_base64, get_verify_key
|
||||
|
||||
from synapse.crypto import keyring
|
||||
from synapse.crypto.event_signing import add_hashes_and_signatures
|
||||
from synapse.events import make_event_from_dict
|
||||
from synapse.federation.federation_base import InvalidEventSignatureError
|
||||
|
||||
from tests import unittest
|
||||
|
||||
|
||||
class FederationBaseTestCase(unittest.HomeserverTestCase):
|
||||
def test_events_signed_by_banned_key_are_refused(self) -> None:
|
||||
"""Ensure that event JSON signed using a banned server_signing_key fails verification."""
|
||||
event_dict = {
|
||||
"content": {"body": "Here is the message content"},
|
||||
"event_id": "$0:domain",
|
||||
"origin_server_ts": 1000000,
|
||||
"type": "m.room.message",
|
||||
"room_id": "!r:domain",
|
||||
"sender": f"@u:{self.hs.config.server.server_name}",
|
||||
"signatures": {},
|
||||
"unsigned": {"age_ts": 1000000},
|
||||
}
|
||||
|
||||
add_hashes_and_signatures(
|
||||
self.hs.config.server.default_room_version,
|
||||
event_dict,
|
||||
self.hs.config.server.server_name,
|
||||
self.hs.signing_key,
|
||||
)
|
||||
event = make_event_from_dict(event_dict)
|
||||
fs = self.hs.get_federation_server()
|
||||
|
||||
# Ensure the signatures check out normally
|
||||
self.get_success(
|
||||
fs._check_sigs_and_hash(self.hs.config.server.default_room_version, event)
|
||||
)
|
||||
|
||||
# Patch the list of banned signing keys and ensure the signature check fails
|
||||
with patch.object(
|
||||
keyring,
|
||||
"BANNED_SERVER_SIGNING_KEYS",
|
||||
(encode_verify_key_base64(get_verify_key(self.hs.signing_key))),
|
||||
):
|
||||
self.get_failure(
|
||||
fs._check_sigs_and_hash(
|
||||
self.hs.config.server.default_room_version, event
|
||||
),
|
||||
InvalidEventSignatureError,
|
||||
)
|
||||
Reference in New Issue
Block a user