key.py 18 KB

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