Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 
 
 

400 linhas
15 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. from typing import TYPE_CHECKING, 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. if TYPE_CHECKING:
  37. from signedjson.key import VerifyKeyWithExpiry
  38. INSECURE_NOTARY_ERROR = """\
  39. Your server is configured to accept key server responses without signature
  40. validation or TLS certificate validation. This is likely to be very insecure. If
  41. you are *sure* you want to do this, set 'accept_keys_insecurely' on the
  42. keyserver configuration."""
  43. RELYING_ON_MATRIX_KEY_ERROR = """\
  44. Your server is configured to accept key server responses without TLS certificate
  45. validation, and which are only signed by the old (possibly compromised)
  46. matrix.org signing key 'ed25519:auto'. This likely isn't what you want to do,
  47. and you should enable 'federation_verify_certificates' in your configuration.
  48. If you are *sure* you want to do this, set 'accept_keys_insecurely' on the
  49. trusted_key_server configuration."""
  50. TRUSTED_KEY_SERVER_NOT_CONFIGURED_WARN = """\
  51. Synapse requires that a list of trusted key servers are specified in order to
  52. provide signing keys for other servers in the federation.
  53. This homeserver does not have a trusted key server configured in
  54. homeserver.yaml and will fall back to the default of 'matrix.org'.
  55. Trusted key servers should be long-lived and stable which makes matrix.org a
  56. good choice for many admins, but some admins may wish to choose another. To
  57. suppress this warning, the admin should set 'trusted_key_servers' in
  58. homeserver.yaml to their desired key server and 'suppress_key_server_warning'
  59. to 'true'.
  60. In a future release the software-defined default will be removed entirely and
  61. the trusted key server will be defined exclusively by the value of
  62. 'trusted_key_servers'.
  63. --------------------------------------------------------------------------------"""
  64. TRUSTED_KEY_SERVER_CONFIGURED_AS_M_ORG_WARN = """\
  65. This server is configured to use 'matrix.org' as its trusted key server via the
  66. 'trusted_key_servers' config option. 'matrix.org' is a good choice for a key
  67. server since it is long-lived, stable and trusted. However, some admins may
  68. wish to use another server for this purpose.
  69. To suppress this warning and continue using 'matrix.org', admins should set
  70. 'suppress_key_server_warning' to 'true' in homeserver.yaml.
  71. --------------------------------------------------------------------------------"""
  72. logger = logging.getLogger(__name__)
  73. @attr.s(slots=True, auto_attribs=True)
  74. class TrustedKeyServer:
  75. # name of the server.
  76. server_name: str
  77. # map from key id to key object, or None to disable signature verification.
  78. verify_keys: Optional[Dict[str, VerifyKey]] = None
  79. class KeyConfig(Config):
  80. section = "key"
  81. def read_config(
  82. self, config: JsonDict, config_dir_path: str, **kwargs: Any
  83. ) -> None:
  84. # the signing key can be specified inline or in a separate file
  85. if "signing_key" in config:
  86. self.signing_key = read_signing_keys([config["signing_key"]])
  87. else:
  88. assert config_dir_path is not None
  89. signing_key_path = config.get("signing_key_path")
  90. if signing_key_path is None:
  91. signing_key_path = os.path.join(
  92. config_dir_path, config["server_name"] + ".signing.key"
  93. )
  94. self.signing_key = self.read_signing_keys(signing_key_path, "signing_key")
  95. self.old_signing_keys = self.read_old_signing_keys(
  96. config.get("old_signing_keys")
  97. )
  98. self.key_refresh_interval = self.parse_duration(
  99. config.get("key_refresh_interval", "1d")
  100. )
  101. suppress_key_server_warning = config.get("suppress_key_server_warning", False)
  102. key_server_signing_keys_path = config.get("key_server_signing_keys_path")
  103. if key_server_signing_keys_path:
  104. self.key_server_signing_keys = self.read_signing_keys(
  105. key_server_signing_keys_path, "key_server_signing_keys_path"
  106. )
  107. else:
  108. self.key_server_signing_keys = list(self.signing_key)
  109. # if neither trusted_key_servers nor perspectives are given, use the default.
  110. if "perspectives" not in config and "trusted_key_servers" not in config:
  111. logger.warning(TRUSTED_KEY_SERVER_NOT_CONFIGURED_WARN)
  112. key_servers = [{"server_name": "matrix.org"}]
  113. else:
  114. key_servers = config.get("trusted_key_servers", [])
  115. if not isinstance(key_servers, list):
  116. raise ConfigError(
  117. "trusted_key_servers, if given, must be a list, not a %s"
  118. % (type(key_servers).__name__,)
  119. )
  120. # merge the 'perspectives' config into the 'trusted_key_servers' config.
  121. key_servers.extend(_perspectives_to_key_servers(config))
  122. if not suppress_key_server_warning and "matrix.org" in (
  123. s["server_name"] for s in key_servers
  124. ):
  125. logger.warning(TRUSTED_KEY_SERVER_CONFIGURED_AS_M_ORG_WARN)
  126. # list of TrustedKeyServer objects
  127. self.key_servers = list(
  128. _parse_key_servers(
  129. key_servers, self.root.tls.federation_verify_certificates
  130. )
  131. )
  132. macaroon_secret_key: Optional[str] = config.get(
  133. "macaroon_secret_key", self.root.registration.registration_shared_secret
  134. )
  135. if not macaroon_secret_key:
  136. # Unfortunately, there are people out there that don't have this
  137. # set. Lets just be "nice" and derive one from their secret key.
  138. logger.warning("Config is missing macaroon_secret_key")
  139. seed = bytes(self.signing_key[0])
  140. self.macaroon_secret_key = hashlib.sha256(seed).digest()
  141. else:
  142. self.macaroon_secret_key = macaroon_secret_key.encode("utf-8")
  143. # a secret which is used to calculate HMACs for form values, to stop
  144. # falsification of values
  145. self.form_secret = config.get("form_secret", None)
  146. def generate_config_section(
  147. self,
  148. config_dir_path: str,
  149. server_name: str,
  150. generate_secrets: bool = False,
  151. **kwargs: Any,
  152. ) -> str:
  153. base_key_name = os.path.join(config_dir_path, server_name)
  154. macaroon_secret_key = ""
  155. form_secret = ""
  156. if generate_secrets:
  157. macaroon_secret_key = 'macaroon_secret_key: "%s"' % (
  158. random_string_with_symbols(50),
  159. )
  160. form_secret = 'form_secret: "%s"' % random_string_with_symbols(50)
  161. return (
  162. """\
  163. %(macaroon_secret_key)s
  164. %(form_secret)s
  165. signing_key_path: "%(base_key_name)s.signing.key"
  166. trusted_key_servers:
  167. - server_name: "matrix.org"
  168. """
  169. % locals()
  170. )
  171. def read_signing_keys(self, signing_key_path: str, name: str) -> List[SigningKey]:
  172. """Read the signing keys in the given path.
  173. Args:
  174. signing_key_path
  175. name: Associated config key name
  176. Returns:
  177. The signing keys read from the given path.
  178. """
  179. signing_keys = self.read_file(signing_key_path, name)
  180. try:
  181. loaded_signing_keys = read_signing_keys(
  182. [
  183. signing_key_line
  184. for signing_key_line in signing_keys.splitlines(keepends=False)
  185. if signing_key_line.strip()
  186. ]
  187. )
  188. if not loaded_signing_keys:
  189. raise ConfigError(f"No signing keys in file {signing_key_path}")
  190. return loaded_signing_keys
  191. except Exception as e:
  192. raise ConfigError("Error reading %s: %s" % (name, str(e)))
  193. def read_old_signing_keys(
  194. self, old_signing_keys: Optional[JsonDict]
  195. ) -> Dict[str, "VerifyKeyWithExpiry"]:
  196. if old_signing_keys is None:
  197. return {}
  198. keys = {}
  199. for key_id, key_data in old_signing_keys.items():
  200. if is_signing_algorithm_supported(key_id):
  201. key_base64 = key_data["key"]
  202. key_bytes = decode_base64(key_base64)
  203. verify_key: "VerifyKeyWithExpiry" = decode_verify_key_bytes(key_id, key_bytes) # type: ignore[assignment]
  204. verify_key.expired = key_data["expired_ts"]
  205. keys[key_id] = verify_key
  206. else:
  207. raise ConfigError(
  208. "Unsupported signing algorithm for old key: %r" % (key_id,)
  209. )
  210. return keys
  211. def generate_files(self, config: Dict[str, Any], config_dir_path: str) -> None:
  212. if "signing_key" in config:
  213. return
  214. signing_key_path = config.get("signing_key_path")
  215. if signing_key_path is None:
  216. signing_key_path = os.path.join(
  217. config_dir_path, config["server_name"] + ".signing.key"
  218. )
  219. if not self.path_exists(signing_key_path):
  220. print("Generating signing key file %s" % (signing_key_path,))
  221. with open(
  222. signing_key_path, "w", opener=lambda p, f: os.open(p, f, mode=0o640)
  223. ) as signing_key_file:
  224. key_id = "a_" + random_string(4)
  225. write_signing_keys(signing_key_file, (generate_signing_key(key_id),))
  226. else:
  227. signing_keys = self.read_file(signing_key_path, "signing_key")
  228. if len(signing_keys.split("\n")[0].split()) == 1:
  229. # handle keys in the old format.
  230. key_id = "a_" + random_string(4)
  231. key = decode_signing_key_base64(
  232. NACL_ED25519, key_id, signing_keys.split("\n")[0]
  233. )
  234. with open(
  235. signing_key_path, "w", opener=lambda p, f: os.open(p, f, mode=0o640)
  236. ) as signing_key_file:
  237. write_signing_keys(signing_key_file, (key,))
  238. def _perspectives_to_key_servers(config: JsonDict) -> Iterator[JsonDict]:
  239. """Convert old-style 'perspectives' configs into new-style 'trusted_key_servers'
  240. Returns an iterable of entries to add to trusted_key_servers.
  241. """
  242. # 'perspectives' looks like:
  243. #
  244. # {
  245. # "servers": {
  246. # "matrix.org": {
  247. # "verify_keys": {
  248. # "ed25519:auto": {
  249. # "key": "Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw"
  250. # }
  251. # }
  252. # }
  253. # }
  254. # }
  255. #
  256. # 'trusted_keys' looks like:
  257. #
  258. # [
  259. # {
  260. # "server_name": "matrix.org",
  261. # "verify_keys": {
  262. # "ed25519:auto": "Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw",
  263. # }
  264. # }
  265. # ]
  266. perspectives_servers = config.get("perspectives", {}).get("servers", {})
  267. for server_name, server_opts in perspectives_servers.items():
  268. trusted_key_server_entry = {"server_name": server_name}
  269. verify_keys = server_opts.get("verify_keys")
  270. if verify_keys is not None:
  271. trusted_key_server_entry["verify_keys"] = {
  272. key_id: key_data["key"] for key_id, key_data in verify_keys.items()
  273. }
  274. yield trusted_key_server_entry
  275. TRUSTED_KEY_SERVERS_SCHEMA = {
  276. "$schema": "http://json-schema.org/draft-04/schema#",
  277. "description": "schema for the trusted_key_servers setting",
  278. "type": "array",
  279. "items": {
  280. "type": "object",
  281. "properties": {
  282. "server_name": {"type": "string"},
  283. "verify_keys": {
  284. "type": "object",
  285. # each key must be a base64 string
  286. "additionalProperties": {"type": "string"},
  287. },
  288. },
  289. "required": ["server_name"],
  290. },
  291. }
  292. def _parse_key_servers(
  293. key_servers: List[Any], federation_verify_certificates: bool
  294. ) -> Iterator[TrustedKeyServer]:
  295. try:
  296. jsonschema.validate(key_servers, TRUSTED_KEY_SERVERS_SCHEMA)
  297. except jsonschema.ValidationError as e:
  298. raise ConfigError(
  299. "Unable to parse 'trusted_key_servers': {}".format(
  300. e.message # noqa: B306, jsonschema.ValidationError.message is a valid attribute
  301. )
  302. )
  303. for server in key_servers:
  304. server_name = server["server_name"]
  305. result = TrustedKeyServer(server_name=server_name)
  306. verify_keys: Optional[Dict[str, str]] = server.get("verify_keys")
  307. if verify_keys is not None:
  308. result.verify_keys = {}
  309. for key_id, key_base64 in verify_keys.items():
  310. if not is_signing_algorithm_supported(key_id):
  311. raise ConfigError(
  312. "Unsupported signing algorithm on key %s for server %s in "
  313. "trusted_key_servers" % (key_id, server_name)
  314. )
  315. try:
  316. key_bytes = decode_base64(key_base64)
  317. verify_key = decode_verify_key_bytes(key_id, key_bytes)
  318. except Exception as e:
  319. raise ConfigError(
  320. "Unable to parse key %s for server %s in "
  321. "trusted_key_servers: %s" % (key_id, server_name, e)
  322. )
  323. result.verify_keys[key_id] = verify_key
  324. if not federation_verify_certificates and not server.get(
  325. "accept_keys_insecurely"
  326. ):
  327. _assert_keyserver_has_verify_keys(result)
  328. yield result
  329. def _assert_keyserver_has_verify_keys(trusted_key_server: TrustedKeyServer) -> None:
  330. if not trusted_key_server.verify_keys:
  331. raise ConfigError(INSECURE_NOTARY_ERROR)
  332. # also check that they are not blindly checking the old matrix.org key
  333. if trusted_key_server.server_name == "matrix.org" and any(
  334. key_id == "ed25519:auto" for key_id in trusted_key_server.verify_keys
  335. ):
  336. raise ConfigError(RELYING_ON_MATRIX_KEY_ERROR)