key.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2015, 2016 OpenMarket Ltd
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. import hashlib
  16. import logging
  17. import os
  18. from signedjson.key import (
  19. NACL_ED25519,
  20. decode_signing_key_base64,
  21. decode_verify_key_bytes,
  22. generate_signing_key,
  23. is_signing_algorithm_supported,
  24. read_signing_keys,
  25. write_signing_keys,
  26. )
  27. from unpaddedbase64 import decode_base64
  28. from synapse.util.stringutils import random_string, random_string_with_symbols
  29. from ._base import Config, ConfigError
  30. logger = logging.getLogger(__name__)
  31. class KeyConfig(Config):
  32. def read_config(self, config):
  33. self.signing_key = self.read_signing_key(config["signing_key_path"])
  34. self.old_signing_keys = self.read_old_signing_keys(
  35. config["old_signing_keys"]
  36. )
  37. self.key_refresh_interval = self.parse_duration(
  38. config["key_refresh_interval"]
  39. )
  40. self.perspectives = self.read_perspectives(
  41. config["perspectives"]
  42. )
  43. self.macaroon_secret_key = config.get(
  44. "macaroon_secret_key", self.registration_shared_secret
  45. )
  46. if not self.macaroon_secret_key:
  47. # Unfortunately, there are people out there that don't have this
  48. # set. Lets just be "nice" and derive one from their secret key.
  49. logger.warn("Config is missing missing macaroon_secret_key")
  50. seed = self.signing_key[0].seed
  51. self.macaroon_secret_key = hashlib.sha256(seed)
  52. self.expire_access_token = config.get("expire_access_token", False)
  53. # a secret which is used to calculate HMACs for form values, to stop
  54. # falsification of values
  55. self.form_secret = config.get("form_secret", None)
  56. def default_config(self, config_dir_path, server_name, is_generating_file=False,
  57. **kwargs):
  58. base_key_name = os.path.join(config_dir_path, server_name)
  59. if is_generating_file:
  60. macaroon_secret_key = random_string_with_symbols(50)
  61. form_secret = '"%s"' % random_string_with_symbols(50)
  62. else:
  63. macaroon_secret_key = None
  64. form_secret = 'null'
  65. return """\
  66. macaroon_secret_key: "%(macaroon_secret_key)s"
  67. # Used to enable access token expiration.
  68. expire_access_token: False
  69. # a secret which is used to calculate HMACs for form values, to stop
  70. # falsification of values
  71. form_secret: %(form_secret)s
  72. ## Signing Keys ##
  73. # Path to the signing key to sign messages with
  74. signing_key_path: "%(base_key_name)s.signing.key"
  75. # The keys that the server used to sign messages with but won't use
  76. # to sign new messages. E.g. it has lost its private key
  77. old_signing_keys: {}
  78. # "ed25519:auto":
  79. # # Base64 encoded public key
  80. # key: "The public part of your old signing key."
  81. # # Millisecond POSIX timestamp when the key expired.
  82. # expired_ts: 123456789123
  83. # How long key response published by this server is valid for.
  84. # Used to set the valid_until_ts in /key/v2 APIs.
  85. # Determines how quickly servers will query to check which keys
  86. # are still valid.
  87. key_refresh_interval: "1d" # 1 Day.
  88. # The trusted servers to download signing keys from.
  89. perspectives:
  90. servers:
  91. "matrix.org":
  92. verify_keys:
  93. "ed25519:auto":
  94. key: "Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw"
  95. """ % locals()
  96. def read_perspectives(self, perspectives_config):
  97. servers = {}
  98. for server_name, server_config in perspectives_config["servers"].items():
  99. for key_id, key_data in server_config["verify_keys"].items():
  100. if is_signing_algorithm_supported(key_id):
  101. key_base64 = key_data["key"]
  102. key_bytes = decode_base64(key_base64)
  103. verify_key = decode_verify_key_bytes(key_id, key_bytes)
  104. servers.setdefault(server_name, {})[key_id] = verify_key
  105. return servers
  106. def read_signing_key(self, signing_key_path):
  107. signing_keys = self.read_file(signing_key_path, "signing_key")
  108. try:
  109. return read_signing_keys(signing_keys.splitlines(True))
  110. except Exception as e:
  111. raise ConfigError(
  112. "Error reading signing_key: %s" % (str(e))
  113. )
  114. def read_old_signing_keys(self, old_signing_keys):
  115. keys = {}
  116. for key_id, key_data in old_signing_keys.items():
  117. if is_signing_algorithm_supported(key_id):
  118. key_base64 = key_data["key"]
  119. key_bytes = decode_base64(key_base64)
  120. verify_key = decode_verify_key_bytes(key_id, key_bytes)
  121. verify_key.expired_ts = key_data["expired_ts"]
  122. keys[key_id] = verify_key
  123. else:
  124. raise ConfigError(
  125. "Unsupported signing algorithm for old key: %r" % (key_id,)
  126. )
  127. return keys
  128. def generate_files(self, config):
  129. signing_key_path = config["signing_key_path"]
  130. if not self.path_exists(signing_key_path):
  131. with open(signing_key_path, "w") as signing_key_file:
  132. key_id = "a_" + random_string(4)
  133. write_signing_keys(
  134. signing_key_file, (generate_signing_key(key_id),),
  135. )
  136. else:
  137. signing_keys = self.read_file(signing_key_path, "signing_key")
  138. if len(signing_keys.split("\n")[0].split()) == 1:
  139. # handle keys in the old format.
  140. key_id = "a_" + random_string(4)
  141. key = decode_signing_key_base64(
  142. NACL_ED25519, key_id, signing_keys.split("\n")[0]
  143. )
  144. with open(signing_key_path, "w") as signing_key_file:
  145. write_signing_keys(
  146. signing_key_file, (key,),
  147. )