mirror of
https://github.com/element-hq/synapse.git
synced 2024-11-25 11:05:49 +03:00
Config option for verifying federation certificates (MSC 1711) (#4967)
This commit is contained in:
parent
788163e204
commit
6824ddd93d
8 changed files with 158 additions and 17 deletions
1
changelog.d/4967.feature
Normal file
1
changelog.d/4967.feature
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Implementation of [MSC1711](https://github.com/matrix-org/matrix-doc/pull/1711) including config options for requiring valid TLS certificates for federation traffic, the ability to disable TLS validation for specific domains, and the ability to specify your own list of CA certificates.
|
|
@ -177,7 +177,6 @@ You can do this with a `.well-known` file as follows:
|
||||||
on `customer.example.net:8000` it correctly handles HTTP requests with
|
on `customer.example.net:8000` it correctly handles HTTP requests with
|
||||||
Host header set to `customer.example.net:8000`.
|
Host header set to `customer.example.net:8000`.
|
||||||
|
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
### Synapse 0.99.0 has just been released, what do I need to do right now?
|
### Synapse 0.99.0 has just been released, what do I need to do right now?
|
||||||
|
|
|
@ -260,6 +260,40 @@ listeners:
|
||||||
#
|
#
|
||||||
#tls_private_key_path: "CONFDIR/SERVERNAME.tls.key"
|
#tls_private_key_path: "CONFDIR/SERVERNAME.tls.key"
|
||||||
|
|
||||||
|
# Whether to verify TLS certificates when sending federation traffic.
|
||||||
|
#
|
||||||
|
# This currently defaults to `false`, however this will change in
|
||||||
|
# Synapse 1.0 when valid federation certificates will be required.
|
||||||
|
#
|
||||||
|
#federation_verify_certificates: true
|
||||||
|
|
||||||
|
# Skip federation certificate verification on the following whitelist
|
||||||
|
# of domains.
|
||||||
|
#
|
||||||
|
# This setting should only be used in very specific cases, such as
|
||||||
|
# federation over Tor hidden services and similar. For private networks
|
||||||
|
# of homeservers, you likely want to use a private CA instead.
|
||||||
|
#
|
||||||
|
# Only effective if federation_verify_certicates is `true`.
|
||||||
|
#
|
||||||
|
#federation_certificate_verification_whitelist:
|
||||||
|
# - lon.example.com
|
||||||
|
# - *.domain.com
|
||||||
|
# - *.onion
|
||||||
|
|
||||||
|
# List of custom certificate authorities for federation traffic.
|
||||||
|
#
|
||||||
|
# This setting should only normally be used within a private network of
|
||||||
|
# homeservers.
|
||||||
|
#
|
||||||
|
# Note that this list will replace those that are provided by your
|
||||||
|
# operating environment. Certificates must be in PEM format.
|
||||||
|
#
|
||||||
|
#federation_custom_ca_list:
|
||||||
|
# - myCA1.pem
|
||||||
|
# - myCA2.pem
|
||||||
|
# - myCA3.pem
|
||||||
|
|
||||||
# ACME support: This will configure Synapse to request a valid TLS certificate
|
# ACME support: This will configure Synapse to request a valid TLS certificate
|
||||||
# for your configured `server_name` via Let's Encrypt.
|
# for your configured `server_name` via Let's Encrypt.
|
||||||
#
|
#
|
||||||
|
|
|
@ -114,11 +114,13 @@ class ServerConfig(Config):
|
||||||
# FIXME: federation_domain_whitelist needs sytests
|
# FIXME: federation_domain_whitelist needs sytests
|
||||||
self.federation_domain_whitelist = None
|
self.federation_domain_whitelist = None
|
||||||
federation_domain_whitelist = config.get(
|
federation_domain_whitelist = config.get(
|
||||||
"federation_domain_whitelist", None
|
"federation_domain_whitelist", None,
|
||||||
)
|
)
|
||||||
# turn the whitelist into a hash for speed of lookup
|
|
||||||
if federation_domain_whitelist is not None:
|
if federation_domain_whitelist is not None:
|
||||||
|
# turn the whitelist into a hash for speed of lookup
|
||||||
self.federation_domain_whitelist = {}
|
self.federation_domain_whitelist = {}
|
||||||
|
|
||||||
for domain in federation_domain_whitelist:
|
for domain in federation_domain_whitelist:
|
||||||
self.federation_domain_whitelist[domain] = True
|
self.federation_domain_whitelist[domain] = True
|
||||||
|
|
||||||
|
|
|
@ -24,8 +24,10 @@ import six
|
||||||
from unpaddedbase64 import encode_base64
|
from unpaddedbase64 import encode_base64
|
||||||
|
|
||||||
from OpenSSL import crypto
|
from OpenSSL import crypto
|
||||||
|
from twisted.internet._sslverify import Certificate, trustRootFromCertificates
|
||||||
|
|
||||||
from synapse.config._base import Config, ConfigError
|
from synapse.config._base import Config, ConfigError
|
||||||
|
from synapse.util import glob_to_regex
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -70,6 +72,53 @@ class TlsConfig(Config):
|
||||||
|
|
||||||
self.tls_fingerprints = list(self._original_tls_fingerprints)
|
self.tls_fingerprints = list(self._original_tls_fingerprints)
|
||||||
|
|
||||||
|
# Whether to verify certificates on outbound federation traffic
|
||||||
|
self.federation_verify_certificates = config.get(
|
||||||
|
"federation_verify_certificates", False,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Whitelist of domains to not verify certificates for
|
||||||
|
fed_whitelist_entries = config.get(
|
||||||
|
"federation_certificate_verification_whitelist", [],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Support globs (*) in whitelist values
|
||||||
|
self.federation_certificate_verification_whitelist = []
|
||||||
|
for entry in fed_whitelist_entries:
|
||||||
|
# Convert globs to regex
|
||||||
|
entry_regex = glob_to_regex(entry)
|
||||||
|
self.federation_certificate_verification_whitelist.append(entry_regex)
|
||||||
|
|
||||||
|
# List of custom certificate authorities for federation traffic validation
|
||||||
|
custom_ca_list = config.get(
|
||||||
|
"federation_custom_ca_list", None,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Read in and parse custom CA certificates
|
||||||
|
self.federation_ca_trust_root = None
|
||||||
|
if custom_ca_list is not None:
|
||||||
|
if len(custom_ca_list) == 0:
|
||||||
|
# A trustroot cannot be generated without any CA certificates.
|
||||||
|
# Raise an error if this option has been specified without any
|
||||||
|
# corresponding certificates.
|
||||||
|
raise ConfigError("federation_custom_ca_list specified without "
|
||||||
|
"any certificate files")
|
||||||
|
|
||||||
|
certs = []
|
||||||
|
for ca_file in custom_ca_list:
|
||||||
|
logger.debug("Reading custom CA certificate file: %s", ca_file)
|
||||||
|
content = self.read_file(ca_file)
|
||||||
|
|
||||||
|
# Parse the CA certificates
|
||||||
|
try:
|
||||||
|
cert_base = Certificate.loadPEM(content)
|
||||||
|
certs.append(cert_base)
|
||||||
|
except Exception as e:
|
||||||
|
raise ConfigError("Error parsing custom CA certificate file %s: %s"
|
||||||
|
% (ca_file, e))
|
||||||
|
|
||||||
|
self.federation_ca_trust_root = trustRootFromCertificates(certs)
|
||||||
|
|
||||||
# This config option applies to non-federation HTTP clients
|
# This config option applies to non-federation HTTP clients
|
||||||
# (e.g. for talking to recaptcha, identity servers, and such)
|
# (e.g. for talking to recaptcha, identity servers, and such)
|
||||||
# It should never be used in production, and is intended for
|
# It should never be used in production, and is intended for
|
||||||
|
@ -99,15 +148,15 @@ class TlsConfig(Config):
|
||||||
try:
|
try:
|
||||||
with open(self.tls_certificate_file, 'rb') as f:
|
with open(self.tls_certificate_file, 'rb') as f:
|
||||||
cert_pem = f.read()
|
cert_pem = f.read()
|
||||||
except Exception:
|
except Exception as e:
|
||||||
logger.exception("Failed to read existing certificate off disk!")
|
raise ConfigError("Failed to read existing certificate file %s: %s"
|
||||||
raise
|
% (self.tls_certificate_file, e))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tls_certificate = crypto.load_certificate(crypto.FILETYPE_PEM, cert_pem)
|
tls_certificate = crypto.load_certificate(crypto.FILETYPE_PEM, cert_pem)
|
||||||
except Exception:
|
except Exception as e:
|
||||||
logger.exception("Failed to parse existing certificate off disk!")
|
raise ConfigError("Failed to parse existing certificate file %s: %s"
|
||||||
raise
|
% (self.tls_certificate_file, e))
|
||||||
|
|
||||||
if not allow_self_signed:
|
if not allow_self_signed:
|
||||||
if tls_certificate.get_subject() == tls_certificate.get_issuer():
|
if tls_certificate.get_subject() == tls_certificate.get_issuer():
|
||||||
|
@ -192,6 +241,40 @@ class TlsConfig(Config):
|
||||||
#
|
#
|
||||||
#tls_private_key_path: "%(tls_private_key_path)s"
|
#tls_private_key_path: "%(tls_private_key_path)s"
|
||||||
|
|
||||||
|
# Whether to verify TLS certificates when sending federation traffic.
|
||||||
|
#
|
||||||
|
# This currently defaults to `false`, however this will change in
|
||||||
|
# Synapse 1.0 when valid federation certificates will be required.
|
||||||
|
#
|
||||||
|
#federation_verify_certificates: true
|
||||||
|
|
||||||
|
# Skip federation certificate verification on the following whitelist
|
||||||
|
# of domains.
|
||||||
|
#
|
||||||
|
# This setting should only be used in very specific cases, such as
|
||||||
|
# federation over Tor hidden services and similar. For private networks
|
||||||
|
# of homeservers, you likely want to use a private CA instead.
|
||||||
|
#
|
||||||
|
# Only effective if federation_verify_certicates is `true`.
|
||||||
|
#
|
||||||
|
#federation_certificate_verification_whitelist:
|
||||||
|
# - lon.example.com
|
||||||
|
# - *.domain.com
|
||||||
|
# - *.onion
|
||||||
|
|
||||||
|
# List of custom certificate authorities for federation traffic.
|
||||||
|
#
|
||||||
|
# This setting should only normally be used within a private network of
|
||||||
|
# homeservers.
|
||||||
|
#
|
||||||
|
# Note that this list will replace those that are provided by your
|
||||||
|
# operating environment. Certificates must be in PEM format.
|
||||||
|
#
|
||||||
|
#federation_custom_ca_list:
|
||||||
|
# - myCA1.pem
|
||||||
|
# - myCA2.pem
|
||||||
|
# - myCA3.pem
|
||||||
|
|
||||||
# ACME support: This will configure Synapse to request a valid TLS certificate
|
# ACME support: This will configure Synapse to request a valid TLS certificate
|
||||||
# for your configured `server_name` via Let's Encrypt.
|
# for your configured `server_name` via Let's Encrypt.
|
||||||
#
|
#
|
||||||
|
|
|
@ -18,10 +18,10 @@ import logging
|
||||||
from zope.interface import implementer
|
from zope.interface import implementer
|
||||||
|
|
||||||
from OpenSSL import SSL, crypto
|
from OpenSSL import SSL, crypto
|
||||||
from twisted.internet._sslverify import _defaultCurveName
|
from twisted.internet._sslverify import ClientTLSOptions, _defaultCurveName
|
||||||
from twisted.internet.abstract import isIPAddress, isIPv6Address
|
from twisted.internet.abstract import isIPAddress, isIPv6Address
|
||||||
from twisted.internet.interfaces import IOpenSSLClientConnectionCreator
|
from twisted.internet.interfaces import IOpenSSLClientConnectionCreator
|
||||||
from twisted.internet.ssl import CertificateOptions, ContextFactory
|
from twisted.internet.ssl import CertificateOptions, ContextFactory, platformTrust
|
||||||
from twisted.python.failure import Failure
|
from twisted.python.failure import Failure
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -90,7 +90,7 @@ def _tolerateErrors(wrapped):
|
||||||
|
|
||||||
|
|
||||||
@implementer(IOpenSSLClientConnectionCreator)
|
@implementer(IOpenSSLClientConnectionCreator)
|
||||||
class ClientTLSOptions(object):
|
class ClientTLSOptionsNoVerify(object):
|
||||||
"""
|
"""
|
||||||
Client creator for TLS without certificate identity verification. This is a
|
Client creator for TLS without certificate identity verification. This is a
|
||||||
copy of twisted.internet._sslverify.ClientTLSOptions with the identity
|
copy of twisted.internet._sslverify.ClientTLSOptions with the identity
|
||||||
|
@ -127,9 +127,30 @@ class ClientTLSOptionsFactory(object):
|
||||||
to remote servers for federation."""
|
to remote servers for federation."""
|
||||||
|
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
# We don't use config options yet
|
self._config = config
|
||||||
self._options = CertificateOptions(verify=False)
|
self._options_noverify = CertificateOptions()
|
||||||
|
|
||||||
|
# Check if we're using a custom list of a CA certificates
|
||||||
|
trust_root = config.federation_ca_trust_root
|
||||||
|
if trust_root is None:
|
||||||
|
# Use CA root certs provided by OpenSSL
|
||||||
|
trust_root = platformTrust()
|
||||||
|
|
||||||
|
self._options_verify = CertificateOptions(trustRoot=trust_root)
|
||||||
|
|
||||||
def get_options(self, host):
|
def get_options(self, host):
|
||||||
# Use _makeContext so that we get a fresh OpenSSL CTX each time.
|
# Use _makeContext so that we get a fresh OpenSSL CTX each time.
|
||||||
return ClientTLSOptions(host, self._options._makeContext())
|
|
||||||
|
# Check if certificate verification has been enabled
|
||||||
|
should_verify = self._config.federation_verify_certificates
|
||||||
|
|
||||||
|
# Check if we've disabled certificate verification for this host
|
||||||
|
if should_verify:
|
||||||
|
for regex in self._config.federation_certificate_verification_whitelist:
|
||||||
|
if regex.match(host):
|
||||||
|
should_verify = False
|
||||||
|
break
|
||||||
|
|
||||||
|
if should_verify:
|
||||||
|
return ClientTLSOptions(host, self._options_verify._makeContext())
|
||||||
|
return ClientTLSOptionsNoVerify(host, self._options_noverify._makeContext())
|
||||||
|
|
|
@ -149,7 +149,7 @@ class MatrixFederationAgent(object):
|
||||||
tls_options = None
|
tls_options = None
|
||||||
else:
|
else:
|
||||||
tls_options = self._tls_client_options_factory.get_options(
|
tls_options = self._tls_client_options_factory.get_options(
|
||||||
res.tls_server_name.decode("ascii")
|
res.tls_server_name.decode("ascii"),
|
||||||
)
|
)
|
||||||
|
|
||||||
# make sure that the Host header is set correctly
|
# make sure that the Host header is set correctly
|
||||||
|
|
|
@ -39,6 +39,7 @@ from synapse.util.logcontext import LoggingContext
|
||||||
from tests.http import ServerTLSContext
|
from tests.http import ServerTLSContext
|
||||||
from tests.server import FakeTransport, ThreadedMemoryReactorClock
|
from tests.server import FakeTransport, ThreadedMemoryReactorClock
|
||||||
from tests.unittest import TestCase
|
from tests.unittest import TestCase
|
||||||
|
from tests.utils import default_config
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -53,7 +54,7 @@ class MatrixFederationAgentTests(TestCase):
|
||||||
|
|
||||||
self.agent = MatrixFederationAgent(
|
self.agent = MatrixFederationAgent(
|
||||||
reactor=self.reactor,
|
reactor=self.reactor,
|
||||||
tls_client_options_factory=ClientTLSOptionsFactory(None),
|
tls_client_options_factory=ClientTLSOptionsFactory(default_config("test")),
|
||||||
_well_known_tls_policy=TrustingTLSPolicyForHTTPS(),
|
_well_known_tls_policy=TrustingTLSPolicyForHTTPS(),
|
||||||
_srv_resolver=self.mock_resolver,
|
_srv_resolver=self.mock_resolver,
|
||||||
_well_known_cache=self.well_known_cache,
|
_well_known_cache=self.well_known_cache,
|
||||||
|
|
Loading…
Reference in a new issue