From 1417924be58355d271d1bc8731b21a246935e1f7 Mon Sep 17 00:00:00 2001 From: Shane Jaroch Date: Wed, 15 Jul 2026 04:57:18 -0400 Subject: [PATCH 01/19] Derive generated signing key ids from public key content --- changelog.d/19930.misc | 1 + synapse/_scripts/generate_signing_key.py | 7 ++-- synapse/config/key.py | 42 ++++++++++++++++++++---- tests/config/test_generate.py | 24 ++++++++++++++ 4 files changed, 64 insertions(+), 10 deletions(-) create mode 100644 changelog.d/19930.misc diff --git a/changelog.d/19930.misc b/changelog.d/19930.misc new file mode 100644 index 00000000000..f37ad4a2046 --- /dev/null +++ b/changelog.d/19930.misc @@ -0,0 +1 @@ +Generate new server signing keys with URL-safe public-key-derived key ids and warn when existing signing keys use numeric ids. diff --git a/synapse/_scripts/generate_signing_key.py b/synapse/_scripts/generate_signing_key.py index ba8aba6ee47..ed923fbd9d2 100755 --- a/synapse/_scripts/generate_signing_key.py +++ b/synapse/_scripts/generate_signing_key.py @@ -23,9 +23,9 @@ import os import sys -from signedjson.key import generate_signing_key, write_signing_keys +from signedjson.key import write_signing_keys -from synapse.util.stringutils import random_string +from synapse.config.key import _generate_signing_key def main() -> None: @@ -40,8 +40,7 @@ def main() -> None: ) args = parser.parse_args() - key_id = "a_" + random_string(4) - key = (generate_signing_key(key_id),) + key = (_generate_signing_key(),) if args.output_file == "-": write_signing_keys(sys.stdout, key) else: diff --git a/synapse/config/key.py b/synapse/config/key.py index bfeeac5e307..ce767a16f8e 100644 --- a/synapse/config/key.py +++ b/synapse/config/key.py @@ -23,6 +23,7 @@ import hashlib import logging import os +import re from typing import TYPE_CHECKING, Any, Iterator import attr @@ -38,10 +39,10 @@ read_signing_keys, write_signing_keys, ) -from unpaddedbase64 import decode_base64 +from unpaddedbase64 import decode_base64, encode_base64 from synapse.types import JsonDict -from synapse.util.stringutils import random_string, random_string_with_symbols +from synapse.util.stringutils import random_string_with_symbols from ._base import Config, ConfigError, read_file @@ -103,6 +104,34 @@ logger = logging.getLogger(__name__) +_WEAK_SIGNING_KEY_VERSION = re.compile(r"^\d+$") + + +def _derive_signing_key_version(signing_key: SigningKey) -> str: + """Derive a stable, path-safe key version from the Ed25519 verify key.""" + + digest = hashlib.sha256(signing_key.verify_key.encode()).digest() + fingerprint = encode_base64(digest[:16], urlsafe=True) + return f"k_{fingerprint}" + + +def _generate_signing_key() -> SigningKey: + signing_key = generate_signing_key("pending_key_id") + signing_key.version = _derive_signing_key_version(signing_key) + return signing_key + + +def _warn_on_weak_signing_key_versions(signing_keys: list[SigningKey]) -> None: + for signing_key in signing_keys: + if _WEAK_SIGNING_KEY_VERSION.match(signing_key.version): + logger.warning( + "Signing key %s:%s uses a numeric key id. Numeric signing key ids " + "are easy to reuse accidentally during key rotation; use a unique " + "key id for each distinct signing key.", + signing_key.alg, + signing_key.version, + ) + @attr.s(slots=True, auto_attribs=True) class TrustedKeyServer: @@ -136,6 +165,8 @@ def read_config( self.signing_key = self.read_signing_keys(signing_key_path, "signing_key") + _warn_on_weak_signing_key_versions(self.signing_key) + self.old_signing_keys = self.read_old_signing_keys( config.get("old_signing_keys") ) @@ -314,16 +345,15 @@ def generate_files(self, config: dict[str, Any], config_dir_path: str) -> None: with open( signing_key_path, "w", opener=lambda p, f: os.open(p, f, mode=0o640) ) as signing_key_file: - key_id = "a_" + random_string(4) - write_signing_keys(signing_key_file, (generate_signing_key(key_id),)) + write_signing_keys(signing_key_file, (_generate_signing_key(),)) else: signing_keys = self.read_file(signing_key_path, "signing_key") if len(signing_keys.split("\n")[0].split()) == 1: # handle keys in the old format. - key_id = "a_" + random_string(4) key = decode_signing_key_base64( - NACL_ED25519, key_id, signing_keys.split("\n")[0] + NACL_ED25519, "pending_key_id", signing_keys.split("\n")[0] ) + key.version = _derive_signing_key_version(key) with open( signing_key_path, "w", opener=lambda p, f: os.open(p, f, mode=0o640) ) as signing_key_file: diff --git a/tests/config/test_generate.py b/tests/config/test_generate.py index 5ab96e16e1b..66039a0dfd9 100644 --- a/tests/config/test_generate.py +++ b/tests/config/test_generate.py @@ -26,9 +26,12 @@ from contextlib import redirect_stdout from io import StringIO +from signedjson.key import generate_signing_key, read_signing_keys, write_signing_keys + from synapse.config.homeserver import HomeServerConfig from tests import unittest +from tests.utils import default_config class ConfigGenerationTestCase(unittest.TestCase): @@ -63,6 +66,27 @@ def test_generate_config_generates_files(self) -> None: os.path.join(os.getcwd(), "homeserver.log"), ) + with open(os.path.join(self.dir, "lemurs.win.signing.key")) as f: + keys = read_signing_keys(f) + + self.assertEqual(1, len(keys)) + self.assertRegex(keys[0].version, r"^k_[A-Za-z0-9_-]{22}$") + + def test_numeric_signing_key_version_warns(self) -> None: + signing_key = generate_signing_key("1") + signing_key_file = StringIO() + write_signing_keys(signing_key_file, (signing_key,)) + + config_dict = default_config(server_name="test") + config_dict["signing_key"] = signing_key_file.getvalue() + + config = HomeServerConfig() + with self.assertLogs("synapse.config.key", level="WARNING") as logs: + config.parse_config_dict(config_dict, "", "") + + self.assertEqual("1", config.key.signing_key[0].version) + self.assertIn("uses a numeric key id", "\n".join(logs.output)) + def assert_log_filename_is(self, log_config_file: str, expected: str) -> None: with open(log_config_file) as f: config = f.read() From d6deda73142f68b5c364588a0b0d2343d792b28d Mon Sep 17 00:00:00 2001 From: Shane Jaroch Date: Wed, 15 Jul 2026 05:53:36 -0400 Subject: [PATCH 02/19] Keep legacy key file migration out of key rotation --- synapse/config/key.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/synapse/config/key.py b/synapse/config/key.py index ce767a16f8e..cd58f7cc15e 100644 --- a/synapse/config/key.py +++ b/synapse/config/key.py @@ -42,7 +42,7 @@ from unpaddedbase64 import decode_base64, encode_base64 from synapse.types import JsonDict -from synapse.util.stringutils import random_string_with_symbols +from synapse.util.stringutils import random_string, random_string_with_symbols from ._base import Config, ConfigError, read_file @@ -351,9 +351,8 @@ def generate_files(self, config: dict[str, Any], config_dir_path: str) -> None: if len(signing_keys.split("\n")[0].split()) == 1: # handle keys in the old format. key = decode_signing_key_base64( - NACL_ED25519, "pending_key_id", signing_keys.split("\n")[0] + NACL_ED25519, "a_" + random_string(4), signing_keys.split("\n")[0] ) - key.version = _derive_signing_key_version(key) with open( signing_key_path, "w", opener=lambda p, f: os.open(p, f, mode=0o640) ) as signing_key_file: From 7fd5c1f3832a6401a180721b0c4e4875aa8fd205 Mon Sep 17 00:00:00 2001 From: Shane Jaroch Date: Wed, 15 Jul 2026 06:01:37 -0400 Subject: [PATCH 03/19] Revert "Keep legacy key file migration out of key rotation" This reverts commit d6deda73142f68b5c364588a0b0d2343d792b28d. --- synapse/config/key.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/synapse/config/key.py b/synapse/config/key.py index cd58f7cc15e..ce767a16f8e 100644 --- a/synapse/config/key.py +++ b/synapse/config/key.py @@ -42,7 +42,7 @@ from unpaddedbase64 import decode_base64, encode_base64 from synapse.types import JsonDict -from synapse.util.stringutils import random_string, random_string_with_symbols +from synapse.util.stringutils import random_string_with_symbols from ._base import Config, ConfigError, read_file @@ -351,8 +351,9 @@ def generate_files(self, config: dict[str, Any], config_dir_path: str) -> None: if len(signing_keys.split("\n")[0].split()) == 1: # handle keys in the old format. key = decode_signing_key_base64( - NACL_ED25519, "a_" + random_string(4), signing_keys.split("\n")[0] + NACL_ED25519, "pending_key_id", signing_keys.split("\n")[0] ) + key.version = _derive_signing_key_version(key) with open( signing_key_path, "w", opener=lambda p, f: os.open(p, f, mode=0o640) ) as signing_key_file: From 54ba57f8903aec8f48a121c4c602d8e3e110d306 Mon Sep 17 00:00:00 2001 From: Shane Jaroch Date: Wed, 15 Jul 2026 06:03:41 -0400 Subject: [PATCH 04/19] Stop auto-rewriting deprecated signing key files --- synapse/config/key.py | 15 +++++---------- tests/config/test_generate.py | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/synapse/config/key.py b/synapse/config/key.py index ce767a16f8e..5ae004d7eac 100644 --- a/synapse/config/key.py +++ b/synapse/config/key.py @@ -29,10 +29,8 @@ import attr import jsonschema from signedjson.key import ( - NACL_ED25519, SigningKey, VerifyKey, - decode_signing_key_base64, decode_verify_key_bytes, generate_signing_key, is_signing_algorithm_supported, @@ -349,15 +347,12 @@ def generate_files(self, config: dict[str, Any], config_dir_path: str) -> None: else: signing_keys = self.read_file(signing_key_path, "signing_key") if len(signing_keys.split("\n")[0].split()) == 1: - # handle keys in the old format. - key = decode_signing_key_base64( - NACL_ED25519, "pending_key_id", signing_keys.split("\n")[0] + raise ConfigError( + "Signing key file %s uses deprecated one-column format. " + "Rotate the signing key explicitly or rewrite the file to " + "the three-column 'algorithm version key' format." + % (signing_key_path,) ) - key.version = _derive_signing_key_version(key) - with open( - signing_key_path, "w", opener=lambda p, f: os.open(p, f, mode=0o640) - ) as signing_key_file: - write_signing_keys(signing_key_file, (key,)) def _perspectives_to_key_servers(config: JsonDict) -> Iterator[JsonDict]: diff --git a/tests/config/test_generate.py b/tests/config/test_generate.py index 66039a0dfd9..51a81484b19 100644 --- a/tests/config/test_generate.py +++ b/tests/config/test_generate.py @@ -28,6 +28,7 @@ from signedjson.key import generate_signing_key, read_signing_keys, write_signing_keys +from synapse.config import ConfigError from synapse.config.homeserver import HomeServerConfig from tests import unittest @@ -87,6 +88,19 @@ def test_numeric_signing_key_version_warns(self) -> None: self.assertEqual("1", config.key.signing_key[0].version) self.assertIn("uses a numeric key id", "\n".join(logs.output)) + def test_deprecated_one_column_signing_key_fails(self) -> None: + self.test_generate_config_generates_files() + + signing_key_path = os.path.join(self.dir, "lemurs.win.signing.key") + with open(signing_key_path) as f: + signing_key = f.read().split()[2] + + with open(signing_key_path, "w") as f: + f.write(signing_key + "\n") + + with self.assertRaisesRegex(ConfigError, "deprecated one-column format"): + HomeServerConfig.load_or_generate_config("", ["-c", self.file]) + def assert_log_filename_is(self, log_config_file: str, expected: str) -> None: with open(log_config_file) as f: config = f.read() From 68e2b77c787821c27f9246c415b1813891e03e71 Mon Sep 17 00:00:00 2001 From: Shane Jaroch Date: Wed, 15 Jul 2026 06:29:10 -0400 Subject: [PATCH 05/19] fix ci pr changelog for my fork # --- changelog.d/{19930.misc => 16.misc} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{19930.misc => 16.misc} (100%) diff --git a/changelog.d/19930.misc b/changelog.d/16.misc similarity index 100% rename from changelog.d/19930.misc rename to changelog.d/16.misc From 222cc8dc26364839b72e406e6f6cc735843fabb1 Mon Sep 17 00:00:00 2001 From: Shane Jaroch Date: Wed, 15 Jul 2026 07:20:41 -0400 Subject: [PATCH 06/19] Keep derived signing key ids Matrix-valid --- synapse/config/key.py | 6 ++++-- tests/config/test_generate.py | 9 ++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/synapse/config/key.py b/synapse/config/key.py index 5ae004d7eac..a8753a2d226 100644 --- a/synapse/config/key.py +++ b/synapse/config/key.py @@ -102,14 +102,16 @@ logger = logging.getLogger(__name__) -_WEAK_SIGNING_KEY_VERSION = re.compile(r"^\d+$") +_WEAK_SIGNING_KEY_VERSION = re.compile(r"^[0-9]+$") def _derive_signing_key_version(signing_key: SigningKey) -> str: """Derive a stable, path-safe key version from the Ed25519 verify key.""" digest = hashlib.sha256(signing_key.verify_key.encode()).digest() - fingerprint = encode_base64(digest[:16], urlsafe=True) + # Matrix key ids do not allow "-" in the version, so normalize the urlsafe + # base64 alphabet into the spec-allowed character set. + fingerprint = encode_base64(digest[:16], urlsafe=True).replace("-", "_") return f"k_{fingerprint}" diff --git a/tests/config/test_generate.py b/tests/config/test_generate.py index 51a81484b19..90ab24006a2 100644 --- a/tests/config/test_generate.py +++ b/tests/config/test_generate.py @@ -43,7 +43,7 @@ def setUp(self) -> None: def tearDown(self) -> None: shutil.rmtree(self.dir) - def test_generate_config_generates_files(self) -> None: + def generate_config(self) -> None: with redirect_stdout(StringIO()): HomeServerConfig.load_or_generate_config( "", @@ -57,6 +57,9 @@ def test_generate_config_generates_files(self) -> None: ], ) + def test_generate_config_generates_files(self) -> None: + self.generate_config() + self.assertSetEqual( {"homeserver.yaml", "lemurs.win.log.config", "lemurs.win.signing.key"}, set(os.listdir(self.dir)), @@ -71,7 +74,7 @@ def test_generate_config_generates_files(self) -> None: keys = read_signing_keys(f) self.assertEqual(1, len(keys)) - self.assertRegex(keys[0].version, r"^k_[A-Za-z0-9_-]{22}$") + self.assertRegex(keys[0].version, r"^k_[A-Za-z0-9_]{22}$") def test_numeric_signing_key_version_warns(self) -> None: signing_key = generate_signing_key("1") @@ -89,7 +92,7 @@ def test_numeric_signing_key_version_warns(self) -> None: self.assertIn("uses a numeric key id", "\n".join(logs.output)) def test_deprecated_one_column_signing_key_fails(self) -> None: - self.test_generate_config_generates_files() + self.generate_config() signing_key_path = os.path.join(self.dir, "lemurs.win.signing.key") with open(signing_key_path) as f: From 73d740ed953636ba944d04571b6de3ccb732c30b Mon Sep 17 00:00:00 2001 From: Shane Jaroch Date: Wed, 15 Jul 2026 07:27:06 -0400 Subject: [PATCH 07/19] Rename config test helper to private method --- tests/config/test_generate.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/config/test_generate.py b/tests/config/test_generate.py index 90ab24006a2..899427d6d07 100644 --- a/tests/config/test_generate.py +++ b/tests/config/test_generate.py @@ -43,7 +43,7 @@ def setUp(self) -> None: def tearDown(self) -> None: shutil.rmtree(self.dir) - def generate_config(self) -> None: + def _generate_config(self) -> None: with redirect_stdout(StringIO()): HomeServerConfig.load_or_generate_config( "", @@ -58,7 +58,7 @@ def generate_config(self) -> None: ) def test_generate_config_generates_files(self) -> None: - self.generate_config() + self._generate_config() self.assertSetEqual( {"homeserver.yaml", "lemurs.win.log.config", "lemurs.win.signing.key"}, @@ -92,7 +92,7 @@ def test_numeric_signing_key_version_warns(self) -> None: self.assertIn("uses a numeric key id", "\n".join(logs.output)) def test_deprecated_one_column_signing_key_fails(self) -> None: - self.generate_config() + self._generate_config() signing_key_path = os.path.join(self.dir, "lemurs.win.signing.key") with open(signing_key_path) as f: From fdc332a94246a58c7dead30d3c63aa7e3d3d93a5 Mon Sep 17 00:00:00 2001 From: Shane Jaroch Date: Wed, 15 Jul 2026 07:51:18 -0400 Subject: [PATCH 08/19] Drop signing key id warning heuristics --- synapse/config/key.py | 20 +------------------- tests/config/test_generate.py | 20 ++------------------ 2 files changed, 3 insertions(+), 37 deletions(-) diff --git a/synapse/config/key.py b/synapse/config/key.py index a8753a2d226..d24ccf3c74b 100644 --- a/synapse/config/key.py +++ b/synapse/config/key.py @@ -23,7 +23,6 @@ import hashlib import logging import os -import re from typing import TYPE_CHECKING, Any, Iterator import attr @@ -102,8 +101,6 @@ logger = logging.getLogger(__name__) -_WEAK_SIGNING_KEY_VERSION = re.compile(r"^[0-9]+$") - def _derive_signing_key_version(signing_key: SigningKey) -> str: """Derive a stable, path-safe key version from the Ed25519 verify key.""" @@ -111,8 +108,7 @@ def _derive_signing_key_version(signing_key: SigningKey) -> str: digest = hashlib.sha256(signing_key.verify_key.encode()).digest() # Matrix key ids do not allow "-" in the version, so normalize the urlsafe # base64 alphabet into the spec-allowed character set. - fingerprint = encode_base64(digest[:16], urlsafe=True).replace("-", "_") - return f"k_{fingerprint}" + return encode_base64(digest[:16], urlsafe=True).replace("-", "_") def _generate_signing_key() -> SigningKey: @@ -121,18 +117,6 @@ def _generate_signing_key() -> SigningKey: return signing_key -def _warn_on_weak_signing_key_versions(signing_keys: list[SigningKey]) -> None: - for signing_key in signing_keys: - if _WEAK_SIGNING_KEY_VERSION.match(signing_key.version): - logger.warning( - "Signing key %s:%s uses a numeric key id. Numeric signing key ids " - "are easy to reuse accidentally during key rotation; use a unique " - "key id for each distinct signing key.", - signing_key.alg, - signing_key.version, - ) - - @attr.s(slots=True, auto_attribs=True) class TrustedKeyServer: # name of the server. @@ -165,8 +149,6 @@ def read_config( self.signing_key = self.read_signing_keys(signing_key_path, "signing_key") - _warn_on_weak_signing_key_versions(self.signing_key) - self.old_signing_keys = self.read_old_signing_keys( config.get("old_signing_keys") ) diff --git a/tests/config/test_generate.py b/tests/config/test_generate.py index 899427d6d07..e455fd13825 100644 --- a/tests/config/test_generate.py +++ b/tests/config/test_generate.py @@ -26,13 +26,12 @@ from contextlib import redirect_stdout from io import StringIO -from signedjson.key import generate_signing_key, read_signing_keys, write_signing_keys +from signedjson.key import read_signing_keys from synapse.config import ConfigError from synapse.config.homeserver import HomeServerConfig from tests import unittest -from tests.utils import default_config class ConfigGenerationTestCase(unittest.TestCase): @@ -74,22 +73,7 @@ def test_generate_config_generates_files(self) -> None: keys = read_signing_keys(f) self.assertEqual(1, len(keys)) - self.assertRegex(keys[0].version, r"^k_[A-Za-z0-9_]{22}$") - - def test_numeric_signing_key_version_warns(self) -> None: - signing_key = generate_signing_key("1") - signing_key_file = StringIO() - write_signing_keys(signing_key_file, (signing_key,)) - - config_dict = default_config(server_name="test") - config_dict["signing_key"] = signing_key_file.getvalue() - - config = HomeServerConfig() - with self.assertLogs("synapse.config.key", level="WARNING") as logs: - config.parse_config_dict(config_dict, "", "") - - self.assertEqual("1", config.key.signing_key[0].version) - self.assertIn("uses a numeric key id", "\n".join(logs.output)) + self.assertRegex(keys[0].version, r"^[A-Za-z0-9_]{22}$") def test_deprecated_one_column_signing_key_fails(self) -> None: self._generate_config() From 3772c1e533c8b6741198984d4dc6bd863d5d859a Mon Sep 17 00:00:00 2001 From: Shane Jaroch Date: Wed, 15 Jul 2026 11:23:21 -0400 Subject: [PATCH 09/19] add real pr number for main upstream --- changelog.d/16.misc | 2 +- changelog.d/19967.misc | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) mode change 100644 => 120000 changelog.d/16.misc create mode 100644 changelog.d/19967.misc diff --git a/changelog.d/16.misc b/changelog.d/16.misc deleted file mode 100644 index f37ad4a2046..00000000000 --- a/changelog.d/16.misc +++ /dev/null @@ -1 +0,0 @@ -Generate new server signing keys with URL-safe public-key-derived key ids and warn when existing signing keys use numeric ids. diff --git a/changelog.d/16.misc b/changelog.d/16.misc new file mode 120000 index 00000000000..5aadc29da9f --- /dev/null +++ b/changelog.d/16.misc @@ -0,0 +1 @@ +19967.misc \ No newline at end of file diff --git a/changelog.d/19967.misc b/changelog.d/19967.misc new file mode 100644 index 00000000000..f37ad4a2046 --- /dev/null +++ b/changelog.d/19967.misc @@ -0,0 +1 @@ +Generate new server signing keys with URL-safe public-key-derived key ids and warn when existing signing keys use numeric ids. From 5114b50c30be69565cf9c31a92e70e446de98988 Mon Sep 17 00:00:00 2001 From: Shane Jaroch Date: Wed, 15 Jul 2026 11:44:42 -0400 Subject: [PATCH 10/19] Inline signing key version derivation --- synapse/config/key.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/synapse/config/key.py b/synapse/config/key.py index d24ccf3c74b..1994205008c 100644 --- a/synapse/config/key.py +++ b/synapse/config/key.py @@ -102,18 +102,12 @@ logger = logging.getLogger(__name__) -def _derive_signing_key_version(signing_key: SigningKey) -> str: - """Derive a stable, path-safe key version from the Ed25519 verify key.""" - - digest = hashlib.sha256(signing_key.verify_key.encode()).digest() - # Matrix key ids do not allow "-" in the version, so normalize the urlsafe - # base64 alphabet into the spec-allowed character set. - return encode_base64(digest[:16], urlsafe=True).replace("-", "_") - - def _generate_signing_key() -> SigningKey: signing_key = generate_signing_key("pending_key_id") - signing_key.version = _derive_signing_key_version(signing_key) + digest = hashlib.sha256(signing_key.verify_key.encode()).digest() + # Matrix key ids do not allow "-" (so, normalize b64url alphabet). + # NOTE: "version" is the term used in the codebase, not suffix or ID. + signing_key.version = encode_base64(digest[:16], urlsafe=True).replace("-", "_") return signing_key From c19cdedd49cc9942e0d8e42470aa56034af85694 Mon Sep 17 00:00:00 2001 From: Shane Jaroch Date: Wed, 15 Jul 2026 12:17:33 -0400 Subject: [PATCH 11/19] log warning on numeric signing `key_id` values, i.e., `ed25519:1` --- synapse/config/key.py | 47 +++++++++++++++++++++++--- tests/config/test_generate.py | 62 ++++++++++++++++++++++++++++++++++- 2 files changed, 103 insertions(+), 6 deletions(-) diff --git a/synapse/config/key.py b/synapse/config/key.py index 1994205008c..67b53fc407a 100644 --- a/synapse/config/key.py +++ b/synapse/config/key.py @@ -23,6 +23,7 @@ import hashlib import logging import os +import re from typing import TYPE_CHECKING, Any, Iterator import attr @@ -100,14 +101,50 @@ """ logger = logging.getLogger(__name__) +_SIGNING_KEY_VERSION_RE = re.compile(r"^[A-Za-z0-9_]+$") -def _generate_signing_key() -> SigningKey: - signing_key = generate_signing_key("pending_key_id") +def _derive_signing_key_version(signing_key: SigningKey) -> str: digest = hashlib.sha256(signing_key.verify_key.encode()).digest() # Matrix key ids do not allow "-" (so, normalize b64url alphabet). # NOTE: "version" is the term used in the codebase, not suffix or ID. - signing_key.version = encode_base64(digest[:16], urlsafe=True).replace("-", "_") + return encode_base64(digest[:16], urlsafe=True).replace("-", "_") + + +def _load_signing_keys(lines: list[str]) -> list[SigningKey]: + loaded_signing_keys = read_signing_keys(lines) + for signing_key in loaded_signing_keys: + expected_version = _derive_signing_key_version(signing_key) + if signing_key.version == expected_version: + continue + if signing_key.version.isdigit(): + logger.warning( + "Signing key %s:%s uses a numeric key id. Numeric signing key ids " + "are deprecated for compatibility reasons; preserving the existing " + "key id unchanged.", + signing_key.alg, + signing_key.version, + ) + elif not _SIGNING_KEY_VERSION_RE.fullmatch(signing_key.version): + logger.error( + "Signing key %s:%s uses a non-spec-compliant key id. " + "Preserving the existing key id unchanged for compatibility.", + signing_key.alg, + signing_key.version, + ) + else: + logger.info( + "Signing key %s:%s is not content-derived; expected %s.", + signing_key.alg, + signing_key.version, + expected_version, + ) + return loaded_signing_keys + + +def _generate_signing_key() -> SigningKey: + signing_key = generate_signing_key("pending_key_id") + signing_key.version = _derive_signing_key_version(signing_key) return signing_key @@ -132,7 +169,7 @@ def read_config( ) -> None: # the signing key can be specified inline or in a separate file if "signing_key" in config: - self.signing_key = read_signing_keys([config["signing_key"]]) + self.signing_key = _load_signing_keys([config["signing_key"]]) else: assert config_dir_path is not None signing_key_path = config.get("signing_key_path") @@ -270,7 +307,7 @@ def read_signing_keys(self, signing_key_path: str, name: str) -> list[SigningKey signing_keys = self.read_file(signing_key_path, name) try: - loaded_signing_keys = read_signing_keys( + loaded_signing_keys = _load_signing_keys( [ signing_key_line for signing_key_line in signing_keys.splitlines(keepends=False) diff --git a/tests/config/test_generate.py b/tests/config/test_generate.py index e455fd13825..e62efc48b41 100644 --- a/tests/config/test_generate.py +++ b/tests/config/test_generate.py @@ -26,12 +26,13 @@ from contextlib import redirect_stdout from io import StringIO -from signedjson.key import read_signing_keys +from signedjson.key import generate_signing_key, read_signing_keys, write_signing_keys from synapse.config import ConfigError from synapse.config.homeserver import HomeServerConfig from tests import unittest +from tests.utils import default_config class ConfigGenerationTestCase(unittest.TestCase): @@ -88,6 +89,65 @@ def test_deprecated_one_column_signing_key_fails(self) -> None: with self.assertRaisesRegex(ConfigError, "deprecated one-column format"): HomeServerConfig.load_or_generate_config("", ["-c", self.file]) + def test_numeric_signing_key_version_warns(self) -> None: + self._generate_config() + + signing_key = generate_signing_key("1") + signing_key_path = os.path.join(self.dir, "lemurs.win.signing.key") + with open(signing_key_path, "w") as f: + write_signing_keys(f, (signing_key,)) + + with self.assertLogs("synapse.config.key", level="WARNING") as logs: + config = HomeServerConfig.load_or_generate_config("", ["-c", self.file]) + + self.assertEqual("1", config.key.signing_key[0].version) + self.assertIn("uses a numeric key id", "\n".join(logs.output)) + + def test_inline_numeric_signing_key_version_warns(self) -> None: + signing_key = generate_signing_key("1") + signing_key_file = StringIO() + write_signing_keys(signing_key_file, (signing_key,)) + + config_dict = default_config(server_name="test") + config_dict["signing_key"] = signing_key_file.getvalue() + + config = HomeServerConfig() + with self.assertLogs("synapse.config.key", level="WARNING") as logs: + config.parse_config_dict(config_dict, "", "") + + self.assertEqual("1", config.key.signing_key[0].version) + self.assertIn("uses a numeric key id", "\n".join(logs.output)) + + def test_inline_invalid_signing_key_version_errors(self) -> None: + signing_key = generate_signing_key("foo-bar") + signing_key_file = StringIO() + write_signing_keys(signing_key_file, (signing_key,)) + + config_dict = default_config(server_name="test") + config_dict["signing_key"] = signing_key_file.getvalue() + + config = HomeServerConfig() + with self.assertLogs("synapse.config.key", level="ERROR") as logs: + config.parse_config_dict(config_dict, "", "") + + self.assertEqual("foo-bar", config.key.signing_key[0].version) + self.assertIn("non-spec-compliant key id", "\n".join(logs.output)) + + def test_inline_non_content_derived_signing_key_version_infos(self) -> None: + signing_key = generate_signing_key("manual_key_id") + signing_key_file = StringIO() + write_signing_keys(signing_key_file, (signing_key,)) + + config_dict = default_config(server_name="test") + config_dict["signing_key"] = signing_key_file.getvalue() + + config = HomeServerConfig() + with self.assertLogs("synapse.config.key", level="INFO") as logs: + config.parse_config_dict(config_dict, "", "") + + self.assertEqual("manual_key_id", config.key.signing_key[0].version) + self.assertIn("not content-derived", "\n".join(logs.output)) + def assert_log_filename_is(self, log_config_file: str, expected: str) -> None: with open(log_config_file) as f: config = f.read() From 3763f3eb7716e744c29cba74fe98600ded5aed28 Mon Sep 17 00:00:00 2001 From: Shane Jaroch Date: Wed, 15 Jul 2026 12:24:37 -0400 Subject: [PATCH 12/19] appease mypy in signing key test --- tests/config/test_generate.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/config/test_generate.py b/tests/config/test_generate.py index e62efc48b41..143d98bb459 100644 --- a/tests/config/test_generate.py +++ b/tests/config/test_generate.py @@ -100,6 +100,7 @@ def test_numeric_signing_key_version_warns(self) -> None: with self.assertLogs("synapse.config.key", level="WARNING") as logs: config = HomeServerConfig.load_or_generate_config("", ["-c", self.file]) + assert config is not None self.assertEqual("1", config.key.signing_key[0].version) self.assertIn("uses a numeric key id", "\n".join(logs.output)) From 47dad1815ddd36bcfba88900e8792b09d752a121 Mon Sep 17 00:00:00 2001 From: Shane Jaroch Date: Wed, 15 Jul 2026 13:05:50 -0400 Subject: [PATCH 13/19] restore auto-migration of one-column signing key files The deprecated one-column format was previously auto-migrated in place; a recent commit replaced that with a hard ConfigError, which would break startup for any deployment still on that old format. Restore the auto-migration, now writing the migrated key with the new content-derived key id instead of a random suffix. Co-Authored-By: Claude Sonnet 5 --- synapse/config/key.py | 15 ++++++++++----- tests/config/test_generate.py | 14 ++++++++++---- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/synapse/config/key.py b/synapse/config/key.py index 67b53fc407a..e2f51da7848 100644 --- a/synapse/config/key.py +++ b/synapse/config/key.py @@ -29,8 +29,10 @@ import attr import jsonschema from signedjson.key import ( + NACL_ED25519, SigningKey, VerifyKey, + decode_signing_key_base64, decode_verify_key_bytes, generate_signing_key, is_signing_algorithm_supported, @@ -362,12 +364,15 @@ def generate_files(self, config: dict[str, Any], config_dir_path: str) -> None: else: signing_keys = self.read_file(signing_key_path, "signing_key") if len(signing_keys.split("\n")[0].split()) == 1: - raise ConfigError( - "Signing key file %s uses deprecated one-column format. " - "Rotate the signing key explicitly or rewrite the file to " - "the three-column 'algorithm version key' format." - % (signing_key_path,) + # handle keys in the old format. + key = decode_signing_key_base64( + NACL_ED25519, "pending_key_id", signing_keys.split("\n")[0] ) + key.version = _derive_signing_key_version(key) + with open( + signing_key_path, "w", opener=lambda p, f: os.open(p, f, mode=0o640) + ) as signing_key_file: + write_signing_keys(signing_key_file, (key,)) def _perspectives_to_key_servers(config: JsonDict) -> Iterator[JsonDict]: diff --git a/tests/config/test_generate.py b/tests/config/test_generate.py index 143d98bb459..ecb19ea79d6 100644 --- a/tests/config/test_generate.py +++ b/tests/config/test_generate.py @@ -28,7 +28,6 @@ from signedjson.key import generate_signing_key, read_signing_keys, write_signing_keys -from synapse.config import ConfigError from synapse.config.homeserver import HomeServerConfig from tests import unittest @@ -76,7 +75,7 @@ def test_generate_config_generates_files(self) -> None: self.assertEqual(1, len(keys)) self.assertRegex(keys[0].version, r"^[A-Za-z0-9_]{22}$") - def test_deprecated_one_column_signing_key_fails(self) -> None: + def test_deprecated_one_column_signing_key_is_migrated(self) -> None: self._generate_config() signing_key_path = os.path.join(self.dir, "lemurs.win.signing.key") @@ -86,8 +85,15 @@ def test_deprecated_one_column_signing_key_fails(self) -> None: with open(signing_key_path, "w") as f: f.write(signing_key + "\n") - with self.assertRaisesRegex(ConfigError, "deprecated one-column format"): - HomeServerConfig.load_or_generate_config("", ["-c", self.file]) + config = HomeServerConfig.load_or_generate_config("", ["-c", self.file]) + assert config is not None + + with open(signing_key_path) as f: + keys = read_signing_keys(f) + + self.assertEqual(1, len(keys)) + self.assertRegex(keys[0].version, r"^[A-Za-z0-9_]{22}$") + self.assertEqual(keys[0].version, config.key.signing_key[0].version) def test_numeric_signing_key_version_warns(self) -> None: self._generate_config() From 76d64111ac28a9d675a0eb6711b83261516d5f46 Mon Sep 17 00:00:00 2001 From: Shane Jaroch Date: Wed, 15 Jul 2026 13:30:46 -0400 Subject: [PATCH 14/19] chore: remove changelog entry for self-PR/fork --- changelog.d/16.misc | 1 - 1 file changed, 1 deletion(-) delete mode 120000 changelog.d/16.misc diff --git a/changelog.d/16.misc b/changelog.d/16.misc deleted file mode 120000 index 5aadc29da9f..00000000000 --- a/changelog.d/16.misc +++ /dev/null @@ -1 +0,0 @@ -19967.misc \ No newline at end of file From 46d3f9bdf6c4ba56182ba4d03bf7ebe896039ba1 Mon Sep 17 00:00:00 2001 From: Shane Jaroch Date: Wed, 15 Jul 2026 15:29:11 -0400 Subject: [PATCH 15/19] Extract signing key derivation into synapse.util.signing_key generate_signing_key.py was importing a private, underscore-prefixed helper from synapse.config.key, which drags in jsonschema and the rest of the config module just to generate a key. Move the content-derived key id logic into a small standalone module with no heavy dependencies, and have both the config loader and the CLI script import from there. Co-Authored-By: Claude Sonnet 5 --- synapse/_scripts/generate_signing_key.py | 4 ++-- synapse/config/key.py | 28 ++++++++-------------- synapse/util/signing_key.py | 30 ++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 20 deletions(-) create mode 100644 synapse/util/signing_key.py diff --git a/synapse/_scripts/generate_signing_key.py b/synapse/_scripts/generate_signing_key.py index ed923fbd9d2..92a51214893 100755 --- a/synapse/_scripts/generate_signing_key.py +++ b/synapse/_scripts/generate_signing_key.py @@ -25,7 +25,7 @@ from signedjson.key import write_signing_keys -from synapse.config.key import _generate_signing_key +from synapse.util.signing_key import generate_content_derived_signing_key def main() -> None: @@ -40,7 +40,7 @@ def main() -> None: ) args = parser.parse_args() - key = (_generate_signing_key(),) + key = (generate_content_derived_signing_key(),) if args.output_file == "-": write_signing_keys(sys.stdout, key) else: diff --git a/synapse/config/key.py b/synapse/config/key.py index e2f51da7848..49fcfc8acba 100644 --- a/synapse/config/key.py +++ b/synapse/config/key.py @@ -34,14 +34,17 @@ VerifyKey, decode_signing_key_base64, decode_verify_key_bytes, - generate_signing_key, is_signing_algorithm_supported, read_signing_keys, write_signing_keys, ) -from unpaddedbase64 import decode_base64, encode_base64 +from unpaddedbase64 import decode_base64 from synapse.types import JsonDict +from synapse.util.signing_key import ( + derive_signing_key_version, + generate_content_derived_signing_key, +) from synapse.util.stringutils import random_string_with_symbols from ._base import Config, ConfigError, read_file @@ -106,17 +109,10 @@ _SIGNING_KEY_VERSION_RE = re.compile(r"^[A-Za-z0-9_]+$") -def _derive_signing_key_version(signing_key: SigningKey) -> str: - digest = hashlib.sha256(signing_key.verify_key.encode()).digest() - # Matrix key ids do not allow "-" (so, normalize b64url alphabet). - # NOTE: "version" is the term used in the codebase, not suffix or ID. - return encode_base64(digest[:16], urlsafe=True).replace("-", "_") - - def _load_signing_keys(lines: list[str]) -> list[SigningKey]: loaded_signing_keys = read_signing_keys(lines) for signing_key in loaded_signing_keys: - expected_version = _derive_signing_key_version(signing_key) + expected_version = derive_signing_key_version(signing_key) if signing_key.version == expected_version: continue if signing_key.version.isdigit(): @@ -144,12 +140,6 @@ def _load_signing_keys(lines: list[str]) -> list[SigningKey]: return loaded_signing_keys -def _generate_signing_key() -> SigningKey: - signing_key = generate_signing_key("pending_key_id") - signing_key.version = _derive_signing_key_version(signing_key) - return signing_key - - @attr.s(slots=True, auto_attribs=True) class TrustedKeyServer: # name of the server. @@ -360,7 +350,9 @@ def generate_files(self, config: dict[str, Any], config_dir_path: str) -> None: with open( signing_key_path, "w", opener=lambda p, f: os.open(p, f, mode=0o640) ) as signing_key_file: - write_signing_keys(signing_key_file, (_generate_signing_key(),)) + write_signing_keys( + signing_key_file, (generate_content_derived_signing_key(),) + ) else: signing_keys = self.read_file(signing_key_path, "signing_key") if len(signing_keys.split("\n")[0].split()) == 1: @@ -368,7 +360,7 @@ def generate_files(self, config: dict[str, Any], config_dir_path: str) -> None: key = decode_signing_key_base64( NACL_ED25519, "pending_key_id", signing_keys.split("\n")[0] ) - key.version = _derive_signing_key_version(key) + key.version = derive_signing_key_version(key) with open( signing_key_path, "w", opener=lambda p, f: os.open(p, f, mode=0o640) ) as signing_key_file: diff --git a/synapse/util/signing_key.py b/synapse/util/signing_key.py new file mode 100644 index 00000000000..9c409de0377 --- /dev/null +++ b/synapse/util/signing_key.py @@ -0,0 +1,30 @@ +# +# This file is licensed under the Affero General Public License (AGPL) version 3. +# +# Copyright (C) 2026 Element Creations 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: +# . +# +import hashlib + +from signedjson.key import SigningKey, generate_signing_key +from unpaddedbase64 import encode_base64 + + +def derive_signing_key_version(signing_key: SigningKey) -> str: + digest = hashlib.sha256(signing_key.verify_key.encode()).digest() + # Matrix key ids do not allow "-" (so, normalize b64url alphabet). + # NOTE: "version" is the term used in the codebase, not suffix or ID. + return encode_base64(digest[:16], urlsafe=True).replace("-", "_") + + +def generate_content_derived_signing_key() -> SigningKey: + signing_key = generate_signing_key("pending_key_id") + signing_key.version = derive_signing_key_version(signing_key) + return signing_key From 0f93d23c3cf63743213ef80b6187e70435780575 Mon Sep 17 00:00:00 2001 From: Shane Jaroch Date: Wed, 15 Jul 2026 15:51:51 -0400 Subject: [PATCH 16/19] clearer var name for signing key placeholder ID; add TODOs --- synapse/config/key.py | 7 ++++++- synapse/util/signing_key.py | 4 +++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/synapse/config/key.py b/synapse/config/key.py index 49fcfc8acba..2109f791873 100644 --- a/synapse/config/key.py +++ b/synapse/config/key.py @@ -42,6 +42,7 @@ from synapse.types import JsonDict from synapse.util.signing_key import ( + PLACEHOLDER_SIGNING_KEY_ID, derive_signing_key_version, generate_content_derived_signing_key, ) @@ -131,6 +132,8 @@ def _load_signing_keys(lines: list[str]) -> list[SigningKey]: signing_key.version, ) else: + # TODO: phase out/reject newly introduced non-content-derived signing + # key ids loaded via `signing_key` or `signing_key_path`. logger.info( "Signing key %s:%s is not content-derived; expected %s.", signing_key.alg, @@ -358,7 +361,9 @@ def generate_files(self, config: dict[str, Any], config_dir_path: str) -> None: if len(signing_keys.split("\n")[0].split()) == 1: # handle keys in the old format. key = decode_signing_key_base64( - NACL_ED25519, "pending_key_id", signing_keys.split("\n")[0] + NACL_ED25519, + PLACEHOLDER_SIGNING_KEY_ID, + signing_keys.split("\n")[0], ) key.version = derive_signing_key_version(key) with open( diff --git a/synapse/util/signing_key.py b/synapse/util/signing_key.py index 9c409de0377..061877b2ae8 100644 --- a/synapse/util/signing_key.py +++ b/synapse/util/signing_key.py @@ -16,6 +16,8 @@ from signedjson.key import SigningKey, generate_signing_key from unpaddedbase64 import encode_base64 +PLACEHOLDER_SIGNING_KEY_ID = "PLACEHOLDER_SIGNING_KEY_ID" + def derive_signing_key_version(signing_key: SigningKey) -> str: digest = hashlib.sha256(signing_key.verify_key.encode()).digest() @@ -25,6 +27,6 @@ def derive_signing_key_version(signing_key: SigningKey) -> str: def generate_content_derived_signing_key() -> SigningKey: - signing_key = generate_signing_key("pending_key_id") + signing_key = generate_signing_key(PLACEHOLDER_SIGNING_KEY_ID) signing_key.version = derive_signing_key_version(signing_key) return signing_key From 616fc68e128fc0ddce41ea21e1b6d9ff6111e8ce Mon Sep 17 00:00:00 2001 From: Shane Jaroch Date: Wed, 15 Jul 2026 16:33:08 -0400 Subject: [PATCH 17/19] rename signing key loader wrapper (to develop's) --- synapse/_scripts/generate_signing_key.py | 4 ++-- synapse/config/key.py | 16 +++++++--------- synapse/util/signing_key.py | 6 +++--- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/synapse/_scripts/generate_signing_key.py b/synapse/_scripts/generate_signing_key.py index 92a51214893..9d1f9d91221 100755 --- a/synapse/_scripts/generate_signing_key.py +++ b/synapse/_scripts/generate_signing_key.py @@ -25,7 +25,7 @@ from signedjson.key import write_signing_keys -from synapse.util.signing_key import generate_content_derived_signing_key +from synapse.util.signing_key import generate_signing_key def main() -> None: @@ -40,7 +40,7 @@ def main() -> None: ) args = parser.parse_args() - key = (generate_content_derived_signing_key(),) + key = (generate_signing_key(),) if args.output_file == "-": write_signing_keys(sys.stdout, key) else: diff --git a/synapse/config/key.py b/synapse/config/key.py index 2109f791873..6b5401aeabb 100644 --- a/synapse/config/key.py +++ b/synapse/config/key.py @@ -35,7 +35,7 @@ decode_signing_key_base64, decode_verify_key_bytes, is_signing_algorithm_supported, - read_signing_keys, + read_signing_keys as _read_signing_keys, write_signing_keys, ) from unpaddedbase64 import decode_base64 @@ -44,7 +44,7 @@ from synapse.util.signing_key import ( PLACEHOLDER_SIGNING_KEY_ID, derive_signing_key_version, - generate_content_derived_signing_key, + generate_signing_key, ) from synapse.util.stringutils import random_string_with_symbols @@ -110,8 +110,8 @@ _SIGNING_KEY_VERSION_RE = re.compile(r"^[A-Za-z0-9_]+$") -def _load_signing_keys(lines: list[str]) -> list[SigningKey]: - loaded_signing_keys = read_signing_keys(lines) +def read_signing_keys(lines: list[str]) -> list[SigningKey]: + loaded_signing_keys = _read_signing_keys(lines) for signing_key in loaded_signing_keys: expected_version = derive_signing_key_version(signing_key) if signing_key.version == expected_version: @@ -164,7 +164,7 @@ def read_config( ) -> None: # the signing key can be specified inline or in a separate file if "signing_key" in config: - self.signing_key = _load_signing_keys([config["signing_key"]]) + self.signing_key = read_signing_keys([config["signing_key"]]) else: assert config_dir_path is not None signing_key_path = config.get("signing_key_path") @@ -302,7 +302,7 @@ def read_signing_keys(self, signing_key_path: str, name: str) -> list[SigningKey signing_keys = self.read_file(signing_key_path, name) try: - loaded_signing_keys = _load_signing_keys( + loaded_signing_keys = read_signing_keys( [ signing_key_line for signing_key_line in signing_keys.splitlines(keepends=False) @@ -353,9 +353,7 @@ def generate_files(self, config: dict[str, Any], config_dir_path: str) -> None: with open( signing_key_path, "w", opener=lambda p, f: os.open(p, f, mode=0o640) ) as signing_key_file: - write_signing_keys( - signing_key_file, (generate_content_derived_signing_key(),) - ) + write_signing_keys(signing_key_file, (generate_signing_key(),)) else: signing_keys = self.read_file(signing_key_path, "signing_key") if len(signing_keys.split("\n")[0].split()) == 1: diff --git a/synapse/util/signing_key.py b/synapse/util/signing_key.py index 061877b2ae8..f11afb94abb 100644 --- a/synapse/util/signing_key.py +++ b/synapse/util/signing_key.py @@ -13,7 +13,7 @@ # import hashlib -from signedjson.key import SigningKey, generate_signing_key +from signedjson.key import SigningKey, generate_signing_key as _generate_signing_key from unpaddedbase64 import encode_base64 PLACEHOLDER_SIGNING_KEY_ID = "PLACEHOLDER_SIGNING_KEY_ID" @@ -26,7 +26,7 @@ def derive_signing_key_version(signing_key: SigningKey) -> str: return encode_base64(digest[:16], urlsafe=True).replace("-", "_") -def generate_content_derived_signing_key() -> SigningKey: - signing_key = generate_signing_key(PLACEHOLDER_SIGNING_KEY_ID) +def generate_signing_key() -> SigningKey: + signing_key = _generate_signing_key(PLACEHOLDER_SIGNING_KEY_ID) signing_key.version = derive_signing_key_version(signing_key) return signing_key From 061c60c312e41aff5d1dea2f37a979ba7c472172 Mon Sep 17 00:00:00 2001 From: Shane Jaroch Date: Wed, 15 Jul 2026 16:45:41 -0400 Subject: [PATCH 18/19] fixup! rename signing key loader wrapper (to develop's) --- synapse/config/key.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/synapse/config/key.py b/synapse/config/key.py index 6b5401aeabb..ab9884e5e94 100644 --- a/synapse/config/key.py +++ b/synapse/config/key.py @@ -35,7 +35,7 @@ decode_signing_key_base64, decode_verify_key_bytes, is_signing_algorithm_supported, - read_signing_keys as _read_signing_keys, + read_signing_keys, write_signing_keys, ) from unpaddedbase64 import decode_base64 @@ -111,7 +111,7 @@ def read_signing_keys(lines: list[str]) -> list[SigningKey]: - loaded_signing_keys = _read_signing_keys(lines) + loaded_signing_keys = read_signing_keys(lines) for signing_key in loaded_signing_keys: expected_version = derive_signing_key_version(signing_key) if signing_key.version == expected_version: From 4544b257cb5443f7000f7304bc3331a5bb3891ab Mon Sep 17 00:00:00 2001 From: Shane Jaroch Date: Wed, 15 Jul 2026 16:48:39 -0400 Subject: [PATCH 19/19] Rename signing key loader wrapper --- synapse/config/key.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/synapse/config/key.py b/synapse/config/key.py index ab9884e5e94..c58a123e8a2 100644 --- a/synapse/config/key.py +++ b/synapse/config/key.py @@ -110,7 +110,7 @@ _SIGNING_KEY_VERSION_RE = re.compile(r"^[A-Za-z0-9_]+$") -def read_signing_keys(lines: list[str]) -> list[SigningKey]: +def load_signing_keys(lines: list[str]) -> list[SigningKey]: loaded_signing_keys = read_signing_keys(lines) for signing_key in loaded_signing_keys: expected_version = derive_signing_key_version(signing_key) @@ -164,7 +164,7 @@ def read_config( ) -> None: # the signing key can be specified inline or in a separate file if "signing_key" in config: - self.signing_key = read_signing_keys([config["signing_key"]]) + self.signing_key = load_signing_keys([config["signing_key"]]) else: assert config_dir_path is not None signing_key_path = config.get("signing_key_path") @@ -302,7 +302,7 @@ def read_signing_keys(self, signing_key_path: str, name: str) -> list[SigningKey signing_keys = self.read_file(signing_key_path, name) try: - loaded_signing_keys = read_signing_keys( + loaded_signing_keys = load_signing_keys( [ signing_key_line for signing_key_line in signing_keys.splitlines(keepends=False)