diff --git a/synapse/config/emailconfig.py b/synapse/config/emailconfig.py
index 342a6ce5fd..ae04252906 100644
--- a/synapse/config/emailconfig.py
+++ b/synapse/config/emailconfig.py
@@ -1,5 +1,7 @@
# -*- coding: utf-8 -*-
-# Copyright 2015, 2016 OpenMarket Ltd
+# Copyright 2015-2016 OpenMarket Ltd
+# Copyright 2017-2018 New Vector Ltd
+# Copyright 2019 The Matrix.org Foundation C.I.C.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -29,12 +31,76 @@ logger = logging.getLogger(__name__)
class EmailConfig(Config):
def read_config(self, config):
+ # TODO: We should separate better the email configuration from the notification
+ # and account validity config.
+
self.email_enable_notifs = False
email_config = config.get("email", {})
+
+ self.email_smtp_host = email_config.get("smtp_host", None)
+ self.email_smtp_port = email_config.get("smtp_port", None)
+ self.email_smtp_user = email_config.get("smtp_user", None)
+ self.email_smtp_pass = email_config.get("smtp_pass", None)
+ self.require_transport_security = email_config.get(
+ "require_transport_security", False
+ )
+ if "app_name" in email_config:
+ self.email_app_name = email_config["app_name"]
+ else:
+ self.email_app_name = "Matrix"
+
+ # TODO: Rename notif_from to something more generic, or have a separate
+ # from for password resets, message notifications, etc?
+ # Currently the email section is a bit bogged down with settings for
+ # multiple functions. Would be good to split it out into separate
+ # sections and only put the common ones under email:
+ self.email_notif_from = email_config.get("notif_from", None)
+ if self.email_notif_from is not None:
+ # make sure it's valid
+ parsed = email.utils.parseaddr(self.email_notif_from)
+ if parsed[1] == '':
+ raise RuntimeError("Invalid notif_from address")
+
+ template_dir = email_config.get("template_dir")
+ # we need an absolute path, because we change directory after starting (and
+ # we don't yet know what auxilliary templates like mail.css we will need).
+ # (Note that loading as package_resources with jinja.PackageLoader doesn't
+ # work for the same reason.)
+ if not template_dir:
+ template_dir = pkg_resources.resource_filename(
+ 'synapse', 'res/templates'
+ )
+
+ self.email_template_dir = os.path.abspath(template_dir)
+
self.email_enable_notifs = email_config.get("enable_notifs", False)
+ account_validity_renewal_enabled = config.get(
+ "account_validity", {},
+ ).get("renew_at")
- if self.email_enable_notifs:
+ email_trust_identity_server_for_password_resets = email_config.get(
+ "trust_identity_server_for_password_resets", False,
+ )
+ self.email_password_reset_behaviour = (
+ "remote" if email_trust_identity_server_for_password_resets else "local"
+ )
+ if self.email_password_reset_behaviour == "local" and email_config == {}:
+ logger.warn(
+ "User password resets have been disabled due to lack of email config"
+ )
+ self.email_password_reset_behaviour = "off"
+
+ # Get lifetime of a validation token in milliseconds
+ self.email_validation_token_lifetime = self.parse_duration(
+ email_config.get("validation_token_lifetime", "1h")
+ )
+
+ if (
+ self.email_enable_notifs
+ or account_validity_renewal_enabled
+ or self.email_password_reset_behaviour == "local"
+ ):
# make sure we can import the required deps
import jinja2
import bleach
@@ -42,6 +108,68 @@ class EmailConfig(Config):
jinja2
bleach
+ if self.email_password_reset_behaviour == "local":
+ required = [
+ "smtp_host",
+ "smtp_port",
+ "notif_from",
+ ]
+
+ missing = []
+ for k in required:
+ if k not in email_config:
+ missing.append(k)
+
+ if (len(missing) > 0):
+ raise RuntimeError(
+ "email.password_reset_behaviour is set to 'local' "
+ "but required keys are missing: %s" %
+ (", ".join(["email." + k for k in missing]),)
+ )
+
+ # Templates for password reset emails
+ self.email_password_reset_template_html = email_config.get(
+ "password_reset_template_html", "password_reset.html",
+ )
+ self.email_password_reset_template_text = email_config.get(
+ "password_reset_template_text", "password_reset.txt",
+ )
+ self.email_password_reset_failure_template = email_config.get(
+ "password_reset_failure_template", "password_reset_failure.html",
+ )
+ # This template does not support any replaceable variables, so we will
+ # read it from the disk once during setup
+ email_password_reset_success_template = email_config.get(
+ "password_reset_success_template", "password_reset_success.html",
+ )
+
+ # Check templates exist
+ for f in [self.email_password_reset_template_html,
+ self.email_password_reset_template_text,
+ self.email_password_reset_failure_template,
+ email_password_reset_success_template]:
+ p = os.path.join(self.email_template_dir, f)
+ if not os.path.isfile(p):
+ raise ConfigError("Unable to find template file %s" % (p, ))
+
+ # Retrieve content of web templates
+ filepath = os.path.join(
+ self.email_template_dir,
+ email_password_reset_success_template,
+ )
+ self.email_password_reset_success_html_content = self.read_file(
+ filepath,
+ "email.password_reset_template_success_html",
+ )
+
+ if config.get("public_baseurl") is None:
+ raise RuntimeError(
+ "email.password_reset_behaviour is set to 'local' but no "
+ "public_baseurl is set. This is necessary to generate password "
+ "reset links"
+ )
+
+ if self.email_enable_notifs:
required = [
"smtp_host",
"smtp_port",
@@ -66,34 +194,13 @@ class EmailConfig(Config):
"email.enable_notifs is True but no public_baseurl is set"
)
- self.email_smtp_host = email_config["smtp_host"]
- self.email_smtp_port = email_config["smtp_port"]
- self.email_notif_from = email_config["notif_from"]
self.email_notif_template_html = email_config["notif_template_html"]
self.email_notif_template_text = email_config["notif_template_text"]
- self.email_expiry_template_html = email_config.get(
- "expiry_template_html", "notice_expiry.html",
- )
- self.email_expiry_template_text = email_config.get(
- "expiry_template_text", "notice_expiry.txt",
- )
-
- template_dir = email_config.get("template_dir")
- # we need an absolute path, because we change directory after starting (and
- # we don't yet know what auxilliary templates like mail.css we will need).
- # (Note that loading as package_resources with jinja.PackageLoader doesn't
- # work for the same reason.)
- if not template_dir:
- template_dir = pkg_resources.resource_filename(
- 'synapse', 'res/templates'
- )
- template_dir = os.path.abspath(template_dir)
for f in self.email_notif_template_text, self.email_notif_template_html:
- p = os.path.join(template_dir, f)
+ p = os.path.join(self.email_template_dir, f)
if not os.path.isfile(p):
raise ConfigError("Unable to find email template file %s" % (p, ))
- self.email_template_dir = template_dir
self.email_notif_for_new_users = email_config.get(
"notif_for_new_users", True
@@ -101,35 +208,24 @@ class EmailConfig(Config):
self.email_riot_base_url = email_config.get(
"riot_base_url", None
)
- self.email_smtp_user = email_config.get(
- "smtp_user", None
- )
- self.email_smtp_pass = email_config.get(
- "smtp_pass", None
+
+ if account_validity_renewal_enabled:
+ self.email_expiry_template_html = email_config.get(
+ "expiry_template_html", "notice_expiry.html",
)
- self.require_transport_security = email_config.get(
- "require_transport_security", False
+ self.email_expiry_template_text = email_config.get(
+ "expiry_template_text", "notice_expiry.txt",
)
- if "app_name" in email_config:
- self.email_app_name = email_config["app_name"]
- else:
- self.email_app_name = "Matrix"
- # make sure it's valid
- parsed = email.utils.parseaddr(self.email_notif_from)
- if parsed[1] == '':
- raise RuntimeError("Invalid notif_from address")
- else:
- self.email_enable_notifs = False
- # Not much point setting defaults for the rest: it would be an
- # error for them to be used.
+ for f in self.email_expiry_template_text, self.email_expiry_template_html:
+ p = os.path.join(self.email_template_dir, f)
+ if not os.path.isfile(p):
+ raise ConfigError("Unable to find email template file %s" % (p, ))
def default_config(self, config_dir_path, server_name, **kwargs):
return """
- # Enable sending emails for notification events or expiry notices
- # Defining a custom URL for Riot is only needed if email notifications
- # should contain links to a self-hosted installation of Riot; when set
- # the "app_name" setting is ignored.
+ # Enable sending emails for password resets, notification events or
+ # account expiry notices
#
# If your SMTP server requires authentication, the optional smtp_user &
# smtp_pass variables should be used
@@ -137,20 +233,62 @@ class EmailConfig(Config):
#email:
# enable_notifs: false
# smtp_host: "localhost"
- # smtp_port: 25
+ # smtp_port: 25 # SSL: 465, STARTTLS: 587
# smtp_user: "exampleusername"
# smtp_pass: "examplepassword"
# require_transport_security: False
# notif_from: "Your Friendly %(app)s Home Server <noreply@example.com>"
# app_name: Matrix
- # # if template_dir is unset, uses the example templates that are part of
- # # the Synapse distribution.
+ #
+ # # Enable email notifications by default
+ # notif_for_new_users: True
+ #
+ # # Defining a custom URL for Riot is only needed if email notifications
+ # # should contain links to a self-hosted installation of Riot; when set
+ # # the "app_name" setting is ignored
+ # riot_base_url: "http://localhost/riot"
+ #
+ # # Enable sending password reset emails via the configured, trusted
+ # # identity servers
+ # #
+ # # IMPORTANT! This will give a malicious or overtaken identity server
+ # # the ability to reset passwords for your users! Make absolutely sure
+ # # that you want to do this! It is strongly recommended that password
+ # # reset emails be sent by the homeserver instead
+ # #
+ # # If this option is set to false and SMTP options have not been
+ # # configured, resetting user passwords via email will be disabled
+ # #trust_identity_server_for_password_resets: false
+ #
+ # # Configure the time that a validation email or text message code
+ # # will expire after sending
+ # #
+ # # This is currently used for password resets
+ # #validation_token_lifetime: 1h
+ #
+ # # Template directory. All template files should be stored within this
+ # # directory
+ # #
# #template_dir: res/templates
+ #
+ # # Templates for email notifications
+ # #
# notif_template_html: notif_mail.html
# notif_template_text: notif_mail.txt
- # # Templates for account expiry notices.
+ #
+ # # Templates for account expiry notices
+ # #
# expiry_template_html: notice_expiry.html
# expiry_template_text: notice_expiry.txt
- # notif_for_new_users: True
- # riot_base_url: "http://localhost/riot"
+ #
+ # # Templates for password reset emails sent by the homeserver
+ # #
+ # #password_reset_template_html: password_reset.html
+ # #password_reset_template_text: password_reset.txt
+ #
+ # # Templates for password reset success and failure pages that a user
+ # # will see after attempting to reset their password
+ # #
+ # #password_reset_template_success_html: password_reset_success.html
+ # #password_reset_template_failure_html: password_reset_failure.html
"""
diff --git a/synapse/config/key.py b/synapse/config/key.py
index eb10259818..424875feae 100644
--- a/synapse/config/key.py
+++ b/synapse/config/key.py
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright 2015, 2016 OpenMarket Ltd
+# Copyright 2019 The Matrix.org Foundation C.I.C.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -17,6 +18,8 @@ import hashlib
import logging
import os
+import attr
+import jsonschema
from signedjson.key import (
NACL_ED25519,
decode_signing_key_base64,
@@ -32,11 +35,36 @@ from synapse.util.stringutils import random_string, random_string_with_symbols
from ._base import Config, ConfigError
+INSECURE_NOTARY_ERROR = """\
+Your server is configured to accept key server responses without signature
+validation or TLS certificate validation. This is likely to be very insecure. If
+you are *sure* you want to do this, set 'accept_keys_insecurely' on the
+keyserver configuration."""
+
+RELYING_ON_MATRIX_KEY_ERROR = """\
+Your server is configured to accept key server responses without TLS certificate
+validation, and which are only signed by the old (possibly compromised)
+matrix.org signing key 'ed25519:auto'. This likely isn't what you want to do,
+and you should enable 'federation_verify_certificates' in your configuration.
+
+If you are *sure* you want to do this, set 'accept_keys_insecurely' on the
+trusted_key_server configuration."""
+
+
logger = logging.getLogger(__name__)
-class KeyConfig(Config):
+@attr.s
+class TrustedKeyServer(object):
+ # string: name of the server.
+ server_name = attr.ib()
+
+ # dict[str,VerifyKey]|None: map from key id to key object, or None to disable
+ # signature verification.
+ verify_keys = attr.ib(default=None)
+
+class KeyConfig(Config):
def read_config(self, config):
# the signing key can be specified inline or in a separate file
if "signing_key" in config:
@@ -49,16 +77,27 @@ class KeyConfig(Config):
config.get("old_signing_keys", {})
)
self.key_refresh_interval = self.parse_duration(
- config.get("key_refresh_interval", "1d"),
+ config.get("key_refresh_interval", "1d")
)
- self.perspectives = self.read_perspectives(
- config.get("perspectives", {}).get("servers", {
- "matrix.org": {"verify_keys": {
- "ed25519:auto": {
- "key": "Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw",
- }
- }}
- })
+
+ # if neither trusted_key_servers nor perspectives are given, use the default.
+ if "perspectives" not in config and "trusted_key_servers" not in config:
+ key_servers = [{"server_name": "matrix.org"}]
+ else:
+ key_servers = config.get("trusted_key_servers", [])
+
+ if not isinstance(key_servers, list):
+ raise ConfigError(
+ "trusted_key_servers, if given, must be a list, not a %s"
+ % (type(key_servers).__name__,)
+ )
+
+ # merge the 'perspectives' config into the 'trusted_key_servers' config.
+ key_servers.extend(_perspectives_to_key_servers(config))
+
+ # list of TrustedKeyServer objects
+ self.key_servers = list(
+ _parse_key_servers(key_servers, self.federation_verify_certificates)
)
self.macaroon_secret_key = config.get(
@@ -78,8 +117,9 @@ class KeyConfig(Config):
# falsification of values
self.form_secret = config.get("form_secret", None)
- def default_config(self, config_dir_path, server_name, generate_secrets=False,
- **kwargs):
+ def default_config(
+ self, config_dir_path, server_name, generate_secrets=False, **kwargs
+ ):
base_key_name = os.path.join(config_dir_path, server_name)
if generate_secrets:
@@ -91,7 +131,8 @@ class KeyConfig(Config):
macaroon_secret_key = "# macaroon_secret_key: <PRIVATE STRING>"
form_secret = "# form_secret: <PRIVATE STRING>"
- return """\
+ return (
+ """\
# a secret which is used to sign access tokens. If none is specified,
# the registration_shared_secret is used, if one is given; otherwise,
# a secret key is derived from the signing key.
@@ -133,33 +174,53 @@ class KeyConfig(Config):
# The trusted servers to download signing keys from.
#
- #perspectives:
- # servers:
- # "matrix.org":
- # verify_keys:
- # "ed25519:auto":
- # key: "Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw"
- """ % locals()
-
- def read_perspectives(self, perspectives_servers):
- servers = {}
- for server_name, server_config in perspectives_servers.items():
- for key_id, key_data in server_config["verify_keys"].items():
- if is_signing_algorithm_supported(key_id):
- key_base64 = key_data["key"]
- key_bytes = decode_base64(key_base64)
- verify_key = decode_verify_key_bytes(key_id, key_bytes)
- servers.setdefault(server_name, {})[key_id] = verify_key
- return servers
+ # When we need to fetch a signing key, each server is tried in parallel.
+ #
+ # Normally, the connection to the key server is validated via TLS certificates.
+ # Additional security can be provided by configuring a `verify key`, which
+ # will make synapse check that the response is signed by that key.
+ #
+ # This setting supercedes an older setting named `perspectives`. The old format
+ # is still supported for backwards-compatibility, but it is deprecated.
+ #
+ # Options for each entry in the list include:
+ #
+ # server_name: the name of the server. required.
+ #
+ # verify_keys: an optional map from key id to base64-encoded public key.
+ # If specified, we will check that the response is signed by at least
+ # one of the given keys.
+ #
+ # accept_keys_insecurely: a boolean. Normally, if `verify_keys` is unset,
+ # and federation_verify_certificates is not `true`, synapse will refuse
+ # to start, because this would allow anyone who can spoof DNS responses
+ # to masquerade as the trusted key server. If you know what you are doing
+ # and are sure that your network environment provides a secure connection
+ # to the key server, you can set this to `true` to override this
+ # behaviour.
+ #
+ # An example configuration might look like:
+ #
+ #trusted_key_servers:
+ # - server_name: "my_trusted_server.example.com"
+ # verify_keys:
+ # "ed25519:auto": "abcdefghijklmnopqrstuvwxyzabcdefghijklmopqr"
+ # - server_name: "my_other_trusted_server.example.com"
+ #
+ # The default configuration is:
+ #
+ #trusted_key_servers:
+ # - server_name: "matrix.org"
+ """
+ % locals()
+ )
def read_signing_key(self, signing_key_path):
signing_keys = self.read_file(signing_key_path, "signing_key")
try:
return read_signing_keys(signing_keys.splitlines(True))
except Exception as e:
- raise ConfigError(
- "Error reading signing_key: %s" % (str(e))
- )
+ raise ConfigError("Error reading signing_key: %s" % (str(e)))
def read_old_signing_keys(self, old_signing_keys):
keys = {}
@@ -182,9 +243,7 @@ class KeyConfig(Config):
if not self.path_exists(signing_key_path):
with open(signing_key_path, "w") as signing_key_file:
key_id = "a_" + random_string(4)
- write_signing_keys(
- signing_key_file, (generate_signing_key(key_id),),
- )
+ write_signing_keys(signing_key_file, (generate_signing_key(key_id),))
else:
signing_keys = self.read_file(signing_key_path, "signing_key")
if len(signing_keys.split("\n")[0].split()) == 1:
@@ -194,6 +253,116 @@ class KeyConfig(Config):
NACL_ED25519, key_id, signing_keys.split("\n")[0]
)
with open(signing_key_path, "w") as signing_key_file:
- write_signing_keys(
- signing_key_file, (key,),
+ write_signing_keys(signing_key_file, (key,))
+
+
+def _perspectives_to_key_servers(config):
+ """Convert old-style 'perspectives' configs into new-style 'trusted_key_servers'
+
+ Returns an iterable of entries to add to trusted_key_servers.
+ """
+
+ # 'perspectives' looks like:
+ #
+ # {
+ # "servers": {
+ # "matrix.org": {
+ # "verify_keys": {
+ # "ed25519:auto": {
+ # "key": "Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw"
+ # }
+ # }
+ # }
+ # }
+ # }
+ #
+ # 'trusted_keys' looks like:
+ #
+ # [
+ # {
+ # "server_name": "matrix.org",
+ # "verify_keys": {
+ # "ed25519:auto": "Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw",
+ # }
+ # }
+ # ]
+
+ perspectives_servers = config.get("perspectives", {}).get("servers", {})
+
+ for server_name, server_opts in perspectives_servers.items():
+ trusted_key_server_entry = {"server_name": server_name}
+ verify_keys = server_opts.get("verify_keys")
+ if verify_keys is not None:
+ trusted_key_server_entry["verify_keys"] = {
+ key_id: key_data["key"] for key_id, key_data in verify_keys.items()
+ }
+ yield trusted_key_server_entry
+
+
+TRUSTED_KEY_SERVERS_SCHEMA = {
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "description": "schema for the trusted_key_servers setting",
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "server_name": {"type": "string"},
+ "verify_keys": {
+ "type": "object",
+ # each key must be a base64 string
+ "additionalProperties": {"type": "string"},
+ },
+ },
+ "required": ["server_name"],
+ },
+}
+
+
+def _parse_key_servers(key_servers, federation_verify_certificates):
+ try:
+ jsonschema.validate(key_servers, TRUSTED_KEY_SERVERS_SCHEMA)
+ except jsonschema.ValidationError as e:
+ raise ConfigError("Unable to parse 'trusted_key_servers': " + e.message)
+
+ for server in key_servers:
+ server_name = server["server_name"]
+ result = TrustedKeyServer(server_name=server_name)
+
+ verify_keys = server.get("verify_keys")
+ if verify_keys is not None:
+ result.verify_keys = {}
+ for key_id, key_base64 in verify_keys.items():
+ if not is_signing_algorithm_supported(key_id):
+ raise ConfigError(
+ "Unsupported signing algorithm on key %s for server %s in "
+ "trusted_key_servers" % (key_id, server_name)
+ )
+ try:
+ key_bytes = decode_base64(key_base64)
+ verify_key = decode_verify_key_bytes(key_id, key_bytes)
+ except Exception as e:
+ raise ConfigError(
+ "Unable to parse key %s for server %s in "
+ "trusted_key_servers: %s" % (key_id, server_name, e)
)
+
+ result.verify_keys[key_id] = verify_key
+
+ if (
+ not federation_verify_certificates and
+ not server.get("accept_keys_insecurely")
+ ):
+ _assert_keyserver_has_verify_keys(result)
+
+ yield result
+
+
+def _assert_keyserver_has_verify_keys(trusted_key_server):
+ if not trusted_key_server.verify_keys:
+ raise ConfigError(INSECURE_NOTARY_ERROR)
+
+ # also check that they are not blindly checking the old matrix.org key
+ if trusted_key_server.server_name == "matrix.org" and any(
+ key_id == "ed25519:auto" for key_id in trusted_key_server.verify_keys
+ ):
+ raise ConfigError(RELYING_ON_MATRIX_KEY_ERROR)
diff --git a/synapse/config/server.py b/synapse/config/server.py
index e763e19e15..7d56e2d141 100644
--- a/synapse/config/server.py
+++ b/synapse/config/server.py
@@ -36,7 +36,7 @@ logger = logging.Logger(__name__)
# in the list.
DEFAULT_BIND_ADDRESSES = ['::', '0.0.0.0']
-DEFAULT_ROOM_VERSION = "1"
+DEFAULT_ROOM_VERSION = "4"
class ServerConfig(Config):
@@ -585,6 +585,22 @@ class ServerConfig(Config):
# Monthly Active User Blocking
#
+ # Used in cases where the admin or server owner wants to limit to the
+ # number of monthly active users.
+ #
+ # 'limit_usage_by_mau' disables/enables monthly active user blocking. When
+ # anabled and a limit is reached the server returns a 'ResourceLimitError'
+ # with error type Codes.RESOURCE_LIMIT_EXCEEDED
+ #
+ # 'max_mau_value' is the hard limit of monthly active users above which
+ # the server will start blocking user actions.
+ #
+ # 'mau_trial_days' is a means to add a grace period for active users. It
+ # means that users must be active for this number of days before they
+ # can be considered active and guards against the case where lots of users
+ # sign up in a short space of time never to return after their initial
+ # session.
+ #
#limit_usage_by_mau: False
#max_mau_value: 50
#mau_trial_days: 2
diff --git a/synapse/config/tls.py b/synapse/config/tls.py
index 72dd5926f9..658f9dd361 100644
--- a/synapse/config/tls.py
+++ b/synapse/config/tls.py
@@ -74,7 +74,7 @@ class TlsConfig(Config):
# Whether to verify certificates on outbound federation traffic
self.federation_verify_certificates = config.get(
- "federation_verify_certificates", False,
+ "federation_verify_certificates", True,
)
# Whitelist of domains to not verify certificates for
@@ -107,7 +107,7 @@ class TlsConfig(Config):
certs = []
for ca_file in custom_ca_list:
logger.debug("Reading custom CA certificate file: %s", ca_file)
- content = self.read_file(ca_file)
+ content = self.read_file(ca_file, "federation_custom_ca_list")
# Parse the CA certificates
try:
@@ -241,12 +241,12 @@ class TlsConfig(Config):
#
#tls_private_key_path: "%(tls_private_key_path)s"
- # Whether to verify TLS certificates when sending federation traffic.
+ # Whether to verify TLS server certificates for outbound federation requests.
#
- # This currently defaults to `false`, however this will change in
- # Synapse 1.0 when valid federation certificates will be required.
+ # Defaults to `true`. To disable certificate verification, uncomment the
+ # following line.
#
- #federation_verify_certificates: true
+ #federation_verify_certificates: false
# Skip federation certificate verification on the following whitelist
# of domains.
|