You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

453 lines
17 KiB

  1. # Copyright 2015, 2016 OpenMarket Ltd
  2. # Copyright 2019 The Matrix.org Foundation C.I.C.
  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. import attr
  19. import jsonschema
  20. from signedjson.key import (
  21. NACL_ED25519,
  22. decode_signing_key_base64,
  23. decode_verify_key_bytes,
  24. generate_signing_key,
  25. is_signing_algorithm_supported,
  26. read_signing_keys,
  27. write_signing_keys,
  28. )
  29. from unpaddedbase64 import decode_base64
  30. from synapse.util.stringutils import random_string, random_string_with_symbols
  31. from ._base import Config, ConfigError
  32. INSECURE_NOTARY_ERROR = """\
  33. Your server is configured to accept key server responses without signature
  34. validation or TLS certificate validation. This is likely to be very insecure. If
  35. you are *sure* you want to do this, set 'accept_keys_insecurely' on the
  36. keyserver configuration."""
  37. RELYING_ON_MATRIX_KEY_ERROR = """\
  38. Your server is configured to accept key server responses without TLS certificate
  39. validation, and which are only signed by the old (possibly compromised)
  40. matrix.org signing key 'ed25519:auto'. This likely isn't what you want to do,
  41. and you should enable 'federation_verify_certificates' in your configuration.
  42. If you are *sure* you want to do this, set 'accept_keys_insecurely' on the
  43. trusted_key_server configuration."""
  44. TRUSTED_KEY_SERVER_NOT_CONFIGURED_WARN = """\
  45. Synapse requires that a list of trusted key servers are specified in order to
  46. provide signing keys for other servers in the federation.
  47. This homeserver does not have a trusted key server configured in
  48. homeserver.yaml and will fall back to the default of 'matrix.org'.
  49. Trusted key servers should be long-lived and stable which makes matrix.org a
  50. good choice for many admins, but some admins may wish to choose another. To
  51. suppress this warning, the admin should set 'trusted_key_servers' in
  52. homeserver.yaml to their desired key server and 'suppress_key_server_warning'
  53. to 'true'.
  54. In a future release the software-defined default will be removed entirely and
  55. the trusted key server will be defined exclusively by the value of
  56. 'trusted_key_servers'.
  57. --------------------------------------------------------------------------------"""
  58. TRUSTED_KEY_SERVER_CONFIGURED_AS_M_ORG_WARN = """\
  59. This server is configured to use 'matrix.org' as its trusted key server via the
  60. 'trusted_key_servers' config option. 'matrix.org' is a good choice for a key
  61. server since it is long-lived, stable and trusted. However, some admins may
  62. wish to use another server for this purpose.
  63. To suppress this warning and continue using 'matrix.org', admins should set
  64. 'suppress_key_server_warning' to 'true' in homeserver.yaml.
  65. --------------------------------------------------------------------------------"""
  66. logger = logging.getLogger(__name__)
  67. @attr.s
  68. class TrustedKeyServer:
  69. # string: name of the server.
  70. server_name = attr.ib()
  71. # dict[str,VerifyKey]|None: map from key id to key object, or None to disable
  72. # signature verification.
  73. verify_keys = attr.ib(default=None)
  74. class KeyConfig(Config):
  75. section = "key"
  76. def read_config(self, config, config_dir_path, **kwargs):
  77. # the signing key can be specified inline or in a separate file
  78. if "signing_key" in config:
  79. self.signing_key = read_signing_keys([config["signing_key"]])
  80. else:
  81. signing_key_path = config.get("signing_key_path")
  82. if signing_key_path is None:
  83. signing_key_path = os.path.join(
  84. config_dir_path, config["server_name"] + ".signing.key"
  85. )
  86. self.signing_key = self.read_signing_keys(signing_key_path, "signing_key")
  87. self.old_signing_keys = self.read_old_signing_keys(
  88. config.get("old_signing_keys")
  89. )
  90. self.key_refresh_interval = self.parse_duration(
  91. config.get("key_refresh_interval", "1d")
  92. )
  93. suppress_key_server_warning = config.get("suppress_key_server_warning", False)
  94. key_server_signing_keys_path = config.get("key_server_signing_keys_path")
  95. if key_server_signing_keys_path:
  96. self.key_server_signing_keys = self.read_signing_keys(
  97. key_server_signing_keys_path, "key_server_signing_keys_path"
  98. )
  99. else:
  100. self.key_server_signing_keys = list(self.signing_key)
  101. # if neither trusted_key_servers nor perspectives are given, use the default.
  102. if "perspectives" not in config and "trusted_key_servers" not in config:
  103. logger.warning(TRUSTED_KEY_SERVER_NOT_CONFIGURED_WARN)
  104. key_servers = [{"server_name": "matrix.org"}]
  105. else:
  106. key_servers = config.get("trusted_key_servers", [])
  107. if not isinstance(key_servers, list):
  108. raise ConfigError(
  109. "trusted_key_servers, if given, must be a list, not a %s"
  110. % (type(key_servers).__name__,)
  111. )
  112. # merge the 'perspectives' config into the 'trusted_key_servers' config.
  113. key_servers.extend(_perspectives_to_key_servers(config))
  114. if not suppress_key_server_warning and "matrix.org" in (
  115. s["server_name"] for s in key_servers
  116. ):
  117. logger.warning(TRUSTED_KEY_SERVER_CONFIGURED_AS_M_ORG_WARN)
  118. # list of TrustedKeyServer objects
  119. self.key_servers = list(
  120. _parse_key_servers(key_servers, self.federation_verify_certificates)
  121. )
  122. self.macaroon_secret_key = config.get(
  123. "macaroon_secret_key", self.registration_shared_secret
  124. )
  125. if not self.macaroon_secret_key:
  126. # Unfortunately, there are people out there that don't have this
  127. # set. Lets just be "nice" and derive one from their secret key.
  128. logger.warning("Config is missing macaroon_secret_key")
  129. seed = bytes(self.signing_key[0])
  130. self.macaroon_secret_key = hashlib.sha256(seed).digest()
  131. # a secret which is used to calculate HMACs for form values, to stop
  132. # falsification of values
  133. self.form_secret = config.get("form_secret", None)
  134. def generate_config_section(
  135. self, config_dir_path, server_name, generate_secrets=False, **kwargs
  136. ):
  137. base_key_name = os.path.join(config_dir_path, server_name)
  138. if generate_secrets:
  139. macaroon_secret_key = 'macaroon_secret_key: "%s"' % (
  140. random_string_with_symbols(50),
  141. )
  142. form_secret = 'form_secret: "%s"' % random_string_with_symbols(50)
  143. else:
  144. macaroon_secret_key = "#macaroon_secret_key: <PRIVATE STRING>"
  145. form_secret = "#form_secret: <PRIVATE STRING>"
  146. return (
  147. """\
  148. # a secret which is used to sign access tokens. If none is specified,
  149. # the registration_shared_secret is used, if one is given; otherwise,
  150. # a secret key is derived from the signing key.
  151. #
  152. %(macaroon_secret_key)s
  153. # a secret which is used to calculate HMACs for form values, to stop
  154. # falsification of values. Must be specified for the User Consent
  155. # forms to work.
  156. #
  157. %(form_secret)s
  158. ## Signing Keys ##
  159. # Path to the signing key to sign messages with
  160. #
  161. signing_key_path: "%(base_key_name)s.signing.key"
  162. # The keys that the server used to sign messages with but won't use
  163. # to sign new messages.
  164. #
  165. old_signing_keys:
  166. # For each key, `key` should be the base64-encoded public key, and
  167. # `expired_ts`should be the time (in milliseconds since the unix epoch) that
  168. # it was last used.
  169. #
  170. # It is possible to build an entry from an old signing.key file using the
  171. # `export_signing_key` script which is provided with synapse.
  172. #
  173. # For example:
  174. #
  175. #"ed25519:id": { key: "base64string", expired_ts: 123456789123 }
  176. # How long key response published by this server is valid for.
  177. # Used to set the valid_until_ts in /key/v2 APIs.
  178. # Determines how quickly servers will query to check which keys
  179. # are still valid.
  180. #
  181. #key_refresh_interval: 1d
  182. # The trusted servers to download signing keys from.
  183. #
  184. # When we need to fetch a signing key, each server is tried in parallel.
  185. #
  186. # Normally, the connection to the key server is validated via TLS certificates.
  187. # Additional security can be provided by configuring a `verify key`, which
  188. # will make synapse check that the response is signed by that key.
  189. #
  190. # This setting supercedes an older setting named `perspectives`. The old format
  191. # is still supported for backwards-compatibility, but it is deprecated.
  192. #
  193. # 'trusted_key_servers' defaults to matrix.org, but using it will generate a
  194. # warning on start-up. To suppress this warning, set
  195. # 'suppress_key_server_warning' to true.
  196. #
  197. # Options for each entry in the list include:
  198. #
  199. # server_name: the name of the server. required.
  200. #
  201. # verify_keys: an optional map from key id to base64-encoded public key.
  202. # If specified, we will check that the response is signed by at least
  203. # one of the given keys.
  204. #
  205. # accept_keys_insecurely: a boolean. Normally, if `verify_keys` is unset,
  206. # and federation_verify_certificates is not `true`, synapse will refuse
  207. # to start, because this would allow anyone who can spoof DNS responses
  208. # to masquerade as the trusted key server. If you know what you are doing
  209. # and are sure that your network environment provides a secure connection
  210. # to the key server, you can set this to `true` to override this
  211. # behaviour.
  212. #
  213. # An example configuration might look like:
  214. #
  215. #trusted_key_servers:
  216. # - server_name: "my_trusted_server.example.com"
  217. # verify_keys:
  218. # "ed25519:auto": "abcdefghijklmnopqrstuvwxyzabcdefghijklmopqr"
  219. # - server_name: "my_other_trusted_server.example.com"
  220. #
  221. trusted_key_servers:
  222. - server_name: "matrix.org"
  223. # Uncomment the following to disable the warning that is emitted when the
  224. # trusted_key_servers include 'matrix.org'. See above.
  225. #
  226. #suppress_key_server_warning: true
  227. # The signing keys to use when acting as a trusted key server. If not specified
  228. # defaults to the server signing key.
  229. #
  230. # Can contain multiple keys, one per line.
  231. #
  232. #key_server_signing_keys_path: "key_server_signing_keys.key"
  233. """
  234. % locals()
  235. )
  236. def read_signing_keys(self, signing_key_path, name):
  237. """Read the signing keys in the given path.
  238. Args:
  239. signing_key_path (str)
  240. name (str): Associated config key name
  241. Returns:
  242. list[SigningKey]
  243. """
  244. signing_keys = self.read_file(signing_key_path, name)
  245. try:
  246. return read_signing_keys(signing_keys.splitlines(True))
  247. except Exception as e:
  248. raise ConfigError("Error reading %s: %s" % (name, str(e)))
  249. def read_old_signing_keys(self, old_signing_keys):
  250. if old_signing_keys is None:
  251. return {}
  252. keys = {}
  253. for key_id, key_data in old_signing_keys.items():
  254. if is_signing_algorithm_supported(key_id):
  255. key_base64 = key_data["key"]
  256. key_bytes = decode_base64(key_base64)
  257. verify_key = decode_verify_key_bytes(key_id, key_bytes)
  258. verify_key.expired_ts = key_data["expired_ts"]
  259. keys[key_id] = verify_key
  260. else:
  261. raise ConfigError(
  262. "Unsupported signing algorithm for old key: %r" % (key_id,)
  263. )
  264. return keys
  265. def generate_files(self, config, config_dir_path):
  266. if "signing_key" in config:
  267. return
  268. signing_key_path = config.get("signing_key_path")
  269. if signing_key_path is None:
  270. signing_key_path = os.path.join(
  271. config_dir_path, config["server_name"] + ".signing.key"
  272. )
  273. if not self.path_exists(signing_key_path):
  274. print("Generating signing key file %s" % (signing_key_path,))
  275. with open(signing_key_path, "w") as signing_key_file:
  276. key_id = "a_" + random_string(4)
  277. write_signing_keys(signing_key_file, (generate_signing_key(key_id),))
  278. else:
  279. signing_keys = self.read_file(signing_key_path, "signing_key")
  280. if len(signing_keys.split("\n")[0].split()) == 1:
  281. # handle keys in the old format.
  282. key_id = "a_" + random_string(4)
  283. key = decode_signing_key_base64(
  284. NACL_ED25519, key_id, signing_keys.split("\n")[0]
  285. )
  286. with open(signing_key_path, "w") as signing_key_file:
  287. write_signing_keys(signing_key_file, (key,))
  288. def _perspectives_to_key_servers(config):
  289. """Convert old-style 'perspectives' configs into new-style 'trusted_key_servers'
  290. Returns an iterable of entries to add to trusted_key_servers.
  291. """
  292. # 'perspectives' looks like:
  293. #
  294. # {
  295. # "servers": {
  296. # "matrix.org": {
  297. # "verify_keys": {
  298. # "ed25519:auto": {
  299. # "key": "Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw"
  300. # }
  301. # }
  302. # }
  303. # }
  304. # }
  305. #
  306. # 'trusted_keys' looks like:
  307. #
  308. # [
  309. # {
  310. # "server_name": "matrix.org",
  311. # "verify_keys": {
  312. # "ed25519:auto": "Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw",
  313. # }
  314. # }
  315. # ]
  316. perspectives_servers = config.get("perspectives", {}).get("servers", {})
  317. for server_name, server_opts in perspectives_servers.items():
  318. trusted_key_server_entry = {"server_name": server_name}
  319. verify_keys = server_opts.get("verify_keys")
  320. if verify_keys is not None:
  321. trusted_key_server_entry["verify_keys"] = {
  322. key_id: key_data["key"] for key_id, key_data in verify_keys.items()
  323. }
  324. yield trusted_key_server_entry
  325. TRUSTED_KEY_SERVERS_SCHEMA = {
  326. "$schema": "http://json-schema.org/draft-04/schema#",
  327. "description": "schema for the trusted_key_servers setting",
  328. "type": "array",
  329. "items": {
  330. "type": "object",
  331. "properties": {
  332. "server_name": {"type": "string"},
  333. "verify_keys": {
  334. "type": "object",
  335. # each key must be a base64 string
  336. "additionalProperties": {"type": "string"},
  337. },
  338. },
  339. "required": ["server_name"],
  340. },
  341. }
  342. def _parse_key_servers(key_servers, federation_verify_certificates):
  343. try:
  344. jsonschema.validate(key_servers, TRUSTED_KEY_SERVERS_SCHEMA)
  345. except jsonschema.ValidationError as e:
  346. raise ConfigError(
  347. "Unable to parse 'trusted_key_servers': {}".format(
  348. e.message # noqa: B306, jsonschema.ValidationError.message is a valid attribute
  349. )
  350. )
  351. for server in key_servers:
  352. server_name = server["server_name"]
  353. result = TrustedKeyServer(server_name=server_name)
  354. verify_keys = server.get("verify_keys")
  355. if verify_keys is not None:
  356. result.verify_keys = {}
  357. for key_id, key_base64 in verify_keys.items():
  358. if not is_signing_algorithm_supported(key_id):
  359. raise ConfigError(
  360. "Unsupported signing algorithm on key %s for server %s in "
  361. "trusted_key_servers" % (key_id, server_name)
  362. )
  363. try:
  364. key_bytes = decode_base64(key_base64)
  365. verify_key = decode_verify_key_bytes(key_id, key_bytes)
  366. except Exception as e:
  367. raise ConfigError(
  368. "Unable to parse key %s for server %s in "
  369. "trusted_key_servers: %s" % (key_id, server_name, e)
  370. )
  371. result.verify_keys[key_id] = verify_key
  372. if not federation_verify_certificates and not server.get(
  373. "accept_keys_insecurely"
  374. ):
  375. _assert_keyserver_has_verify_keys(result)
  376. yield result
  377. def _assert_keyserver_has_verify_keys(trusted_key_server):
  378. if not trusted_key_server.verify_keys:
  379. raise ConfigError(INSECURE_NOTARY_ERROR)
  380. # also check that they are not blindly checking the old matrix.org key
  381. if trusted_key_server.server_name == "matrix.org" and any(
  382. key_id == "ed25519:auto" for key_id in trusted_key_server.verify_keys
  383. ):
  384. raise ConfigError(RELYING_ON_MATRIX_KEY_ERROR)