diff --git a/changelog.d/6546.feature b/changelog.d/6546.feature
new file mode 100644
index 0000000000..954aacb0d0
--- /dev/null
+++ b/changelog.d/6546.feature
@@ -0,0 +1 @@
+Add an export_signing_key script to extract the public part of signing keys when rotating them.
diff --git a/docs/code_style.md b/docs/code_style.md
index f983f72d6c..71aecd41f7 100644
--- a/docs/code_style.md
+++ b/docs/code_style.md
@@ -137,6 +137,7 @@ Some guidelines follow:
correctly handles the top-level option being set to `None` (as it
will be if no sub-options are enabled).
- Lines should be wrapped at 80 characters.
+- Use two-space indents.
Example:
@@ -155,13 +156,13 @@ Example:
# Settings for the frobber
#
frobber:
- # frobbing speed. Defaults to 1.
- #
- #speed: 10
+ # frobbing speed. Defaults to 1.
+ #
+ #speed: 10
- # frobbing distance. Defaults to 1000.
- #
- #distance: 100
+ # frobbing distance. Defaults to 1000.
+ #
+ #distance: 100
Note that the sample configuration is generated from the synapse code
and is maintained by a script, `scripts-dev/generate_sample_config`.
diff --git a/docs/sample_config.yaml b/docs/sample_config.yaml
index 1787248f53..e3b05423b8 100644
--- a/docs/sample_config.yaml
+++ b/docs/sample_config.yaml
@@ -1122,14 +1122,19 @@ metrics_flags:
signing_key_path: "CONFDIR/SERVERNAME.signing.key"
# The keys that the server used to sign messages with but won't use
-# to sign new messages. E.g. it has lost its private key
-#
-#old_signing_keys:
-# "ed25519:auto":
-# # Base64 encoded public key
-# key: "The public part of your old signing key."
-# # Millisecond POSIX timestamp when the key expired.
-# expired_ts: 123456789123
+# to sign new messages.
+#
+old_signing_keys:
+ # For each key, `key` should be the base64-encoded public key, and
+ # `expired_ts`should be the time (in milliseconds since the unix epoch) that
+ # it was last used.
+ #
+ # It is possible to build an entry from an old signing.key file using the
+ # `export_signing_key` script which is provided with synapse.
+ #
+ # For example:
+ #
+ #"ed25519:id": { key: "base64string", expired_ts: 123456789123 }
# How long key response published by this server is valid for.
# Used to set the valid_until_ts in /key/v2 APIs.
diff --git a/scripts/export_signing_key b/scripts/export_signing_key
new file mode 100755
index 0000000000..8aec9d802b
--- /dev/null
+++ b/scripts/export_signing_key
@@ -0,0 +1,94 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Copyright 2019 The Matrix.org Foundation C.I.C.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+import argparse
+import sys
+import time
+from typing import Optional
+
+import nacl.signing
+from signedjson.key import encode_verify_key_base64, get_verify_key, read_signing_keys
+
+
+def exit(status: int = 0, message: Optional[str] = None):
+ if message:
+ print(message, file=sys.stderr)
+ sys.exit(status)
+
+
+def format_plain(public_key: nacl.signing.VerifyKey):
+ print(
+ "%s:%s %s"
+ % (public_key.alg, public_key.version, encode_verify_key_base64(public_key),)
+ )
+
+
+def format_for_config(public_key: nacl.signing.VerifyKey, expiry_ts: int):
+ print(
+ ' "%s:%s": { key: "%s", expired_ts: %i }'
+ % (
+ public_key.alg,
+ public_key.version,
+ encode_verify_key_base64(public_key),
+ expiry_ts,
+ )
+ )
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser()
+
+ parser.add_argument(
+ "key_file", nargs="+", type=argparse.FileType("r"), help="The key file to read",
+ )
+
+ parser.add_argument(
+ "-x",
+ action="store_true",
+ dest="for_config",
+ help="format the output for inclusion in the old_signing_keys config setting",
+ )
+
+ parser.add_argument(
+ "--expiry-ts",
+ type=int,
+ default=int(time.time() * 1000) + 6*3600000,
+ help=(
+ "The expiry time to use for -x, in milliseconds since 1970. The default "
+ "is (now+6h)."
+ ),
+ )
+
+ args = parser.parse_args()
+
+ formatter = (
+ (lambda k: format_for_config(k, args.expiry_ts))
+ if args.for_config
+ else format_plain
+ )
+
+ keys = []
+ for file in args.key_file:
+ try:
+ res = read_signing_keys(file)
+ except Exception as e:
+ exit(
+ status=1,
+ message="Error reading key from file %s: %s %s"
+ % (file.name, type(e), e),
+ )
+ res = []
+ for key in res:
+ formatter(get_verify_key(key))
diff --git a/synapse/config/key.py b/synapse/config/key.py
index 52ff1b2621..066e7838c3 100644
--- a/synapse/config/key.py
+++ b/synapse/config/key.py
@@ -108,7 +108,7 @@ class KeyConfig(Config):
self.signing_key = self.read_signing_keys(signing_key_path, "signing_key")
self.old_signing_keys = self.read_old_signing_keys(
- config.get("old_signing_keys", {})
+ config.get("old_signing_keys")
)
self.key_refresh_interval = self.parse_duration(
config.get("key_refresh_interval", "1d")
@@ -199,14 +199,19 @@ class KeyConfig(Config):
signing_key_path: "%(base_key_name)s.signing.key"
# The keys that the server used to sign messages with but won't use
- # to sign new messages. E.g. it has lost its private key
+ # to sign new messages.
#
- #old_signing_keys:
- # "ed25519:auto":
- # # Base64 encoded public key
- # key: "The public part of your old signing key."
- # # Millisecond POSIX timestamp when the key expired.
- # expired_ts: 123456789123
+ old_signing_keys:
+ # For each key, `key` should be the base64-encoded public key, and
+ # `expired_ts`should be the time (in milliseconds since the unix epoch) that
+ # it was last used.
+ #
+ # It is possible to build an entry from an old signing.key file using the
+ # `export_signing_key` script which is provided with synapse.
+ #
+ # For example:
+ #
+ #"ed25519:id": { key: "base64string", expired_ts: 123456789123 }
# How long key response published by this server is valid for.
# Used to set the valid_until_ts in /key/v2 APIs.
@@ -290,6 +295,8 @@ class KeyConfig(Config):
raise ConfigError("Error reading %s: %s" % (name, str(e)))
def read_old_signing_keys(self, old_signing_keys):
+ if old_signing_keys is None:
+ return {}
keys = {}
for key_id, key_data in old_signing_keys.items():
if is_signing_algorithm_supported(key_id):
|