key.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  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, generate_secrets=False,
  57. **kwargs):
  58. base_key_name = os.path.join(config_dir_path, server_name)
  59. if generate_secrets:
  60. macaroon_secret_key = 'macaroon_secret_key: "%s"' % (
  61. random_string_with_symbols(50),
  62. )
  63. form_secret = 'form_secret: "%s"' % random_string_with_symbols(50)
  64. else:
  65. macaroon_secret_key = "# macaroon_secret_key: <PRIVATE STRING>"
  66. form_secret = "# form_secret: <PRIVATE STRING>"
  67. return """\
  68. # a secret which is used to sign access tokens. If none is specified,
  69. # the registration_shared_secret is used, if one is given; otherwise,
  70. # a secret key is derived from the signing key.
  71. #
  72. # Note that changing this will invalidate any active access tokens, so
  73. # all clients will have to log back in.
  74. %(macaroon_secret_key)s
  75. # Used to enable access token expiration.
  76. expire_access_token: False
  77. # a secret which is used to calculate HMACs for form values, to stop
  78. # falsification of values. Must be specified for the User Consent
  79. # forms to work.
  80. %(form_secret)s
  81. ## Signing Keys ##
  82. # Path to the signing key to sign messages with
  83. signing_key_path: "%(base_key_name)s.signing.key"
  84. # The keys that the server used to sign messages with but won't use
  85. # to sign new messages. E.g. it has lost its private key
  86. old_signing_keys: {}
  87. # "ed25519:auto":
  88. # # Base64 encoded public key
  89. # key: "The public part of your old signing key."
  90. # # Millisecond POSIX timestamp when the key expired.
  91. # expired_ts: 123456789123
  92. # How long key response published by this server is valid for.
  93. # Used to set the valid_until_ts in /key/v2 APIs.
  94. # Determines how quickly servers will query to check which keys
  95. # are still valid.
  96. key_refresh_interval: "1d" # 1 Day.
  97. # The trusted servers to download signing keys from.
  98. perspectives:
  99. servers:
  100. "matrix.org":
  101. verify_keys:
  102. "ed25519:auto":
  103. key: "Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw"
  104. """ % locals()
  105. def read_perspectives(self, perspectives_config):
  106. servers = {}
  107. for server_name, server_config in perspectives_config["servers"].items():
  108. for key_id, key_data in server_config["verify_keys"].items():
  109. if is_signing_algorithm_supported(key_id):
  110. key_base64 = key_data["key"]
  111. key_bytes = decode_base64(key_base64)
  112. verify_key = decode_verify_key_bytes(key_id, key_bytes)
  113. servers.setdefault(server_name, {})[key_id] = verify_key
  114. return servers
  115. def read_signing_key(self, signing_key_path):
  116. signing_keys = self.read_file(signing_key_path, "signing_key")
  117. try:
  118. return read_signing_keys(signing_keys.splitlines(True))
  119. except Exception as e:
  120. raise ConfigError(
  121. "Error reading signing_key: %s" % (str(e))
  122. )
  123. def read_old_signing_keys(self, old_signing_keys):
  124. keys = {}
  125. for key_id, key_data in old_signing_keys.items():
  126. if is_signing_algorithm_supported(key_id):
  127. key_base64 = key_data["key"]
  128. key_bytes = decode_base64(key_base64)
  129. verify_key = decode_verify_key_bytes(key_id, key_bytes)
  130. verify_key.expired_ts = key_data["expired_ts"]
  131. keys[key_id] = verify_key
  132. else:
  133. raise ConfigError(
  134. "Unsupported signing algorithm for old key: %r" % (key_id,)
  135. )
  136. return keys
  137. def generate_files(self, config):
  138. signing_key_path = config["signing_key_path"]
  139. if not self.path_exists(signing_key_path):
  140. with open(signing_key_path, "w") as signing_key_file:
  141. key_id = "a_" + random_string(4)
  142. write_signing_keys(
  143. signing_key_file, (generate_signing_key(key_id),),
  144. )
  145. else:
  146. signing_keys = self.read_file(signing_key_path, "signing_key")
  147. if len(signing_keys.split("\n")[0].split()) == 1:
  148. # handle keys in the old format.
  149. key_id = "a_" + random_string(4)
  150. key = decode_signing_key_base64(
  151. NACL_ED25519, key_id, signing_keys.split("\n")[0]
  152. )
  153. with open(signing_key_path, "w") as signing_key_file:
  154. write_signing_keys(
  155. signing_key_file, (key,),
  156. )